Compare commits

...

84 Commits

Author SHA1 Message Date
Ginger bec8ee7599 fix: Add unstable feature flag 2026-05-26 13:17:05 +00:00
Ginger 66e35aaaba chore: News fragment 2026-05-26 13:17:05 +00:00
Ginger 9c523e8fa1 feat: Add support for MSC4466 2026-05-26 13:17:05 +00:00
Renovate Bot 793d399477 chore(deps): update node-patch-updates to v2.0.13 2026-05-26 13:12:29 +00:00
Renovate Bot 15d69aefbb chore(deps): update rust crate minicbor-serde to 0.7.0 2026-05-26 13:12:09 +00:00
Renovate Bot 77b1652f4a chore(deps): lock file maintenance 2026-05-26 11:59:18 +00:00
Renovate Bot 5f9594363d chore(deps): update github-actions-digest 2026-05-26 05:18:06 +00:00
timedout 5cba4b126f style: Combine "unsupported version" checks 2026-05-25 19:44:40 +01:00
timedout d8a7f7c7ca perf: Skip updating child/parent spaces in upgrade when sender is not joined 2026-05-25 19:40:15 +01:00
timedout d3fca86dec style: Drop unstable prefix in function definitions 2026-05-25 19:38:17 +01:00
timedout 5f88abf341 fix: Correctly copy parents and children during upgrade 2026-05-25 19:37:29 +01:00
timedout 416814094c fix: Correctly update space children on upgrade 2026-05-25 19:37:29 +01:00
timedout 5b8799e71f fix: Include sender in older room versions 2026-05-25 19:37:29 +01:00
timedout cc5349ee57 fix: Don't de-power creators when downgrading from v12 to earlier versions 2026-05-25 19:37:29 +01:00
timedout 7b68572b2e fix: Don't give v12 rooms room IDs 2026-05-25 19:37:29 +01:00
timedout 057eb9f644 fix: Adhere to MSC4168 more strongly & in definition order 2026-05-25 19:37:29 +01:00
timedout 253603edbc refactor: Fix several bugs in upgrade endpoint, update MSC4168 impl 2026-05-25 19:37:25 +01:00
timedout b771b9d160 style: Fix typo 2026-05-25 18:26:48 +01:00
timedout eb829c2951 fix: Ensure event_id is correctly stripped before verifying policy server signature 2026-05-25 18:20:57 +01:00
timedout d32b39181a fix: Don't return early if the policy server does something stupid
Spec compliance is for nerds I guess
2026-05-25 18:17:41 +01:00
timedout 72b99a1f84 style: Reformat 2026-05-25 18:17:40 +01:00
timedout ae37f218a2 perf: Avoid cloning incoming PDUs to check them
Also allows us to store signatures on PDUs received over federation that we got a fresh signature for
2026-05-25 18:17:29 +01:00
timedout 40cecca103 feat: Add extract_signature helper 2026-05-25 18:17:13 +01:00
timedout 2a80a82f74 style: Document functions 2026-05-25 18:17:13 +01:00
timedout fbf4eac2dc fix: Ensure event_id is removed before policy-checking event 2026-05-25 18:17:13 +01:00
timedout 4784010702 fix: Ensure policy server signed with the correct key 2026-05-25 18:17:13 +01:00
timedout 1c88854a54 feat: Enable shutdown interrupt in ratelimit handler 2026-05-25 18:17:12 +01:00
timedout e0fe71c708 feat: Follow spec more closely, code clean up, use ruma request type 2026-05-25 18:17:12 +01:00
timedout 0f0dcb4f58 fix: Return Forbidden instead of internal error when PS doesn't sign 2026-05-25 18:17:12 +01:00
timedout 367c42ad28 fix: Treat malformed policy config events as missing 2026-05-25 18:17:12 +01:00
timedout c8e0f7ebd3 style: Reformat 2026-05-25 18:17:10 +01:00
timedout fdc9aec534 fix: Verify policy server signatures on all events, not just timeline ones
style: Clarifications

style: Clippy
2026-05-25 18:16:55 +01:00
timedout 5f9cc83b18 feat: Support advertising a policy server public key in well-known
# Conflicts:
#	src/api/client/well_known.rs
#	src/core/config/mod.rs
2026-05-25 18:14:58 +01:00
timedout 47051af392 feat: Update policy server implementation to be closer to latest spec
Untested

chore: Add news fragment

feat: Support stable policy servers

feat: Don't attempt erroneous loopback federation for policy server checks

refactor: Update PS upgrade to use new ruma

fix: Only check loopback via after attempting incoming verification
2026-05-25 18:14:54 +01:00
timedout c1a6e649da feat: Combine local & remote force join 2026-05-25 18:01:08 +01:00
timedout 1d172be503 style: Authentication -> authorization 2026-05-25 17:55:44 +01:00
timedout f01e119890 style: Make graph output easier to comprehend 2026-05-25 17:53:53 +01:00
timedout 4d27a935d6 perf: Move rejected events check 2026-05-25 17:27:56 +01:00
timedout 512a96f832 style: Warn -> debug_warn 2026-05-25 17:18:25 +01:00
timedout 6715f63acc fix: Don't serve events over s2s that are rejected 2026-05-25 17:18:25 +01:00
timedout 3764faeefc style: Reformat 2026-05-25 17:18:25 +01:00
timedout 5d4b7bfea3 fix: Store PDUs as outliers even when rejected
This prevents future network lookups if we've already rejected an event and see a reference to it again
2026-05-25 17:18:24 +01:00
timedout 4df08779e3 chore: Update newsfrag 2026-05-25 17:18:24 +01:00
timedout 6b835a327d style: Rename unmark_pdu to clear_pdu_markers 2026-05-25 17:18:24 +01:00
timedout 7dd61cd560 feat: Add !admin debug show-auth-chain
Because why not am I right lads
2026-05-25 17:18:24 +01:00
timedout d9535eccf1 feat: Make !admin debug get-pdu more informative 2026-05-25 17:18:24 +01:00
timedout a97f91e079 fix: Don't hard fail on events which depend on soft-failed events 2026-05-25 17:18:24 +01:00
timedout f0401b4fc7 fix: Mark events as rejected in more places, correct soft-fail extremity behaviour 2026-05-25 17:18:24 +01:00
timedout cda64b880a chore: Add news fragment for 1747
Co-Authored-By: star <star@nexy7574.co.uk>
2026-05-25 17:18:23 +01:00
timedout 1f6cab9e2e feat: Implement event rejection
Co-Authored-By: star <star@nexy7574.co.uk>
2026-05-25 17:18:23 +01:00
Renovate Bot afa80576f4 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v43.195.3 2026-05-25 05:17:59 +00:00
Henry-Hiles 5a63eb729c fix: disable rocksdb on nix by default 2026-05-24 12:16:19 -04:00
Henry-Hiles 27da50136e chore: cleanup unused args in nix package 2026-05-24 10:48:38 -04:00
Bart Oostveen db724b67ff fix: use in-flake version of rocksdb instead of nixpkgs' upstream package
Fixes #1801
2026-05-23 20:00:40 +02:00
Renovate Bot 14a0d2f538 chore(deps): update rust crate serde_json to v1.0.150 2026-05-22 15:26:55 +00:00
Renovate Bot 3b9932e09c chore(deps): update rust crate built to v0.8.1 2026-05-22 05:04:25 +00:00
new-years-eve 02409c06b8 feat: Add config check to make sure default ACL doesn't self-ban the server 2026-05-21 17:09:43 +00:00
new-years-eve bb51db0d7d add changelog 2026-05-21 17:09:43 +00:00
new-years-eve 834f2caffe feat: Add config option for a default ACL on room creation
This allows for rooms to be created with a m.room.server_acl event by
default. This event can be thought of as part of the initial_state
events, although it is not provided by the client.

Implements #775
2026-05-21 17:09:43 +00:00
Renovate Bot 202786c46b chore(deps): update rust crate either to v1.16.0 2026-05-21 15:53:08 +00:00
Ginger 035bfea93c fix: Correct error code 2026-05-21 12:11:03 +00:00
Ginger 185f8c42dc fix: Properly check forbidden_remote_server_names for incoming requests 2026-05-21 12:11:03 +00:00
Ginger d5fc81d39e fix: Remove check for active user when propagating profile updates 2026-05-21 12:10:58 +00:00
Ginger 1cd0228d87 fix: Restore functionality of require_auth_for_profile_requests 2026-05-21 12:10:48 +00:00
Ginger 4968d4c8b7 docs: Clarify documentation for require_email_for_registration 2026-05-21 12:10:44 +00:00
Renovate Bot bb6ec1f352 chore(deps): update node-patch-updates to v2.0.12 2026-05-19 21:17:07 +00:00
renovate 14602e730e chore(Nix): Updated flake hashes 2026-05-19 19:42:59 +00:00
Jade Ellis cdaca69f3a chore: Update renovate config 2026-05-19 20:40:02 +01:00
Jade Ellis 9c1d5b3e95 chore: Upgrade RocksDB to 11.1.1 2026-05-19 20:30:49 +01:00
Jade Ellis 3987331c3b chore: Fix clippy warnings 2026-05-19 20:26:04 +01:00
Jade Ellis cb3ebcf24e chore: Upgrade rust
This also upbrades the debian version to trixie, because the new LLVM
version doesn't seem to have a build
2026-05-19 20:22:21 +01:00
Jade Ellis 2d4bf1b35f chore: Upgrade deps 2026-05-19 19:28:59 +01:00
aviac 388cbeb60e build(nix): allow overriding TARGET_CPU 2026-05-19 09:58:48 +00:00
Jade Ellis b4e104925d fix(ci): Ignore changelog entries in the autolabeller 2026-05-19 10:57:39 +01:00
Renovate Bot 14c1d37b47 chore(deps): update rust crate dtor to v1 2026-05-18 05:05:09 +00:00
timedout 1bba4fd252 style: Reformat 2026-05-18 03:15:15 +01:00
timedout 8af0662a18 feat: Verify custom room ID has been used after creating the room 2026-05-18 01:21:13 +01:00
timedout 2804278e9b fix: Restore custom room ID functionality 2026-05-18 01:05:12 +01:00
Jade 7c36bd54f5 chore: Revert 8e9c7c1a3b
revert chore(deps): update opentelemetry-rust monorepo to 0.32.0 - multiple otel sdk versions
2026-05-17 08:35:25 +00:00
Renovate Bot 8e9c7c1a3b chore(deps): update opentelemetry-rust monorepo to 0.32.0 2026-05-16 19:55:09 +00:00
Renovate Bot 8fe8438f5d chore(deps): update pre-commit hook crate-ci/typos to v1.46.2 2026-05-16 19:17:04 +00:00
Jade a7d4f3537b chore(deps): Update renovate 2026-05-16 19:06:33 +00:00
Renovate Bot 18789f9aea chore(deps): lock file maintenance 2026-05-16 19:01:45 +00:00
Renovate Bot 2f50f1fc2a chore(deps): update https://github.com/taiki-e/install-action digest to 3771e22 2026-05-16 17:11:05 +00:00
66 changed files with 2087 additions and 1162 deletions
@@ -44,7 +44,7 @@ runs:
- name: Login to builtin registry - name: Login to builtin registry
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
with: with:
registry: ${{ env.BUILTIN_REGISTRY }} registry: ${{ env.BUILTIN_REGISTRY }}
username: ${{ inputs.registry_user }} username: ${{ inputs.registry_user }}
@@ -52,7 +52,7 @@ runs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
with: with:
# Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125) # Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125)
driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }} driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }}
@@ -61,7 +61,7 @@ runs:
- name: Extract metadata (tags) for Docker - name: Extract metadata (tags) for Docker
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
id: meta id: meta
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6 uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6
with: with:
flavor: | flavor: |
latest=auto latest=auto
@@ -67,7 +67,7 @@ runs:
uses: ./.forgejo/actions/rust-toolchain uses: ./.forgejo/actions/rust-toolchain
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
with: with:
# Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125) # Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125)
driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }} driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }}
@@ -79,7 +79,7 @@ runs:
- name: Login to builtin registry - name: Login to builtin registry
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
with: with:
registry: ${{ env.BUILTIN_REGISTRY }} registry: ${{ env.BUILTIN_REGISTRY }}
username: ${{ inputs.registry_user }} username: ${{ inputs.registry_user }}
@@ -87,7 +87,7 @@ runs:
- name: Extract metadata (labels, annotations) for Docker - name: Extract metadata (labels, annotations) for Docker
id: meta id: meta
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6 uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6
with: with:
images: ${{ inputs.images }} images: ${{ inputs.images }}
# default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509 # default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509
@@ -17,7 +17,7 @@ inputs:
llvm-version: llvm-version:
description: 'LLVM version to install' description: 'LLVM version to install'
required: false required: false
default: '20' default: '21'
outputs: outputs:
llvm-version: llvm-version:
+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@184183c2401be73c3bf42c2e61268aa5855379c1 # v2 uses: https://github.com/taiki-e/install-action@920ab1831fbf4fb3ef75c8ead83556c918bb7290 # 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
@@ -28,7 +28,7 @@ jobs:
const labelsToAdd = new Set(); const labelsToAdd = new Set();
for (const file of fileNames) { for (const file of fileNames) {
if (file.startsWith('docs/') || file.startsWith('theme/') || file.endsWith('.md') || file == 'rspress.config.ts') { if (file.startsWith('docs/') || file.startsWith('theme/') || (file.endsWith('.md') && !file.startsWith('changelog.d/')) || file == 'rspress.config.ts') {
labelsToAdd.add('Documentation'); labelsToAdd.add('Documentation');
} }
if (file.startsWith('.forgejo/')) { if (file.startsWith('.forgejo/')) {
+2 -2
View File
@@ -62,7 +62,7 @@ jobs:
registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }} registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
- name: Build and push Docker image by digest - name: Build and push Docker image by digest
id: build id: build
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
with: with:
context: . context: .
file: "docker/Dockerfile" file: "docker/Dockerfile"
@@ -149,7 +149,7 @@ jobs:
registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }} registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
- name: Build and push max-perf Docker image by digest - name: Build and push max-perf Docker image by digest
id: build id: build
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
with: with:
context: . context: .
file: "docker/Dockerfile" file: "docker/Dockerfile"
+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.140.0@sha256:61303c28b10a491c559529fb6f41745850e4755a43a54c04c3ae6848d6eaf5cc image: ghcr.io/renovatebot/renovate:43.195.3@sha256:868dffc3d6a46f42dfefe48b6978cc063d8df9c1d58a93a694c8989afa503e34
options: --tmpfs /tmp:exec options: --tmpfs /tmp:exec
steps: steps:
- name: Checkout - name: Checkout
+1 -1
View File
@@ -24,7 +24,7 @@ repos:
- id: check-added-large-files - id: check-added-large-files
- repo: https://github.com/crate-ci/typos - repo: https://github.com/crate-ci/typos
rev: v1.46.1 rev: v1.46.2
hooks: hooks:
- id: typos - id: typos
- id: typos - id: typos
Generated
+138 -140
View File
@@ -71,9 +71,9 @@ dependencies = [
[[package]] [[package]]
name = "annotate-snippets" name = "annotate-snippets"
version = "0.12.15" version = "0.12.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92570a3f9c98e7e84df84b71d0965ac99b1871fcd75a3773a3bd1bad13f64cf7" checksum = "f211a51805bc641f3ad5b7664c77d2547af685cc33b4cd8d31964027a46f13f1"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"memchr", "memchr",
@@ -193,7 +193,7 @@ dependencies = [
"serde", "serde",
"serde_derive", "serde_derive",
"unicode-ident", "unicode-ident",
"winnow 1.0.2", "winnow 1.0.3",
] ]
[[package]] [[package]]
@@ -253,15 +253,15 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.5.0" version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]] [[package]]
name = "aws-lc-rs" name = "aws-lc-rs"
version = "1.16.3" version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00"
dependencies = [ dependencies = [
"aws-lc-sys", "aws-lc-sys",
"zeroize", "zeroize",
@@ -269,9 +269,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-lc-sys" name = "aws-lc-sys"
version = "0.40.0" version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4"
dependencies = [ dependencies = [
"cc", "cc",
"cmake", "cmake",
@@ -530,15 +530,15 @@ dependencies = [
[[package]] [[package]]
name = "built" name = "built"
version = "0.8.0" version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9"
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.20.2" version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
@@ -625,9 +625,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.61" version = "1.2.62"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@@ -949,7 +949,7 @@ dependencies = [
"lock_api", "lock_api",
"log", "log",
"maplit", "maplit",
"nix 0.31.3", "nix",
"num-traits", "num-traits",
"parking_lot", "parking_lot",
"rand 0.10.1", "rand 0.10.1",
@@ -1393,18 +1393,18 @@ dependencies = [
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.2.1" version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
dependencies = [ dependencies = [
"hybrid-array", "hybrid-array",
] ]
[[package]] [[package]]
name = "ctor" name = "ctor"
version = "0.13.1" version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3429e8f8e3ce0ffe475c411850f70468c70d7a87d2ac3d15dd44703fb885aede" checksum = "6d765eb1c0bda10d31e0ea185f5ee15da532d60b0912d2bd1441783439e749c5"
dependencies = [ dependencies = [
"link-section", "link-section",
"linktime-proc-macro", "linktime-proc-macro",
@@ -1563,12 +1563,12 @@ dependencies = [
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.11.2" version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
dependencies = [ dependencies = [
"block-buffer 0.12.0", "block-buffer 0.12.0",
"crypto-common 0.2.1", "crypto-common 0.2.2",
"ctutils", "ctutils",
] ]
@@ -1604,9 +1604,9 @@ dependencies = [
[[package]] [[package]]
name = "dtor" name = "dtor"
version = "0.13.1" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66261f2a6a4976b45407e398e63bc50a25fccf464d417f7d20f2e2b0974b9888" checksum = "e2137ce22f50d4c43ce098daf41c904cc700de1ce8bc2daf53ed4e702180a464"
dependencies = [ dependencies = [
"linktime-proc-macro", "linktime-proc-macro",
] ]
@@ -1643,9 +1643,9 @@ dependencies = [
[[package]] [[package]]
name = "either" name = "either"
version = "1.15.0" version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@@ -1709,7 +1709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.52.0", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -1926,9 +1926,9 @@ checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
[[package]] [[package]]
name = "futures-timer" name = "futures-timer"
version = "3.0.3" version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
@@ -2051,9 +2051,9 @@ dependencies = [
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.4.13" version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"bytes", "bytes",
@@ -2124,9 +2124,9 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.17.0" version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]] [[package]]
name = "hdrhistogram" name = "hdrhistogram"
@@ -2237,7 +2237,7 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
dependencies = [ dependencies = [
"digest 0.11.2", "digest 0.11.3",
] ]
[[package]] [[package]]
@@ -2327,9 +2327,9 @@ checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
[[package]] [[package]]
name = "hybrid-array" name = "hybrid-array"
version = "0.4.11" version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5" checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da"
dependencies = [ dependencies = [
"typenum", "typenum",
] ]
@@ -2557,7 +2557,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.17.0", "hashbrown 0.17.1",
"serde", "serde",
"serde_core", "serde_core",
] ]
@@ -2686,9 +2686,9 @@ dependencies = [
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.97" version = "0.3.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"futures-util", "futures-util",
@@ -2828,9 +2828,9 @@ dependencies = [
[[package]] [[package]]
name = "link-section" name = "link-section"
version = "0.13.1" version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2c24837c4fd5ab6a31d64133eae954f5199247523cf29586117e85245c0dd3" checksum = "4d1e908a416d6e9f725743b84a36feea40c4c131e805fbc26d61f9f451f36080"
[[package]] [[package]]
name = "linked-hash-map" name = "linked-hash-map"
@@ -3005,15 +3005,15 @@ dependencies = [
[[package]] [[package]]
name = "minicbor" name = "minicbor"
version = "2.2.1" version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e70eae6d4f18f7d76877fe7b13f0bc21f7c2b7239d2041c338335f7b388d0dd7" checksum = "1b7a5041e12946f8b7d3f5a9d96383a19d694b9335457c522be7815b9abafb02"
[[package]] [[package]]
name = "minicbor-serde" name = "minicbor-serde"
version = "0.6.2" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80047f75e28e3b38f6ab2ec3c2c7669f6b411fa6f8424e1a90a3fd784b19a3f4" checksum = "293c7245401f035e2dcc4b12ebdb5c9d8847247fc79fe1b5b0a0d58d7275324c"
dependencies = [ dependencies = [
"minicbor", "minicbor",
"serde", "serde",
@@ -3089,18 +3089,6 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]]
name = "nix"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.31.3" version = "0.31.3"
@@ -3150,7 +3138,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [ dependencies = [
"windows-sys 0.60.2", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -3188,9 +3176,9 @@ dependencies = [
[[package]] [[package]]
name = "num-conv" name = "num-conv"
version = "0.2.1" version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
[[package]] [[package]]
name = "num-integer" name = "num-integer"
@@ -3428,9 +3416,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]] [[package]]
name = "opentelemetry" name = "opentelemetry"
version = "0.31.0" version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" checksum = "b0142c63252a9e054e68a4c61a5778f7b14f576274d593f8ce883d191a099682"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
@@ -3442,22 +3430,22 @@ dependencies = [
[[package]] [[package]]
name = "opentelemetry-http" name = "opentelemetry-http"
version = "0.31.0" version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" checksum = "5683015d09e2df236ef005b17f6f196f0d5f6313c4fa43a7b6a53b52776e4331"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bytes", "bytes",
"http", "http",
"opentelemetry", "opentelemetry",
"reqwest 0.12.28", "reqwest 0.13.3",
] ]
[[package]] [[package]]
name = "opentelemetry-otlp" name = "opentelemetry-otlp"
version = "0.31.1" version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f69cd6acbb9af919df949cd1ec9e5e7fdc2ef15d234b6b795aaa525cc02f71f" checksum = "9966929966d17620d7c316c643ba62631826e10021409357772d5eea84f62c35"
dependencies = [ dependencies = [
"http", "http",
"opentelemetry", "opentelemetry",
@@ -3465,18 +3453,18 @@ dependencies = [
"opentelemetry-proto", "opentelemetry-proto",
"opentelemetry_sdk", "opentelemetry_sdk",
"prost", "prost",
"reqwest 0.12.28", "reqwest 0.13.3",
"thiserror", "thiserror",
"tokio", "tokio",
"tonic", "tonic",
"tracing", "tonic-types",
] ]
[[package]] [[package]]
name = "opentelemetry-proto" name = "opentelemetry-proto"
version = "0.31.0" version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" checksum = "56d658ba1faf63f7b9c492cfbe6e0ec365440a16132d3270c1065f7b33f1b638"
dependencies = [ dependencies = [
"opentelemetry", "opentelemetry",
"opentelemetry_sdk", "opentelemetry_sdk",
@@ -3487,15 +3475,16 @@ dependencies = [
[[package]] [[package]]
name = "opentelemetry_sdk" name = "opentelemetry_sdk"
version = "0.31.0" version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" checksum = "368afaed344110f40b179bb8fbe54bc52d98f9bd2b281799ef32487c2650c956"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-executor", "futures-executor",
"futures-util", "futures-util",
"opentelemetry", "opentelemetry",
"percent-encoding", "percent-encoding",
"portable-atomic",
"rand 0.9.4", "rand 0.9.4",
"thiserror", "thiserror",
"tokio", "tokio",
@@ -3504,13 +3493,13 @@ dependencies = [
[[package]] [[package]]
name = "os_info" name = "os_info"
version = "3.14.0" version = "3.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224" checksum = "9cf20a545b305cf1da722b236b5155c9bb35f1d5ceb28c048bd96ca842f41b5b"
dependencies = [ dependencies = [
"android_system_properties", "android_system_properties",
"log", "log",
"nix 0.30.1", "nix",
"objc2", "objc2",
"objc2-foundation", "objc2-foundation",
"objc2-ui-kit", "objc2-ui-kit",
@@ -3645,18 +3634,18 @@ dependencies = [
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.1.11" version = "1.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
dependencies = [ dependencies = [
"pin-project-internal", "pin-project-internal",
] ]
[[package]] [[package]]
name = "pin-project-internal" name = "pin-project-internal"
version = "1.1.11" version = "1.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -3831,9 +3820,9 @@ dependencies = [
[[package]] [[package]]
name = "pulldown-cmark" name = "pulldown-cmark"
version = "0.13.3" version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad" checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"memchr", "memchr",
@@ -4064,9 +4053,7 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"bytes", "bytes",
"futures-channel",
"futures-core", "futures-core",
"futures-util",
"http", "http",
"http-body", "http-body",
"http-body-util", "http-body-util",
@@ -4164,7 +4151,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma" name = "ruma"
version = "0.15.1" version = "0.15.1"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc" source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [ dependencies = [
"assign", "assign",
"js_int", "js_int",
@@ -4183,7 +4170,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-appservice-api" name = "ruma-appservice-api"
version = "0.15.0" version = "0.15.0"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc" source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@@ -4195,7 +4182,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-client-api" name = "ruma-client-api"
version = "0.23.1" version = "0.23.1"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc" source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"assign", "assign",
@@ -4217,7 +4204,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-common" name = "ruma-common"
version = "0.18.0" version = "0.18.0"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc" source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"base64 0.22.1", "base64 0.22.1",
@@ -4250,7 +4237,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-events" name = "ruma-events"
version = "0.33.0" version = "0.33.0"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc" source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"indexmap", "indexmap",
@@ -4271,7 +4258,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-federation-api" name = "ruma-federation-api"
version = "0.14.0" version = "0.14.0"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc" source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [ dependencies = [
"bytes", "bytes",
"headers", "headers",
@@ -4294,7 +4281,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-identifiers-validation" name = "ruma-identifiers-validation"
version = "0.12.1" version = "0.12.1"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc" source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [ dependencies = [
"js_int", "js_int",
"thiserror", "thiserror",
@@ -4303,7 +4290,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-macros" name = "ruma-macros"
version = "0.18.0" version = "0.18.0"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc" source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"cfg-if", "cfg-if",
@@ -4319,7 +4306,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-push-gateway-api" name = "ruma-push-gateway-api"
version = "0.14.0" version = "0.14.0"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc" source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@@ -4331,7 +4318,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-signatures" name = "ruma-signatures"
version = "0.20.0" version = "0.20.0"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc" source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"ed25519-dalek", "ed25519-dalek",
@@ -4347,7 +4334,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-state-res" name = "ruma-state-res"
version = "0.16.0" version = "0.16.0"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc" source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@@ -4372,8 +4359,8 @@ dependencies = [
[[package]] [[package]]
name = "rust-librocksdb-sys" name = "rust-librocksdb-sys"
version = "0.42.0+10.10.1" version = "0.45.1+11.1.1"
source = "git+https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1?rev=31fb8f772c7afcdc0061ab6a40cfa3a1be2fccd9#31fb8f772c7afcdc0061ab6a40cfa3a1be2fccd9" source = "git+https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1?rev=0a25ff92f7c09b55eec496b9c192c7d5136ab2b8#0a25ff92f7c09b55eec496b9c192c7d5136ab2b8"
dependencies = [ dependencies = [
"bindgen", "bindgen",
"bzip2-sys", "bzip2-sys",
@@ -4389,8 +4376,8 @@ dependencies = [
[[package]] [[package]]
name = "rust-rocksdb" name = "rust-rocksdb"
version = "0.46.0" version = "0.49.1"
source = "git+https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1?rev=31fb8f772c7afcdc0061ab6a40cfa3a1be2fccd9#31fb8f772c7afcdc0061ab6a40cfa3a1be2fccd9" source = "git+https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1?rev=0a25ff92f7c09b55eec496b9c192c7d5136ab2b8#0a25ff92f7c09b55eec496b9c192c7d5136ab2b8"
dependencies = [ dependencies = [
"libc", "libc",
"parking_lot", "parking_lot",
@@ -4428,7 +4415,7 @@ dependencies = [
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
"windows-sys 0.52.0", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -4487,7 +4474,7 @@ dependencies = [
"security-framework", "security-framework",
"security-framework-sys", "security-framework-sys",
"webpki-root-certs", "webpki-root-certs",
"windows-sys 0.52.0", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -4760,7 +4747,7 @@ checksum = "dcc7fe48e34d02a97bc8e6253b8b91e5a47fe2c47eaacb5149cefbb69922eaf0"
dependencies = [ dependencies = [
"ahash", "ahash",
"annotate-snippets", "annotate-snippets",
"base64 0.21.7", "base64 0.22.1",
"encoding_rs_io", "encoding_rs_io",
"getrandom 0.3.4", "getrandom 0.3.4",
"granit-parser", "granit-parser",
@@ -4806,9 +4793,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.149" version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@@ -4887,7 +4874,7 @@ checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures 0.3.0", "cpufeatures 0.3.0",
"digest 0.11.2", "digest 0.11.3",
] ]
[[package]] [[package]]
@@ -4909,7 +4896,7 @@ checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures 0.3.0", "cpufeatures 0.3.0",
"digest 0.11.2", "digest 0.11.3",
] ]
[[package]] [[package]]
@@ -5004,9 +4991,9 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]] [[package]]
name = "siphasher" name = "siphasher"
version = "1.0.2" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
[[package]] [[package]]
name = "slab" name = "slab"
@@ -5040,7 +5027,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.60.2", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -5300,9 +5287,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.52.1" version = "1.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
dependencies = [ dependencies = [
"bytes", "bytes",
"libc", "libc",
@@ -5409,7 +5396,7 @@ dependencies = [
"serde_spanned 1.1.1", "serde_spanned 1.1.1",
"toml_datetime 1.1.1+spec-1.1.0", "toml_datetime 1.1.1+spec-1.1.0",
"toml_parser", "toml_parser",
"winnow 1.0.2", "winnow 1.0.3",
] ]
[[package]] [[package]]
@@ -5462,7 +5449,7 @@ dependencies = [
"indexmap", "indexmap",
"toml_datetime 1.1.1+spec-1.1.0", "toml_datetime 1.1.1+spec-1.1.0",
"toml_parser", "toml_parser",
"winnow 1.0.2", "winnow 1.0.3",
] ]
[[package]] [[package]]
@@ -5471,7 +5458,7 @@ version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
dependencies = [ dependencies = [
"winnow 1.0.2", "winnow 1.0.3",
] ]
[[package]] [[package]]
@@ -5488,9 +5475,9 @@ checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
[[package]] [[package]]
name = "tonic" name = "tonic"
version = "0.14.5" version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum", "axum",
@@ -5517,15 +5504,26 @@ dependencies = [
[[package]] [[package]]
name = "tonic-prost" name = "tonic-prost"
version = "0.14.5" version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0"
dependencies = [ dependencies = [
"bytes", "bytes",
"prost", "prost",
"tonic", "tonic",
] ]
[[package]]
name = "tonic-types"
version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ab1b02061f83d519bba3caa167f88f261ef05720ab8ebc954ade70de3348e8"
dependencies = [
"prost",
"prost-types",
"tonic",
]
[[package]] [[package]]
name = "tower" name = "tower"
version = "0.5.3" version = "0.5.3"
@@ -5547,9 +5545,9 @@ dependencies = [
[[package]] [[package]]
name = "tower-http" name = "tower-http"
version = "0.6.10" version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
dependencies = [ dependencies = [
"async-compression", "async-compression",
"bitflags", "bitflags",
@@ -5661,9 +5659,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing-opentelemetry" name = "tracing-opentelemetry"
version = "0.32.1" version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc" checksum = "adbc64cba7137545b8044cb1fe9814f7aacf3c6b5f9b45be8bb5db538befdb26"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"opentelemetry", "opentelemetry",
@@ -5907,9 +5905,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.120" version = "0.2.122"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@@ -5920,9 +5918,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-futures" name = "wasm-bindgen-futures"
version = "0.4.70" version = "0.4.72"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@@ -5930,9 +5928,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.120" version = "0.2.122"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@@ -5940,9 +5938,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.120" version = "0.2.122"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
@@ -5953,9 +5951,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.120" version = "0.2.122"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -6009,9 +6007,9 @@ dependencies = [
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.97" version = "0.3.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@@ -6088,7 +6086,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -6299,9 +6297,9 @@ dependencies = [
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "1.0.2" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@@ -6479,9 +6477,9 @@ dependencies = [
[[package]] [[package]]
name = "zerofrom" name = "zerofrom"
version = "0.1.7" version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
dependencies = [ dependencies = [
"zerofrom-derive", "zerofrom-derive",
] ]
+10 -9
View File
@@ -39,10 +39,10 @@ features = ["ffi", "std", "union"]
version = "1.1.0" version = "1.1.0"
[workspace.dependencies.ctor] [workspace.dependencies.ctor]
version = "0.13.0" version = "1.0.6"
[workspace.dependencies.dtor] [workspace.dependencies.dtor]
version = "0.13.0" version = "1.0.0"
[workspace.dependencies.cargo_toml] [workspace.dependencies.cargo_toml]
version = "0.22" version = "0.22"
@@ -344,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 = "9c9dccc93f054bbd28f23f630223fffa6289ecbc" rev = "3ecd80b92794d2d93f657a7b3db62d4be237526b"
features = [ features = [
"appservice-api-c", "appservice-api-c",
"client-api", "client-api",
@@ -379,12 +379,13 @@ features = [
"unstable-msc4293", "unstable-msc4293",
"unstable-msc4406", "unstable-msc4406",
"unstable-msc4439", "unstable-msc4439",
"unstable-msc4466",
"unstable-extensible-events", "unstable-extensible-events",
] ]
[workspace.dependencies.rust-rocksdb] [workspace.dependencies.rust-rocksdb]
git = "https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1" git = "https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1"
rev = "31fb8f772c7afcdc0061ab6a40cfa3a1be2fccd9" rev = "0a25ff92f7c09b55eec496b9c192c7d5136ab2b8"
default-features = false default-features = false
features = [ features = [
"multi-threaded-cf", "multi-threaded-cf",
@@ -404,20 +405,20 @@ default-features = false
# optional opentelemetry, performance measurements, flamegraphs, etc for performance measurements and monitoring # optional opentelemetry, performance measurements, flamegraphs, etc for performance measurements and monitoring
[workspace.dependencies.opentelemetry] [workspace.dependencies.opentelemetry]
version = "0.31.0" version = "0.32.0"
[workspace.dependencies.tracing-flame] [workspace.dependencies.tracing-flame]
version = "0.2.0" version = "0.2.0"
[workspace.dependencies.tracing-opentelemetry] [workspace.dependencies.tracing-opentelemetry]
version = "0.32.0" version = "0.33.0"
[workspace.dependencies.opentelemetry_sdk] [workspace.dependencies.opentelemetry_sdk]
version = "0.31.0" version = "0.32.0"
features = ["rt-tokio"] features = ["rt-tokio"]
[workspace.dependencies.opentelemetry-otlp] [workspace.dependencies.opentelemetry-otlp]
version = "0.31.0" version = "0.32.0"
features = ["http", "grpc-tonic", "trace", "logs", "metrics"] features = ["http", "grpc-tonic", "trace", "logs", "metrics"]
@@ -534,7 +535,7 @@ version = "2.1.1"
features = ["std"] features = ["std"]
[workspace.dependencies.minicbor-serde] [workspace.dependencies.minicbor-serde]
version = "0.6.0" version = "0.7.0"
features = ["std"] features = ["std"]
[workspace.dependencies.maplit] [workspace.dependencies.maplit]
+1
View File
@@ -0,0 +1 @@
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
@@ -0,0 +1 @@
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
@@ -0,0 +1 @@
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
@@ -0,0 +1 @@
Added config option for default room ACLs. Contributed by @eve.
+9
View File
@@ -0,0 +1,9 @@
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
@@ -0,0 +1 @@
Fixed several bugs in the `POST /_matrix/client/v3/rooms/{roomId}/upgrade` endpoint. Contributed by @nex.
+1
View File
@@ -0,0 +1 @@
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.
+40 -24
View File
@@ -372,21 +372,18 @@
# #
#federation_timeout = 60 #federation_timeout = 60
# MSC4284 Policy server request timeout (seconds). Generally policy # Policy server request timeout (seconds). Generally policy
# servers should respond near instantly, however may slow down under # servers should respond near instantly, however may slow down under
# load. If a policy server doesn't respond in a short amount of time, the # load. If a policy server doesn't respond in a short amount of time, the
# room it is configured in may become unusable if this limit is set too # room it is configured in may become unusable if this limit is set too
# high. 10 seconds is a good default, however dropping this to 3-5 seconds # high. 30 seconds is a good default, however lower values may be
# can be acceptable. # acceptable if temporary send failures are an okay trade-off.
# #
# Please be aware that policy requests are *NOT* currently re-tried, so if
# a spam check request fails, the event will be assumed to be not spam,
# which in some cases may result in spam being sent to or received from
# the room that would typically be prevented.
# #
# About policy servers: https://matrix.org/blog/2025/04/introducing-policy-servers/ # About policy servers: https://matrix.org/blog/2025/04/introducing-policy-servers/
# (Stabilized in Matrix v1.18)
# #
#policy_server_request_timeout = 10 #policy_server_request_timeout = 30
# Federation client idle connection pool timeout (seconds). # Federation client idle connection pool timeout (seconds).
# #
@@ -624,6 +621,30 @@
# #
#default_room_version = "12" #default_room_version = "12"
# A default allow value for the Access Control List when creating a room.
#
# If a list is provided, new rooms will be created with
# a m.room.server_acl event. Only servers which match one of the patterns
# in the list will be permitted to participate in the room.
#
# ACLs in existing rooms will not be updated automatically. This is not
# a substitute for moderation bots.
#
#default_room_acl_allow =
# A default deny value for the Access Control List when creating a room.
#
# If a list is provided, new rooms will be created with
# a m.room.server_acl event. Servers which match one of the patterns
# in the list will be NOT permitted to participate in the room.
#
# This config cannot be used if the default_room_acl_allow config is used.
#
# ACLs in existing rooms will not be updated automatically. This is not
# a substitute for moderation bots.
#
#default_room_acl_deny =
# Enable OpenTelemetry OTLP tracing export. This replaces the deprecated # Enable OpenTelemetry OTLP tracing export. This replaces the deprecated
# Jaeger exporter. Traces will be sent via OTLP to a collector (such as # Jaeger exporter. Traces will be sent via OTLP to a collector (such as
# Jaeger) that supports the OpenTelemetry Protocol. # Jaeger) that supports the OpenTelemetry Protocol.
@@ -1570,19 +1591,6 @@
# #
#block_non_admin_invites = false #block_non_admin_invites = false
# Enable or disable making requests to MSC4284 Policy Servers.
# It is recommended you keep this enabled unless you experience frequent
# connectivity issues, such as in a restricted networking environment.
#
#enable_msc4284_policy_servers = true
# Enable running locally generated events through configured MSC4284
# policy servers. You may wish to disable this if your server is
# single-user for a slight speed benefit in some rooms, but otherwise
# should leave it enabled.
#
#policy_server_check_own_events = true
# Allow admins to enter commands in rooms other than "#admins" (admin # Allow admins to enter commands in rooms other than "#admins" (admin
# room) by prefixing your message with "\!admin" or "\\!admin" followed up # room) by prefixing your message with "\!admin" or "\\!admin" followed up
# a normal continuwuity admin command. The reply will be publicly visible # a normal continuwuity admin command. The reply will be publicly visible
@@ -1849,6 +1857,11 @@
# #
#support_page = #support_page =
# The ed25519 public key for the policy server available at this server's
# name. Must be unpadded base64.
#
#policy_server_public_key =
# Role string for server support contacts, to be served as part of the # Role string for server support contacts, to be served as part of the
# MSC1929 server support endpoint at /.well-known/matrix/support. # MSC1929 server support endpoint at /.well-known/matrix/support.
# #
@@ -1959,8 +1972,10 @@
# #
#sender = #sender =
# Whether to require that users provide an email address when they # Whether to allow public registration with an email address.
# register. #
# Note that, if this option is enabled, anyone will be able to register an
# account with just an email address.
# #
# If either this option or `require_email_for_token_registration` are set, # If either this option or `require_email_for_token_registration` are set,
# users will not be allowed to remove their email address. # users will not be allowed to remove their email address.
@@ -1968,6 +1983,7 @@
#require_email_for_registration = false #require_email_for_registration = false
# Whether to require that users who register with a registration token # Whether to require that users who register with a registration token
# provide an email address. # provide an email address. This option is independent of
# `require_email_for_registration`.
# #
#require_email_for_token_registration = false #require_email_for_token_registration = false
+3 -3
View File
@@ -1,5 +1,5 @@
ARG RUST_VERSION=1 ARG RUST_VERSION=1
ARG DEBIAN_VERSION=bookworm ARG DEBIAN_VERSION=trixie
FROM --platform=$BUILDPLATFORM docker.io/tonistiigi/xx AS xx FROM --platform=$BUILDPLATFORM docker.io/tonistiigi/xx AS xx
FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-slim-${DEBIAN_VERSION} AS base FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-slim-${DEBIAN_VERSION} AS base
@@ -10,7 +10,7 @@ RUN rm -f /etc/apt/apt.conf.d/docker-clean
# Match Rustc version as close as possible # Match Rustc version as close as possible
# rustc -vV # rustc -vV
ARG LLVM_VERSION=21 ARG LLVM_VERSION=22
# ENV RUSTUP_TOOLCHAIN=${RUST_VERSION} # ENV RUSTUP_TOOLCHAIN=${RUST_VERSION}
# Install repo tools # Install repo tools
@@ -22,7 +22,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get install -y \ apt-get update && apt-get install -y \
pkg-config make jq \ pkg-config make jq \
wget curl git software-properties-common \ wget curl git lsb-release gpg \
file file
# golang cmake # golang cmake
Generated
+21 -21
View File
@@ -3,11 +3,11 @@
"advisory-db": { "advisory-db": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1777645914, "lastModified": 1779575509,
"narHash": "sha256-P1T7QVQS13OvkXEuEhI91CLaQfyv6iqV9vW8IBLLDYg=", "narHash": "sha256-wXKYURZz76ZC5lbuDA1oVQA/MxSB3pSJ1raF1HG0oIc=",
"owner": "rustsec", "owner": "rustsec",
"repo": "advisory-db", "repo": "advisory-db",
"rev": "d6ba1f7070ba91f45efe372d68eb648be67d0417", "rev": "831c50f4a4304068f125e603add6a8839f08b3eb",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -18,11 +18,11 @@
}, },
"crane": { "crane": {
"locked": { "locked": {
"lastModified": 1777335812, "lastModified": 1779130139,
"narHash": "sha256-bEg5xoAxAwsyfnGhkEX7RJViTIBIYPd8ISg4O1c0HFc=", "narHash": "sha256-BLrtr42azquO7MdGFU5a7KiMl3YpFlTeIXqy1fT5GlQ=",
"owner": "ipetkov", "owner": "ipetkov",
"repo": "crane", "repo": "crane",
"rev": "5e0fb2f64edff2822249f21293b8304dedaaf676", "rev": "edb38893982a3338972bb4a2ec7ce7c29ba10fd9",
"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": 1777624102, "lastModified": 1779612045,
"narHash": "sha256-thSyElkje577x/kAbP72nHlfiFc1a+tCudskLPHXe9s=", "narHash": "sha256-+7lfNVnmXJDkiRYHd5NoNwYoyUcc0LcXPaIJqjO7VWM=",
"owner": "nix-community", "owner": "nix-community",
"repo": "fenix", "repo": "fenix",
"rev": "4d81601e0b73f20d81d066754ad0e7d1e7f75a06", "rev": "d7be747f0a65af378de515fc3cee131bf99a008f",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -74,11 +74,11 @@
"nixpkgs-lib": "nixpkgs-lib" "nixpkgs-lib": "nixpkgs-lib"
}, },
"locked": { "locked": {
"lastModified": 1775087534, "lastModified": 1778716662,
"narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=", "narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b", "rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -89,11 +89,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1777268161, "lastModified": 1779508470,
"narHash": "sha256-bxrdOn8SCOv8tN4JbTF/TXq7kjo9ag4M+C8yzzIRYbE=", "narHash": "sha256-Ap9KJX+5xHIn3bPIpfNgT6MEXdAECECwo4/rmlQD74M=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "1c3fe55ad329cbcb28471bb30f05c9827f724c76", "rev": "29916453413845e54a65b8a1cf996842300cd299",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -105,11 +105,11 @@
}, },
"nixpkgs-lib": { "nixpkgs-lib": {
"locked": { "locked": {
"lastModified": 1774748309, "lastModified": 1777168982,
"narHash": "sha256-+U7gF3qxzwD5TZuANzZPeJTZRHS29OFQgkQ2kiTJBIQ=", "narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=",
"owner": "nix-community", "owner": "nix-community",
"repo": "nixpkgs.lib", "repo": "nixpkgs.lib",
"rev": "333c4e0545a6da976206c74db8773a1645b5870a", "rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -132,11 +132,11 @@
"rust-analyzer-src": { "rust-analyzer-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1777583169, "lastModified": 1779569060,
"narHash": "sha256-dVJ4+wrRKc8oIgp3rLOFSq1obt/sCKlXy3h47qof/w0=", "narHash": "sha256-NSnk5D+3KEfRdbgPijs33N2RAKSG6A74SwfnynLcouo=",
"owner": "rust-lang", "owner": "rust-lang",
"repo": "rust-analyzer", "repo": "rust-analyzer",
"rev": "aa64e4828a2bbba44463c1229a81c748d3cce583", "rev": "987ea33645ab1c709b1df6823038abcb2fe8973e",
"type": "github" "type": "github"
}, },
"original": { "original": {
+7 -4
View File
@@ -5,11 +5,11 @@
liburing, liburing,
craneLib, craneLib,
pkg-config, pkg-config,
callPackage,
rustPlatform, rustPlatform,
cargoExtraArgs ? "", cargoExtraArgs ? "",
rustflags ? "", rustflags ? "",
rocksdb ? callPackage ./rocksdb.nix { }, target_cpu ? null,
rocksdb,
profile ? "release", profile ? "release",
}: }:
let let
@@ -39,7 +39,10 @@ let
ROCKSDB_LIB_DIR = "${rocksdb}/lib"; ROCKSDB_LIB_DIR = "${rocksdb}/lib";
CARGO_PROFILE = profile; CARGO_PROFILE = profile;
RUSTFLAGS = rustflags; RUSTFLAGS = rustflags;
}; }
// (lib.optionalAttrs (target_cpu != null) {
TARGET_CPU = target_cpu;
});
}; };
in in
craneLib.buildPackage ( craneLib.buildPackage (
@@ -56,7 +59,7 @@ craneLib.buildPackage (
] ]
}" }"
patchelf --set-rpath "$old_rpath:$extra_rpath" $out/bin/conduwuit patchelf --set-rpath "$old_rpath:$extra_rpath" $out/bin/conduwuit
''; '';
meta = { meta = {
+8 -5
View File
@@ -15,6 +15,7 @@
rocksdb = pkgs.callPackage ./rocksdb.nix { }; rocksdb = pkgs.callPackage ./rocksdb.nix { };
default = pkgs.callPackage ./continuwuity.nix { default = pkgs.callPackage ./continuwuity.nix {
inherit self craneLib; inherit self craneLib;
inherit (self'.packages) rocksdb;
# extra features via `cargoExtraArgs` # extra features via `cargoExtraArgs`
cargoExtraArgs = "-F http3"; cargoExtraArgs = "-F http3";
# extra RUSTFLAGS via `rustflags` # extra RUSTFLAGS via `rustflags`
@@ -22,11 +23,13 @@
rustflags = "--cfg reqwest_unstable"; rustflags = "--cfg reqwest_unstable";
}; };
# users may also override this with other cargo profiles to build for other feature sets # users may also override this with other cargo profiles to build for other feature sets
# # for features configuration see `default` package which enables http3 by default
# other examples include:
# # example: different compilation profile and different target_cpu
# - release-high-perf max-perf-haswell = self'.packages.default.override {
max-perf = self'.packages.default.override { # compiles explicitly for haswell arch cpus
target_cpu = "haswell";
# compiles slower but with more thorough optimizations
profile = "release-max-perf"; profile = "release-max-perf";
}; };
}; };
+7 -5
View File
@@ -1,5 +1,7 @@
{ {
stdenv, # stdenv,
# enableJemalloc ? stdenv.hostPlatform.isLinux,
enableJemalloc ? false,
rocksdb, rocksdb,
fetchFromGitea, fetchFromGitea,
rust-jemalloc-sys-unprefixed, rust-jemalloc-sys-unprefixed,
@@ -13,16 +15,16 @@
# #
# [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17 # [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17
jemalloc = rust-jemalloc-sys-unprefixed; jemalloc = rust-jemalloc-sys-unprefixed;
enableJemalloc = stdenv.hostPlatform.isLinux; inherit enableJemalloc;
}).overrideAttrs }).overrideAttrs
({ ({
version = "continuwuity-v0.5.0-unstable-2026-03-27"; version = "continuwuity-v0.5.0-unstable-2026-05-19";
src = fetchFromGitea { src = fetchFromGitea {
domain = "forgejo.ellis.link"; domain = "forgejo.ellis.link";
owner = "continuwuation"; owner = "continuwuation";
repo = "rocksdb"; repo = "rocksdb";
rev = "463f47afceebfe088f6922420265546bd237f249"; rev = "3756b2b905e13216d8b56bcc783d814e7b073aff";
hash = "sha256-1ef75IDMs5Hba4VWEyXPJb02JyShy5k4gJfzGDhopRk="; hash = "sha256-rSv4fr2bf9JJwdodgeuPCuceeh7k97KVxrAOC0wyPQY=";
}; };
# We have this already at https://forgejo.ellis.link/continuwuation/rocksdb/commit/a935c0273e1ba44eacf88ce3685a9b9831486155 # We have this already at https://forgejo.ellis.link/continuwuation/rocksdb/commit/a935c0273e1ba44eacf88ce3685a9b9831486155
+1 -1
View File
@@ -16,7 +16,7 @@
file = inputs.self + "/rust-toolchain.toml"; file = inputs.self + "/rust-toolchain.toml";
# See also `rust-toolchain.toml` # See also `rust-toolchain.toml`
sha256 = "sha256-sqSWJDUxc+zaz1nBWMAJKTAGBuGWP25GCftIOlCEAtA="; sha256 = "sha256-gh/xTkxKHL4eiRXzWv8KP7vfjSk61Iq48x47BEDFgfk=";
}; };
in in
{ {
+133 -133
View File
@@ -125,13 +125,13 @@
} }
}, },
"node_modules/@rsbuild/core": { "node_modules/@rsbuild/core": {
"version": "2.0.5", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.0.5.tgz", "resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.0.7.tgz",
"integrity": "sha512-KajO50hbXb32S8MsyDh2f+xKcVeRy9Gfzdcy0JjpMLj22djHugly6jrGo7jH7ls9X6/TDcyCTncSuNK4+D2lTw==", "integrity": "sha512-LsBONEzsjzOAqO72ot39eI7g53zSfF9QuDXTu4ks8IUX+EZsxRSniQfc+1zVA6a6y3b9KUUtG96avoMLKbWklQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@rspack/core": "~2.0.2", "@rspack/core": "~2.0.4",
"@swc/helpers": "^0.5.21" "@swc/helpers": "^0.5.21"
}, },
"bin": { "bin": {
@@ -169,28 +169,28 @@
} }
}, },
"node_modules/@rspack/binding": { "node_modules/@rspack/binding": {
"version": "2.0.2", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.0.2.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.0.4.tgz",
"integrity": "sha512-0kZPplW9GWx8mfC6DfsaRY3QBIYPuUs42JfmSM6aSb8tMHZAXQeLeMB8M+h8i4SeI+aFtCgO6UuYGtyWf7+L+A==", "integrity": "sha512-/QeJDPUw/lWkBJESG264KA9u6/rAjvoJhKncU4rkTi5Ap45kue5HTgOzr0ufxKdd2Xl72fjFBuqlKmtFDD5LiQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optionalDependencies": { "optionalDependencies": {
"@rspack/binding-darwin-arm64": "2.0.2", "@rspack/binding-darwin-arm64": "2.0.4",
"@rspack/binding-darwin-x64": "2.0.2", "@rspack/binding-darwin-x64": "2.0.4",
"@rspack/binding-linux-arm64-gnu": "2.0.2", "@rspack/binding-linux-arm64-gnu": "2.0.4",
"@rspack/binding-linux-arm64-musl": "2.0.2", "@rspack/binding-linux-arm64-musl": "2.0.4",
"@rspack/binding-linux-x64-gnu": "2.0.2", "@rspack/binding-linux-x64-gnu": "2.0.4",
"@rspack/binding-linux-x64-musl": "2.0.2", "@rspack/binding-linux-x64-musl": "2.0.4",
"@rspack/binding-wasm32-wasi": "2.0.2", "@rspack/binding-wasm32-wasi": "2.0.4",
"@rspack/binding-win32-arm64-msvc": "2.0.2", "@rspack/binding-win32-arm64-msvc": "2.0.4",
"@rspack/binding-win32-ia32-msvc": "2.0.2", "@rspack/binding-win32-ia32-msvc": "2.0.4",
"@rspack/binding-win32-x64-msvc": "2.0.2" "@rspack/binding-win32-x64-msvc": "2.0.4"
} }
}, },
"node_modules/@rspack/binding-darwin-arm64": { "node_modules/@rspack/binding-darwin-arm64": {
"version": "2.0.2", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.0.2.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.0.4.tgz",
"integrity": "sha512-0o7lbgBBsDlICWdjIH0q3e0BsSco4GRiImHWVfZSVEG+q2+ykZJvSvYCVhPM1Co375Z0S3VMPa/8SjcY1FHwlw==", "integrity": "sha512-0Q1QXFEsZfDc4opiDnb8q50KlBbC2VovViDaYlMJZBzvjAo325mh3itXPfz7YZ31M+TxRE7TUiJXH3ltiV1Hdg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -202,9 +202,9 @@
] ]
}, },
"node_modules/@rspack/binding-darwin-x64": { "node_modules/@rspack/binding-darwin-x64": {
"version": "2.0.2", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.0.2.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.0.4.tgz",
"integrity": "sha512-tOwxZpoPlTlRs/w6UyUinXJ4TYRVHMlR7+eQxO1R3muKpixvhXQjtvoaY16HuFyTVky5F0IfOoWr3x9FEsgdLg==", "integrity": "sha512-oO5J2QYf7+H+aYRj85EiGrDOoDEE9EDDl7NgXv46iWvIF0wXowEHXqnjMFxHxRq2Vf6scT+0yYQX9blWcvMWAA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -216,9 +216,9 @@
] ]
}, },
"node_modules/@rspack/binding-linux-arm64-gnu": { "node_modules/@rspack/binding-linux-arm64-gnu": {
"version": "2.0.2", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.0.2.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.0.4.tgz",
"integrity": "sha512-1ZD4YFhG1rmgqj+W8hfwHyKV8xDxGsc/3KgU0FwmiVEX7JfzhCkgBO/xlCG79kRKSrzuVzt4icO/G3cCKn0pag==", "integrity": "sha512-BEk6mIYBK4BihW9qXXITJORrVXecTlkRjrqhgefili4xjXtLdcUnxAm9sN/2oJ8m378n2h33qDh4gr2orPBFWQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -233,9 +233,9 @@
] ]
}, },
"node_modules/@rspack/binding-linux-arm64-musl": { "node_modules/@rspack/binding-linux-arm64-musl": {
"version": "2.0.2", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.0.2.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.0.4.tgz",
"integrity": "sha512-/PtTkM/DsDLjeuXTmeJeRfbjCDbcL9jvoVgZrgxYFZ28y2cdLvbChbW9uigOzs5dQEs1CIBQXMTTj7KhdBTuQg==", "integrity": "sha512-Hyt3z1RwNcSMIoaoWLN4Hb/696/O5JPukf8rXQASvf2UkC+X3ij7tr+8lMSYi3Zysi1QL375CnT4fNoABEW0JA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -250,9 +250,9 @@
] ]
}, },
"node_modules/@rspack/binding-linux-x64-gnu": { "node_modules/@rspack/binding-linux-x64-gnu": {
"version": "2.0.2", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.0.2.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.0.4.tgz",
"integrity": "sha512-bBjsZxMHRaPo6X9SokApm6ucs+UhXtAJFyJJyuk2BH4XJsLeCU9Dz1vMwioeohFbJUUeTASVPm6/BL+RhSaunw==", "integrity": "sha512-xHorBPBZAg0Pn9Q0k9dWZ9euowieDxcSOzQ9JhTCmhDY6wZH5M/kCBFlCs/OQeW5/NUArW3x3MwEdO/0QJHMxg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -267,9 +267,9 @@
] ]
}, },
"node_modules/@rspack/binding-linux-x64-musl": { "node_modules/@rspack/binding-linux-x64-musl": {
"version": "2.0.2", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.0.2.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.0.4.tgz",
"integrity": "sha512-HjlpInqzabDNkhVsUJpsHPqa9QYVWBViJoyWNjzXCAW0vKMDvwaphyUvokSinX8FGTlZi/sr5UEaHJo6XtQ35g==", "integrity": "sha512-QLxEGUXofF0kVNU12Y2NT3Qi9lGs+WbnYpapVeb+2IXtrAXJfU7Rhy7lAp5GLMzYMQNrKKL9SVnTWKbODbNW9Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -284,9 +284,9 @@
] ]
}, },
"node_modules/@rspack/binding-wasm32-wasi": { "node_modules/@rspack/binding-wasm32-wasi": {
"version": "2.0.2", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.0.2.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.0.4.tgz",
"integrity": "sha512-YaRYNFLJRpkGfYjSWR7n9f+nQKtrlmrrffpAn/blc2geHcRvXoBc5SCs1idPtsLhj7H9qWWhs7ucjyHy4csWFg==", "integrity": "sha512-YhN8HkiH46ONU9tm5dyoXDImDWGpU7E4SPqGI4OyAVF0445uIChurIUmTIOYcD6cg81GGeIjozWJOcb635Dcqw==",
"cpu": [ "cpu": [
"wasm32" "wasm32"
], ],
@@ -300,9 +300,9 @@
} }
}, },
"node_modules/@rspack/binding-win32-arm64-msvc": { "node_modules/@rspack/binding-win32-arm64-msvc": {
"version": "2.0.2", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.0.2.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.0.4.tgz",
"integrity": "sha512-d/3kTEKq+asLjRFPO96t+wfWiM7DLN76VQEPDD9bc1kdsZXlVJBuvyXfsgK8bbEvKplWXYcSsokhmEnuXrLOpg==", "integrity": "sha512-MUlYIz82xQRN0aoiXXyEBrNVUwiOSSFRi7nuCgUKduaSdlbPWzCY31IdtOygZ06LVp5JIGUEtyqSrjQq4FrMRw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -314,9 +314,9 @@
] ]
}, },
"node_modules/@rspack/binding-win32-ia32-msvc": { "node_modules/@rspack/binding-win32-ia32-msvc": {
"version": "2.0.2", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.0.2.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.0.4.tgz",
"integrity": "sha512-161cWineq3RW+Jdm1FAfSpXeUtYWvhB3kAbm46vNT9h/YYz+spwsFMvveAZ1nsVSVL0IC5lDBGUte7yUAY8K2g==", "integrity": "sha512-D7UcIFMzlY2yhhyuW4Ej15gBWmTwUM5DxuObl3Kv31qRv/pmV3MsqUeG5m2dqLbUxzqPH87qnp0cArbkJQ1b+w==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -328,9 +328,9 @@
] ]
}, },
"node_modules/@rspack/binding-win32-x64-msvc": { "node_modules/@rspack/binding-win32-x64-msvc": {
"version": "2.0.2", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.0.2.tgz", "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.0.4.tgz",
"integrity": "sha512-y7Q0S1FE+OlkL5GMqLG0PwxrPw6E1r892KhGrGKE1Vdufe5YTEx6xTPxzZ+b7N2KPD7s9G1/iJmWHQxb1+Bjkg==", "integrity": "sha512-MnYKPfdrAEbtpKg/1SZ6cNtzreIRyQJK4APbxLLPXENdTH5QXQkaTjLMKEeJcJ51FRhI/+yNpOUm2oTHdCQ1Og==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -342,13 +342,13 @@
] ]
}, },
"node_modules/@rspack/core": { "node_modules/@rspack/core": {
"version": "2.0.2", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.0.2.tgz", "resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.0.4.tgz",
"integrity": "sha512-VM3UHOo26uC+4QSqY5tU1ybI7KuXY5rTof8nhFOaBY9SYau0Smvr+hMSAPmrmHwknB6dXT8yaNVxrj7I+qxE1Q==", "integrity": "sha512-OuxdQeeKWQpNvFBRDOcnoSaQvp6E4APM/6JJMM/k0p6oL1TEFQVGdNu3VGY4mRAsebnNBXapMVMhj+v66Bn2pg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@rspack/binding": "2.0.2" "@rspack/binding": "2.0.4"
}, },
"engines": { "engines": {
"node": "^20.19.0 || >=22.12.0" "node": "^20.19.0 || >=22.12.0"
@@ -383,17 +383,17 @@
} }
}, },
"node_modules/@rspress/core": { "node_modules/@rspress/core": {
"version": "2.0.11", "version": "2.0.13",
"resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.11.tgz", "resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.13.tgz",
"integrity": "sha512-4YBOFmSMFv5GWrCa80qSIW8VxqZQQS/PknVq2r7Hb7kgfB38Fzciopn3hjb3hNwI4TTRbsi/Jev2HyRWD4bYAQ==", "integrity": "sha512-lbaBA5eqh7wKdH98TUQEI+SfS3Z6YgaNCup3X+ttrYVLOrxN8PJvbedo6fFAcl+wP3XLy6D0pcnnzAgu8y3tdg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@mdx-js/mdx": "^3.1.1", "@mdx-js/mdx": "^3.1.1",
"@mdx-js/react": "^3.1.1", "@mdx-js/react": "^3.1.1",
"@rsbuild/core": "^2.0.5", "@rsbuild/core": "^2.0.7",
"@rsbuild/plugin-react": "~2.0.0", "@rsbuild/plugin-react": "~2.0.0",
"@rspress/shared": "2.0.11", "@rspress/shared": "2.0.13",
"@shikijs/rehype": "^4.0.2", "@shikijs/rehype": "^4.0.2",
"@types/unist": "^3.0.3", "@types/unist": "^3.0.3",
"@unhead/react": "^2.1.15", "@unhead/react": "^2.1.15",
@@ -411,8 +411,8 @@
"react-dom": "^19.2.6", "react-dom": "^19.2.6",
"react-lazy-with-preload": "^2.2.1", "react-lazy-with-preload": "^2.2.1",
"react-reconciler": "0.33.0", "react-reconciler": "0.33.0",
"react-render-to-markdown": "19.0.1", "react-render-to-markdown": "19.1.0",
"react-router-dom": "^7.15.0", "react-router-dom": "^7.15.1",
"rehype-external-links": "^3.0.0", "rehype-external-links": "^3.0.0",
"rehype-raw": "^7.0.0", "rehype-raw": "^7.0.0",
"remark-cjk-friendly": "^2.0.1", "remark-cjk-friendly": "^2.0.1",
@@ -436,9 +436,9 @@
} }
}, },
"node_modules/@rspress/plugin-client-redirects": { "node_modules/@rspress/plugin-client-redirects": {
"version": "2.0.11", "version": "2.0.13",
"resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.11.tgz", "resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.13.tgz",
"integrity": "sha512-DI9vod5mGccg57c19CuFpN3mGP1FEEueOUnEUz1UHXSyXg9YTj+ox7Xla4jUUzAzoPVGiWSSsfbtCTwdoxAsbg==", "integrity": "sha512-dP753ASTvH6eDtSEulcqq2lE/kTSdOWSCw0nzvXG+7atTWTHDp6z47uH3CGD8E78cBuKyEi4OH+U7V0EtCTc0Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -449,9 +449,9 @@
} }
}, },
"node_modules/@rspress/plugin-sitemap": { "node_modules/@rspress/plugin-sitemap": {
"version": "2.0.11", "version": "2.0.13",
"resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.11.tgz", "resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.13.tgz",
"integrity": "sha512-046LCHgbJXdaPipWB2SWMjZcAtIrOjXGZOD92xlTjhZ74D7Mk1Nod1MQdtOEoISWedcHdgpUVXMDbB1doKBpPQ==", "integrity": "sha512-JtkNlxNuA7BzknKIrLvLQkSk0XVi7OXzrE76ma/cLvleccNWr8LGrHtrac4IrDr+HauK4WKTM2JaHGGHUdOUKw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -462,26 +462,26 @@
} }
}, },
"node_modules/@rspress/shared": { "node_modules/@rspress/shared": {
"version": "2.0.11", "version": "2.0.13",
"resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.11.tgz", "resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.13.tgz",
"integrity": "sha512-7l5Pso4s597utJyisVEnd7n/40h053nfE8DwGQMeS8RLGtSwVgxFwNHsSrvQEGtFlLrg2aWWSITqnAVO1wfTew==", "integrity": "sha512-LmDfr7+MDNWRBbxcNoWkW68A35oRonpDJq2Jlx3L8GCzG4sAsyd6Yw0DebTWAxx7hVOXGMf37nEf1J4aOLEZfg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@rsbuild/core": "^2.0.5", "@rsbuild/core": "^2.0.7",
"@shikijs/rehype": "^4.0.2", "@shikijs/rehype": "^4.0.2",
"unified": "^11.0.5" "unified": "^11.0.5"
} }
}, },
"node_modules/@shikijs/core": { "node_modules/@shikijs/core": {
"version": "4.0.2", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.0.2.tgz", "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.1.0.tgz",
"integrity": "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==", "integrity": "sha512-jLJtSJeuFffqX6/inRE1zqU5aFv2hrszvYgq3OjbAgFRZiWv7abKMDdQzYxuSDfmUPQozZvI/kuy6VMTvnvqTQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/primitive": "4.0.2", "@shikijs/primitive": "4.1.0",
"@shikijs/types": "4.0.2", "@shikijs/types": "4.1.0",
"@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"
@@ -491,28 +491,28 @@
} }
}, },
"node_modules/@shikijs/engine-javascript": { "node_modules/@shikijs/engine-javascript": {
"version": "4.0.2", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.0.2.tgz", "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.1.0.tgz",
"integrity": "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==", "integrity": "sha512-YquhawCUgaBfhsS72e2Y/dI59gCBNPHu3fEO/tvLaXrTssxZrY5ddjtNLTwndrMgPo8b3IscE+xoICDzpTmlFQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.0.2", "@shikijs/types": "4.1.0",
"@shikijs/vscode-textmate": "^10.0.2", "@shikijs/vscode-textmate": "^10.0.2",
"oniguruma-to-es": "^4.3.4" "oniguruma-to-es": "^4.3.6"
}, },
"engines": { "engines": {
"node": ">=20" "node": ">=20"
} }
}, },
"node_modules/@shikijs/engine-oniguruma": { "node_modules/@shikijs/engine-oniguruma": {
"version": "4.0.2", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.0.2.tgz", "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.1.0.tgz",
"integrity": "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==", "integrity": "sha512-axLpjVs45YBvvINa+dJF+NPW+KtFkNXsFr4SDw2BMj9GdeMnGxVB9PQb2xXlJYovslt/nz6giedAyOANkfc7hg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.0.2", "@shikijs/types": "4.1.0",
"@shikijs/vscode-textmate": "^10.0.2" "@shikijs/vscode-textmate": "^10.0.2"
}, },
"engines": { "engines": {
@@ -520,26 +520,26 @@
} }
}, },
"node_modules/@shikijs/langs": { "node_modules/@shikijs/langs": {
"version": "4.0.2", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.0.2.tgz", "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.1.0.tgz",
"integrity": "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==", "integrity": "sha512-nwOMruEkbgdZfQ/b8CgpNBVOpvG1k0N5tbmgiFeqsan401+x3ILqlzZJowSla4Agmq4hG2Uf2wh5jLTEhR8VSg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.0.2" "@shikijs/types": "4.1.0"
}, },
"engines": { "engines": {
"node": ">=20" "node": ">=20"
} }
}, },
"node_modules/@shikijs/primitive": { "node_modules/@shikijs/primitive": {
"version": "4.0.2", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.0.2.tgz", "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.1.0.tgz",
"integrity": "sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==", "integrity": "sha512-zx2/2Uwj2q9X3KSyYREEhXO23xBw5WUhP4orK2lE4r+t9JGITmEe0JH+wPmJhqHpOT2bRRs6lAL945+LDvOAGw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.0.2", "@shikijs/types": "4.1.0",
"@shikijs/vscode-textmate": "^10.0.2", "@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4" "@types/hast": "^3.0.4"
}, },
@@ -548,16 +548,16 @@
} }
}, },
"node_modules/@shikijs/rehype": { "node_modules/@shikijs/rehype": {
"version": "4.0.2", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-4.0.2.tgz", "resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-4.1.0.tgz",
"integrity": "sha512-cmPlKLD8JeojasNFoY64162ScpEdEdQUMuVodPCrv1nx1z3bjmGwoKWDruQWa/ejSznImlaeB0Ty6Q3zPaVQAA==", "integrity": "sha512-HQwltCcO2/UiFz44/8whyji4rP1VghLu++MgvQn+lQA8/gvuycGkay8DH8o8VAOvLBDKGOkBEw7cC1Cm33GObQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.0.2", "@shikijs/types": "4.1.0",
"@types/hast": "^3.0.4", "@types/hast": "^3.0.4",
"hast-util-to-string": "^3.0.1", "hast-util-to-string": "^3.0.1",
"shiki": "4.0.2", "shiki": "4.1.0",
"unified": "^11.0.5", "unified": "^11.0.5",
"unist-util-visit": "^5.1.0" "unist-util-visit": "^5.1.0"
}, },
@@ -566,22 +566,22 @@
} }
}, },
"node_modules/@shikijs/themes": { "node_modules/@shikijs/themes": {
"version": "4.0.2", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.0.2.tgz", "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.1.0.tgz",
"integrity": "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==", "integrity": "sha512-emCcTnUM7yO2wltYbaxm+yLvcCI4+h8XBKc4KmJ7EZUXoSGjcCHifkI//R4OFit9ewpg7H2/9tjOuXrT2v/Knw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/types": "4.0.2" "@shikijs/types": "4.1.0"
}, },
"engines": { "engines": {
"node": ">=20" "node": ">=20"
} }
}, },
"node_modules/@shikijs/types": { "node_modules/@shikijs/types": {
"version": "4.0.2", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.0.2.tgz", "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.1.0.tgz",
"integrity": "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==", "integrity": "sha512-3EQWX54fMpniOrDblzAhiwiJwpiTMW6+B9DWyUd9ska483tbayFYuw47UxwuPknI31bKnySfVQ/QW+jFL4rFdA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -631,9 +631,9 @@
} }
}, },
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.8", "version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@@ -682,9 +682,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "19.2.14", "version": "19.2.15",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
@@ -700,9 +700,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@ungap/structured-clone": { "node_modules/@ungap/structured-clone": {
"version": "1.3.0", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz",
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
@@ -1150,9 +1150,9 @@
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/get-east-asian-width": { "node_modules/get-east-asian-width": {
"version": "1.5.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
"integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -2809,9 +2809,9 @@
} }
}, },
"node_modules/react-render-to-markdown": { "node_modules/react-render-to-markdown": {
"version": "19.0.1", "version": "19.1.0",
"resolved": "https://registry.npmjs.org/react-render-to-markdown/-/react-render-to-markdown-19.0.1.tgz", "resolved": "https://registry.npmjs.org/react-render-to-markdown/-/react-render-to-markdown-19.1.0.tgz",
"integrity": "sha512-BPv48o+ubcu2JyUDIktvJXFqLIZqR7hA4mvGu1eFIofz9fogT2me9UvXwRvqvGs9jEtNaJkxZIUKUX0oiK4hDA==", "integrity": "sha512-dF9b3tO41ezqdmHP8X92kbHbMexJ6iC7iHw4ykC8fwiO7DgpFc9PhMoKlI+BcPzRxGcWgQSdrixVB9RykhjJpQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -2822,9 +2822,9 @@
} }
}, },
"node_modules/react-router": { "node_modules/react-router": {
"version": "7.15.0", "version": "7.15.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.0.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.1.tgz",
"integrity": "sha512-HW9vYwuM8f4yx66Izy8xfrzCM+SBJluoZcCbww9A1TySax11S5Vgw6fi3ZjMONw9J4gQwngL7PzkyIpJJpJ7RQ==", "integrity": "sha512-R8rl9HhgikFYoPJymnUtPXWbnDb3oget6lQnfIoupbt61aT9aOhRkDsY2XRhZRyX1Z/8a5sL74fXmFNm3NRK5A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -2845,13 +2845,13 @@
} }
}, },
"node_modules/react-router-dom": { "node_modules/react-router-dom": {
"version": "7.15.0", "version": "7.15.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.15.0.tgz", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.15.1.tgz",
"integrity": "sha512-VcrVg64Fo8nwBvDscajG8gRTLIuTC6N50nb22l2HOOV4PTOHgoGp8mUjy9wLiHYoYTSYI36tUnXZgasSRFZorQ==", "integrity": "sha512-AzF62gjY6U9rkMq4RfP/r2EVtQ7DMfNMjyOp/flLTCrtRylLiK4wT4pSq6O8rOXZ2eXdZYJPEYe+ifomiv+Igg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"react-router": "7.15.0" "react-router": "7.15.1"
}, },
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=20.0.0"
@@ -3164,18 +3164,18 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/shiki": { "node_modules/shiki": {
"version": "4.0.2", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-4.0.2.tgz", "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.1.0.tgz",
"integrity": "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==", "integrity": "sha512-l/ABZPUR5v70jI10EzqfMS/I96vjSGv2y0ihUV+WYFzv0EfvW4s54m0Lg8wCrrL+2IkwBzFTuxkZjPf8b2NX9Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@shikijs/core": "4.0.2", "@shikijs/core": "4.1.0",
"@shikijs/engine-javascript": "4.0.2", "@shikijs/engine-javascript": "4.1.0",
"@shikijs/engine-oniguruma": "4.0.2", "@shikijs/engine-oniguruma": "4.1.0",
"@shikijs/langs": "4.0.2", "@shikijs/langs": "4.1.0",
"@shikijs/themes": "4.0.2", "@shikijs/themes": "4.1.0",
"@shikijs/types": "4.0.2", "@shikijs/types": "4.1.0",
"@shikijs/vscode-textmate": "^10.0.2", "@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4" "@types/hast": "^3.0.4"
}, },
+19 -3
View File
@@ -5,7 +5,7 @@
"osvVulnerabilityAlerts": true, "osvVulnerabilityAlerts": true,
"lockFileMaintenance": { "lockFileMaintenance": {
"enabled": true, "enabled": true,
"schedule": ["at any time"] "schedule": ["* * * * 0,6"]
}, },
"platformAutomerge": true, "platformAutomerge": true,
"nix": { "nix": {
@@ -66,6 +66,17 @@
"matchUpdateTypes": ["minor", "patch"], "matchUpdateTypes": ["minor", "patch"],
"groupName": "github-actions-non-major" "groupName": "github-actions-non-major"
}, },
{
"description": "Batch GitHub Actions digest updates",
"matchManagers": ["github-actions"],
"matchUpdateTypes": ["digest"],
"groupName": "github-actions-digest",
"automerge": true,
"automergeStrategy": "fast-forward",
"schedule": [
"* 0-7 * * 2"
]
},
{ {
"description": "Batch patch-level Node.js dependency updates", "description": "Batch patch-level Node.js dependency updates",
"matchManagers": ["npm"], "matchManagers": ["npm"],
@@ -83,7 +94,10 @@
"matchPackageNames": ["crate-ci/typos"], "matchPackageNames": ["crate-ci/typos"],
"matchUpdateTypes": ["minor", "patch"], "matchUpdateTypes": ["minor", "patch"],
"automerge": true, "automerge": true,
"automergeStrategy": "fast-forward" "automergeStrategy": "fast-forward",
"schedule": [
"* 0-7 * * 3"
]
}, },
{ {
"description": "Auto-merge renovatebot docker image updates", "description": "Auto-merge renovatebot docker image updates",
@@ -91,7 +105,9 @@
"matchPackageNames": ["ghcr.io/renovatebot/renovate"], "matchPackageNames": ["ghcr.io/renovatebot/renovate"],
"automerge": true, "automerge": true,
"automergeStrategy": "fast-forward", "automergeStrategy": "fast-forward",
"extends": ["schedule:earlyMondays"] "schedule": [
"* 0-7 * * 1"
]
} }
], ],
"customManagers": [ "customManagers": [
+1 -1
View File
@@ -10,7 +10,7 @@
[toolchain] [toolchain]
profile = "minimal" profile = "minimal"
channel = "1.92.0" channel = "1.95.0"
components = [ components = [
# For rust-analyzer # For rust-analyzer
"rust-src", "rust-src",
+229 -9
View File
@@ -1,5 +1,5 @@
use std::{ use std::{
collections::HashMap, collections::{HashMap, HashSet},
fmt::Write, fmt::Write,
iter::once, iter::once,
time::{Instant, SystemTime}, time::{Instant, SystemTime},
@@ -22,7 +22,7 @@ use futures::{FutureExt, StreamExt, TryStreamExt};
use lettre::message::Mailbox; use lettre::message::Mailbox;
use ruma::{ use ruma::{
CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId,
OwnedRoomOrAliasId, OwnedServerName, RoomId, RoomVersionId, OwnedRoomOrAliasId, OwnedServerName, RoomId, RoomVersionId, UInt,
api::federation::event::get_room_state, events::AnyStateEvent, serde::Raw, api::federation::event::get_room_state, events::AnyStateEvent, serde::Raw,
}; };
use service::rooms::{ use service::rooms::{
@@ -69,6 +69,205 @@ pub(super) async fn get_auth_chain(&self, event_id: OwnedEventId) -> Result {
self.write_str(&out).await self.write_str(&out).await
} }
#[derive(Clone, Copy, Eq, PartialEq)]
enum NodeStatus {
Normal(bool),
SoftFailed(bool),
Rejected(bool),
}
struct AuthChild {
node_id: String,
event_id: OwnedEventId,
depth: UInt,
ts: UInt,
first_seen: bool,
pdu: Option<PduEvent>,
}
fn render_node(
graph: &mut String,
node_id: &str,
event_id: &EventId,
name: &str,
status: NodeStatus,
) -> Result {
let evt_str = event_id.to_string();
let status_label = match status {
| NodeStatus::Normal(false) => format!("{evt_str}: {name}"),
| NodeStatus::Normal(true) => format!("{evt_str}: {name} (missing locally)"),
| NodeStatus::SoftFailed(false) => format!("{evt_str}: {name} (soft-failed)"),
| NodeStatus::SoftFailed(true) =>
format!("{evt_str}: {name} (soft-failed & missing locally)"),
| NodeStatus::Rejected(false) => format!("{evt_str}: {name} (rejected)"),
| NodeStatus::Rejected(true) => format!("{evt_str}: {name} (rejected & missing locally)"),
};
writeln!(graph, "{node_id}[\"{}\"]", status_label.as_str())?;
match status {
| NodeStatus::Rejected(_) => writeln!(graph, "class {node_id} rejected;")?,
| NodeStatus::SoftFailed(_) => writeln!(graph, "class {node_id} soft_failed;")?,
| NodeStatus::Normal(_) => {},
}
Ok(())
}
#[admin_command]
pub(super) async fn show_auth_chain(&self, event_id: OwnedEventId) -> Result {
let node_status = async |event_id: &EventId, missing: bool| -> NodeStatus {
if self
.services
.rooms
.pdu_metadata
.is_event_rejected(event_id)
.await
{
NodeStatus::Rejected(missing)
} else if self
.services
.rooms
.pdu_metadata
.is_event_soft_failed(event_id)
.await
{
NodeStatus::SoftFailed(missing)
} else {
NodeStatus::Normal(missing)
}
};
let Ok(root) = self.services.rooms.timeline.get_pdu(&event_id).await else {
return Err!("Event not found.");
};
let mut graph = String::from(
"```mermaid\n%% This is a mermaid graph. You can plug this output into\n\
%% https://mermaid.live/edit to visualise it on-the-fly.\nflowchart TD\n\
classDef rejected fill:#ffe5e5,stroke:#cc0000,stroke-width:2px,color:#000;\n\
classDef soft_failed fill:#fff6cc,stroke:#c9a400,stroke-width:2px,color:#000;\n"
);
let mut node_ids: HashMap<OwnedEventId, String> = HashMap::new();
let mut cached_events: HashMap<OwnedEventId, PduEvent> =
HashMap::from([(event_id.clone(), root.clone())]);
let mut scheduled: HashSet<OwnedEventId> = HashSet::from([event_id.clone()]);
let mut visited: HashSet<OwnedEventId> = HashSet::new();
let mut stack = vec![root];
let mut next_node_id = 0_usize;
let node_id_for = |event_id: &OwnedEventId,
node_ids: &mut HashMap<OwnedEventId, String>,
next_node_id: &mut usize| {
node_ids
.entry(event_id.clone())
.or_insert_with(|| {
let id = format!("n{}", *next_node_id);
*next_node_id = next_node_id.saturating_add(1);
id
})
.clone()
};
let node_name = |e: &PduEvent| {
if let Some(state_key) = e.state_key() {
format!("{},'{}'", e.event_type(), state_key)
} else {
format!("{}", e.event_type())
}
};
while let Some(event) = stack.pop() {
let current_event_id = event.event_id().to_owned();
if !visited.insert(current_event_id.clone()) {
continue;
}
let current_node_id = node_id_for(&current_event_id, &mut node_ids, &mut next_node_id);
let current_status = node_status(&current_event_id, false).await;
render_node(
&mut graph,
&current_node_id,
&current_event_id,
&node_name(&event),
current_status,
)?;
let mut children = Vec::with_capacity(event.auth_events.len());
for auth_event_id in event.auth_events().rev() {
let auth_event_id = auth_event_id.to_owned();
let auth_node_id = node_id_for(&auth_event_id, &mut node_ids, &mut next_node_id);
writeln!(graph, "{current_node_id} --> {auth_node_id}")?;
let first_seen = scheduled.insert(auth_event_id.clone());
let auth_pdu = if let Some(auth_pdu) = cached_events.get(&auth_event_id) {
// NOTE: events might be referenced multiple times (like the create event)
// so this saves some cheeky db lookup time
Some(auth_pdu.clone())
} else if first_seen {
match self.services.rooms.timeline.get_pdu(&auth_event_id).await {
| Ok(auth_event) => {
cached_events.insert(auth_event_id.clone(), auth_event.clone());
Some(auth_event)
},
| Err(_) => None,
}
} else {
None
};
// NOTE: Depth is used as the primary sorting key here, even though it has no
// bearing on state resolution or anything. Timestamp is used as a
// tiebreaker, failing back to lexicographical comparison.
let (depth, ts) = auth_pdu
.as_ref()
.map_or((UInt::MAX, UInt::MAX), |pdu| (pdu.depth, pdu.origin_server_ts));
children.push(AuthChild {
node_id: auth_node_id,
event_id: auth_event_id,
depth,
ts,
first_seen,
pdu: auth_pdu,
});
}
children.sort_by(|a, b| {
a.depth
.cmp(&b.depth)
.then(a.ts.cmp(&b.ts))
.then(a.event_id.as_str().cmp(b.event_id.as_str()))
});
for child in children.into_iter().rev() {
if !child.first_seen {
continue;
}
if let Some(child_pdu) = child.pdu {
// We have this PDU so will want to traverse it.
stack.push(child_pdu);
} else {
// We don't have this PDU locally so we can't traverse its auth events,
// but we can still render it as a node.
render_node(
&mut graph,
&child.node_id,
&child.event_id,
"",
node_status(&child.event_id, true).await,
)?;
}
}
}
graph.push_str("```\n");
self.write_str(&graph).await
}
#[admin_command] #[admin_command]
pub(super) async fn parse_pdu(&self) -> Result { pub(super) async fn parse_pdu(&self) -> Result {
if self.body.len() < 2 if self.body.len() < 2
@@ -111,15 +310,31 @@ pub(super) async fn get_pdu(&self, event_id: OwnedEventId) -> Result {
outlier = true; outlier = true;
pdu_json = self.services.rooms.timeline.get_pdu_json(&event_id).await; pdu_json = self.services.rooms.timeline.get_pdu_json(&event_id).await;
} }
let rejected = self
.services
.rooms
.pdu_metadata
.is_event_rejected(&event_id)
.await;
let soft_failed = self
.services
.rooms
.pdu_metadata
.is_event_soft_failed(&event_id)
.await;
match pdu_json { match pdu_json {
| Err(_) => return Err!("PDU not found locally."), | Err(_) => return Err!("PDU not found locally."),
| Ok(json) => { | Ok(json) => {
let text = serde_json::to_string_pretty(&json)?; let text = serde_json::to_string_pretty(&json)?;
let msg = if outlier { let msg = if rejected {
"Outlier (Rejected / Soft Failed) PDU found in our database" "Rejected PDU:"
} else if soft_failed {
"Soft-failed PDU:"
} else if outlier {
"Outlier PDU:"
} else { } else {
"PDU found in our database" "PDU:"
}; };
write!(self, "{msg}\n```json\n{text}\n```") write!(self, "{msg}\n```json\n{text}\n```")
}, },
@@ -614,6 +829,10 @@ pub(super) async fn force_set_room_state_from_server(
.await; .await;
state.insert(shortstatekey, pdu.event_id.clone()); state.insert(shortstatekey, pdu.event_id.clone());
self.services
.rooms
.pdu_metadata
.clear_pdu_markers(pdu.event_id());
} }
} }
@@ -631,6 +850,10 @@ pub(super) async fn force_set_room_state_from_server(
.rooms .rooms
.outlier .outlier
.add_pdu_outlier(&event_id, &value); .add_pdu_outlier(&event_id, &value);
self.services
.rooms
.pdu_metadata
.clear_pdu_markers(&event_id);
} }
info!("Resolving new room state"); info!("Resolving new room state");
@@ -662,10 +885,7 @@ pub(super) async fn force_set_room_state_from_server(
.force_state(room_id.clone().as_ref(), short_state_hash, added, removed, &state_lock) .force_state(room_id.clone().as_ref(), short_state_hash, added, removed, &state_lock)
.await?; .await?;
info!( info!("Updating joined counts for room");
"Updating joined counts for room just in case (e.g. we may have found a difference in \
the room's m.room.member state"
);
self.services self.services
.rooms .rooms
.state_cache .state_cache
+10 -1
View File
@@ -17,12 +17,21 @@ pub enum DebugCommand {
message: Vec<String>, message: Vec<String>,
}, },
/// Get the auth_chain of a PDU /// Loads the auth_chain of a PDU, reporting how long it took.
GetAuthChain { GetAuthChain {
/// An event ID (the $ character followed by the base64 reference hash) /// An event ID (the $ character followed by the base64 reference hash)
event_id: OwnedEventId, event_id: OwnedEventId,
}, },
/// Walks & displays the auth_chain of a PDU in a mermaid graph format.
///
/// This is useless to basically anyone but developers, and is also probably
/// slow and memory hungry.
ShowAuthChain {
/// The root event ID to start walking back from.
event_id: OwnedEventId,
},
/// Parse and print a PDU from a JSON /// Parse and print a PDU from a JSON
/// ///
/// The PDU event is only checked for validity and is not added to the /// The PDU event is only checked for validity and is not added to the
+6 -1
View File
@@ -740,14 +740,19 @@ pub(super) async fn force_join_room(
&self, &self,
user_id: String, user_id: String,
room_id: OwnedRoomOrAliasId, room_id: OwnedRoomOrAliasId,
via: Option<String>,
) -> Result { ) -> Result {
let user_id = parse_local_user_id(self.services, &user_id)?; let user_id = parse_local_user_id(self.services, &user_id)?;
let (room_id, servers) = self let (room_id, mut servers) = self
.services .services
.rooms .rooms
.alias .alias
.resolve_with_servers(&room_id, None) .resolve_with_servers(&room_id, None)
.await?; .await?;
if let Some(via) = via.map(ServerName::parse).transpose()? {
servers.retain(|n| *n != via);
servers.insert(0, via);
}
assert!( assert!(
self.services.globals.user_is_local(&user_id), self.services.globals.user_is_local(&user_id),
+7
View File
@@ -179,8 +179,15 @@ pub enum UserCommand {
/// Manually join a local user to a room. /// Manually join a local user to a room.
ForceJoinRoom { ForceJoinRoom {
/// The user to join
user_id: String, user_id: String,
/// The room to join
room_id: OwnedRoomOrAliasId, room_id: OwnedRoomOrAliasId,
/// The server name to join via.
///
/// This server will always be tried first, however if more are
/// available, they may be tried after.
via: Option<String>,
}, },
/// Manually leave a local user from a room. /// Manually leave a local user from a room.
+1 -1
View File
@@ -187,7 +187,7 @@ pub(crate) async fn change_password_route(
if services.server.config.admin_room_notices { if services.server.config.admin_room_notices {
services services
.admin .admin
.notice(&format!("User {} changed their password.", &sender_user)) .notice(&format!("User {sender_user} changed their password."))
.await; .await;
} }
+1 -5
View File
@@ -105,11 +105,7 @@ pub(crate) async fn banned_room_check(
return Err!(Request(Forbidden("This room is banned on this homeserver."))); return Err!(Request(Forbidden("This room is banned on this homeserver.")));
} }
} else if let Some(server_name) = server_name { } else if let Some(server_name) = server_name {
if services if services.moderation.is_remote_server_forbidden(server_name) {
.config
.forbidden_remote_server_names
.is_match(server_name.host())
{
warn!( warn!(
"User {user_id} who is not an admin tried joining a room which has the server \ "User {user_id} who is not an admin tried joining a room which has the server \
name {server_name} that is globally forbidden. Rejecting.", name {server_name} that is globally forbidden. Rejecting.",
+123 -47
View File
@@ -8,12 +8,12 @@ use ruma::{
UserId, UserId,
api::{ api::{
client::profile::{ client::profile::{
delete_profile_field, get_profile, get_profile_field, set_profile_field, PropagateTo, delete_profile_field, get_profile, get_profile_field, set_profile_field,
}, },
federation, federation,
}, },
assign, assign,
events::room::member::{MembershipState, RoomMemberEventContent}, events::room::member::MembershipState,
presence::PresenceState, presence::PresenceState,
profile::{ProfileFieldName, ProfileFieldValue}, profile::{ProfileFieldName, ProfileFieldValue},
}; };
@@ -62,8 +62,13 @@ pub(crate) async fn set_profile_field_route(
return Err!(Request(InvalidParam("You may not change a remote user's profile data."))); return Err!(Request(InvalidParam("You may not change a remote user's profile data.")));
} }
set_profile_field(&services, &body.user_id, ProfileFieldChange::Set(body.value.clone())) set_profile_field(
.await?; &services,
&body.user_id,
ProfileFieldChange::Set(body.value.clone()),
body.propagate_to.clone(),
)
.await?;
Ok(set_profile_field::v3::Response::new()) Ok(set_profile_field::v3::Response::new())
} }
@@ -83,8 +88,13 @@ pub(crate) async fn delete_profile_field_route(
return Err!(Request(InvalidParam("You may not change a remote user's profile data."))); return Err!(Request(InvalidParam("You may not change a remote user's profile data.")));
} }
set_profile_field(&services, &body.user_id, ProfileFieldChange::Delete(body.field.clone())) set_profile_field(
.await?; &services,
&body.user_id,
ProfileFieldChange::Delete(body.field.clone()),
body.propagate_to.clone(),
)
.await?;
Ok(delete_profile_field::v3::Response::new()) Ok(delete_profile_field::v3::Response::new())
} }
@@ -119,7 +129,13 @@ async fn fetch_full_profile(
continue; continue;
}; };
let _ = set_profile_field(services, user_id, ProfileFieldChange::Set(value)).await; let _ = set_profile_field(
services,
user_id,
ProfileFieldChange::Set(value),
PropagateTo::None,
)
.await;
} }
Some(BTreeMap::from_iter(response)) Some(BTreeMap::from_iter(response))
@@ -153,8 +169,13 @@ async fn fetch_profile_field(
if let Some(value) = response.get(field.as_str()).map(ToOwned::to_owned) { if let Some(value) = response.get(field.as_str()).map(ToOwned::to_owned) {
if let Ok(value) = ProfileFieldValue::new(field.as_str(), value) { if let Ok(value) = ProfileFieldValue::new(field.as_str(), value) {
let _ = set_profile_field(services, user_id, ProfileFieldChange::Set(value.clone())) let _ = set_profile_field(
.await; services,
user_id,
ProfileFieldChange::Set(value.clone()),
PropagateTo::None,
)
.await;
Ok(Some(value)) Ok(Some(value))
} else { } else {
@@ -163,7 +184,13 @@ async fn fetch_profile_field(
))) )))
} }
} else { } else {
let _ = set_profile_field(services, user_id, ProfileFieldChange::Delete(field)).await; let _ = set_profile_field(
services,
user_id,
ProfileFieldChange::Delete(field),
PropagateTo::None,
)
.await;
Ok(None) Ok(None)
} }
@@ -256,6 +283,7 @@ async fn set_profile_field(
services: &Services, services: &Services,
user_id: &UserId, user_id: &UserId,
change: ProfileFieldChange, change: ProfileFieldChange,
propagate_to: PropagateTo,
) -> Result<()> { ) -> Result<()> {
const MAX_KEY_LENGTH_BYTES: usize = 255; const MAX_KEY_LENGTH_BYTES: usize = 255;
const MAX_PROFILE_LENGTH_BYTES: usize = 65536; const MAX_PROFILE_LENGTH_BYTES: usize = 65536;
@@ -303,6 +331,91 @@ async fn set_profile_field(
} }
} }
// If the user is local and changed their displayname or avatar_url, update it
// in all their joined rooms. This is done before updating their profile data
// so we can check the old value of the field if `propagate_to` is `unchanged`.
if matches!(field_name, ProfileFieldName::AvatarUrl | ProfileFieldName::DisplayName)
&& matches!(propagate_to, PropagateTo::All | PropagateTo::Unchanged)
&& services.globals.user_is_local(user_id)
{
let current_displayname = services.users.displayname(user_id).await.ok();
let current_avatar_url = services.users.avatar_url(user_id).await.ok();
let mut all_joined_rooms = services.rooms.state_cache.rooms_joined(user_id);
while let Some(room_id) = all_joined_rooms.next().await {
// TODO: this clobbers any custom fields on the event content
let mut current_membership = services
.rooms
.state_accessor
.get_member(&room_id, user_id)
.await
.expect("should be able to fetch membership event for joined room");
assert_eq!(
current_membership.membership,
MembershipState::Join,
"user should be joined"
);
// If `propagate_to` is `unchanged`, and the current value of the field we're
// updating was changed from its global value in this room, skip it.
if matches!(propagate_to, PropagateTo::Unchanged) {
let field_changed_from_global = match field_name {
| ProfileFieldName::AvatarUrl =>
current_membership.avatar_url.as_ref() != current_avatar_url.as_ref(),
| ProfileFieldName::DisplayName =>
current_membership.displayname.as_ref() != current_displayname.as_ref(),
| _ => unreachable!(),
};
if field_changed_from_global {
continue;
}
}
let state_lock = services.rooms.state.mutex.lock(room_id.as_str()).await;
// Preserve keys in accordance with the key copying rules
current_membership.reason = None;
current_membership.join_authorized_via_users_server = None;
match &change {
| ProfileFieldChange::Set(ProfileFieldValue::AvatarUrl(avatar_url)) => {
current_membership.avatar_url = Some(avatar_url.clone());
},
| ProfileFieldChange::Set(ProfileFieldValue::DisplayName(displayname)) => {
current_membership.displayname = Some(displayname.clone());
},
| ProfileFieldChange::Delete(ProfileFieldName::AvatarUrl) => {
current_membership.avatar_url = None;
},
| ProfileFieldChange::Delete(ProfileFieldName::DisplayName) => {
current_membership.displayname = None;
},
| _ => unreachable!(),
}
let _ = services
.rooms
.timeline
.build_and_append_pdu(
PartialPdu::state(user_id.to_string(), &current_membership),
user_id,
Some(&room_id),
&state_lock,
)
.await;
}
if services.config.allow_local_presence {
// Send a presence EDU to indicate the profile changed
let _ = services
.presence
.ping_presence(user_id, &PresenceState::Online)
.await;
}
}
match change { match change {
| ProfileFieldChange::Set(ProfileFieldValue::DisplayName(displayname)) => { | ProfileFieldChange::Set(ProfileFieldValue::DisplayName(displayname)) => {
services services
@@ -326,42 +439,5 @@ async fn set_profile_field(
.set_profile_key(user_id, other.field_name().as_str(), other.value()), .set_profile_key(user_id, other.field_name().as_str(), other.value()),
} }
// If the user is local and changed their displayname or avatar_url, update it
// in all their joined rooms
if matches!(field_name, ProfileFieldName::AvatarUrl | ProfileFieldName::DisplayName)
&& services.users.is_active_local(user_id).await
{
let displayname = services.users.displayname(user_id).await.ok();
let avatar_url = services.users.avatar_url(user_id).await.ok();
let membership_content = assign!(
RoomMemberEventContent::new(MembershipState::Join), { displayname, avatar_url }
);
let mut all_joined_rooms = services.rooms.state_cache.rooms_joined(user_id);
while let Some(room_id) = all_joined_rooms.next().await {
let state_lock = services.rooms.state.mutex.lock(room_id.as_str()).await;
let _ = services
.rooms
.timeline
.build_and_append_pdu(
PartialPdu::state(user_id.to_string(), &membership_content),
user_id,
Some(&room_id),
&state_lock,
)
.await;
}
if services.config.allow_local_presence {
// Send a presence EDU to indicate the profile changed
let _ = services
.presence
.ping_presence(user_id, &PresenceState::Online)
.await;
}
}
Ok(()) Ok(())
} }
+6 -6
View File
@@ -219,14 +219,14 @@ async fn is_event_report_valid(
fn build_report(report: Report) -> RoomMessageEventContent { fn build_report(report: Report) -> RoomMessageEventContent {
let mut text = let mut text =
format!("@room New {} report received from {}:\n\n", report.report_type, report.sender); format!("@room New {} report received from {}:\n\n", report.report_type, report.sender);
if report.user_id.is_some() { if let Some(user_id) = report.user_id {
let _ = writeln!(text, "- Reported User ID: `{}`", report.user_id.unwrap()); let _ = writeln!(text, "- Reported User ID: `{user_id}`");
} }
if report.room_id.is_some() { if let Some(room_id) = report.room_id {
let _ = writeln!(text, "- Reported Room ID: `{}`", report.room_id.unwrap()); let _ = writeln!(text, "- Reported Room ID: `{room_id}`");
} }
if report.event_id.is_some() { if let Some(event_id) = report.event_id {
let _ = writeln!(text, "- Reported Event ID: `{}`", report.event_id.unwrap()); let _ = writeln!(text, "- Reported Event ID: `{event_id}`");
} }
let _ = writeln!(text, "- Report Reason: {}", report.reason); let _ = writeln!(text, "- Report Reason: {}", report.reason);
+79 -32
View File
@@ -10,7 +10,7 @@ use conduwuit_service::{Services, appservice::RegistrationInfo};
use futures::FutureExt; use futures::FutureExt;
use ruma::{ use ruma::{
CanonicalJsonObject, CanonicalJsonValue, Int, MilliSecondsSinceUnixEpoch, OwnedRoomAliasId, CanonicalJsonObject, CanonicalJsonValue, Int, MilliSecondsSinceUnixEpoch, OwnedRoomAliasId,
OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, RoomVersionId, UserId, OwnedUserId, RoomAliasId, RoomId, RoomVersionId, UserId,
api::client::room::{self, create_room}, api::client::room::{self, create_room},
assign, assign,
events::{ events::{
@@ -24,6 +24,7 @@ use ruma::{
member::{MembershipState, RoomMemberEventContent}, member::{MembershipState, RoomMemberEventContent},
name::RoomNameEventContent, name::RoomNameEventContent,
power_levels::RoomPowerLevelsEventContent, power_levels::RoomPowerLevelsEventContent,
server_acl::RoomServerAclEventContent,
topic::RoomTopicEventContent, topic::RoomTopicEventContent,
}, },
}, },
@@ -86,25 +87,41 @@ pub(crate) async fn create_room_route(
}; };
let room_version_rules = room_version.rules().unwrap(); let room_version_rules = room_version.rules().unwrap();
let room_id: Option<OwnedRoomId> = match room_version_rules.room_id_format { // For custom room IDs, if the user is creating a room with a v1 room ID format,
| RoomIdFormatVersion::V1 => { // we can just use that ID directly. However, if it's a custom *v2* room ID, we
// Check for custom room ID field // need to make sure that we don't generate one, which would in turn trick us
if let Some(CanonicalJsonValue::String(room_id)) = // into generating invalid v2 room events.
body.json_body.as_ref().unwrap().get("room_id") //
{ // expect_room_id is the custom room ID that the user is expecting - for v2
Some( // formatted rooms, we check that the m.room.create event's generated room ID
RoomId::parse(room_id) // exactly matches this, and abort if it doesn't. Otherwise, we use it as the
.map_err(|_| err!(Request(BadJson("Malformed custom room ID"))))?, // room ID itself.
) let expect_room_id = {
} else { let body_ref = body.json_body.as_ref().unwrap();
Some(RoomId::new_v1(services.globals.server_name())) if let Some(CanonicalJsonValue::String(room_id)) = body_ref
} .get("fi.mau.room_id")
}, .or_else(|| body_ref.get("room_id"))
{
Some(
RoomId::parse(room_id)
.map_err(|e| err!(Request(BadJson("Malformed custom room ID: {e}"))))?,
)
} else {
None
}
};
let room_id = match room_version_rules.room_id_format {
| RoomIdFormatVersion::V1 => Some(
expect_room_id
.clone()
.unwrap_or_else(|| RoomId::new_v1(services.globals.server_name())),
),
| _ => None, | _ => None,
}; };
// check if room ID doesn't already exist instead of erroring on auth check // check if room ID doesn't already exist instead of erroring on auth check
if let Some(ref room_id) = room_id { if let Some(room_id) = room_id.as_ref().or(expect_room_id.as_ref()) {
if services.rooms.short.get_shortroomid(room_id).await.is_ok() { if services.rooms.short.get_shortroomid(room_id).await.is_ok() {
return Err!(Request(RoomInUse("Room with that custom room ID already exists",))); return Err!(Request(RoomInUse("Room with that custom room ID already exists",)));
} }
@@ -243,15 +260,16 @@ pub(crate) async fn create_room_route(
// Allow requesters to override the `origin_server_ts` to customize room ids // Allow requesters to override the `origin_server_ts` to customize room ids
// from v12 onwards // from v12 onwards
let custom_origin_server_ts = body let custom_origin_server_ts = {
.json_body let body_ref = body.json_body.as_ref().unwrap();
.as_ref() body_ref
.unwrap() .get("origin_server_ts")
.get("origin_server_ts") .or_else(|| body_ref.get("fi.mau.origin_server_ts"))
.and_then(CanonicalJsonValue::as_integer) .and_then(CanonicalJsonValue::as_integer)
.map(Into::into) .map(Into::into)
.and_then(|value: i64| value.try_into().ok()) .and_then(|value: i64| value.try_into().ok())
.map(MilliSecondsSinceUnixEpoch); .map(MilliSecondsSinceUnixEpoch)
};
let create_event_id = services let create_event_id = services
.rooms .rooms
@@ -281,6 +299,13 @@ pub(crate) async fn create_room_route(
}; };
drop(state_lock); drop(state_lock);
debug!("Room created with ID {room_id}"); debug!("Room created with ID {room_id}");
if let Some(expected_room_id) = expect_room_id
&& expected_room_id != room_id
{
return Err!(BadServerResponse(
"Room's final room ID was {room_id}, but expected {expected_room_id}"
));
}
let state_lock = services.rooms.state.mutex.lock(room_id.as_str()).await; let state_lock = services.rooms.state.mutex.lock(room_id.as_str()).await;
// 2. Let the room creator join // 2. Let the room creator join
@@ -453,7 +478,32 @@ pub(crate) async fn create_room_route(
.boxed() .boxed()
.await?; .await?;
// 6. Events listed in initial_state // 6. Initial state events provided by the homeserver
let mut server_initial_state: Vec<PartialPdu> = Vec::new();
if let Some(allow_list) = services.server.config.default_room_acl_allow.clone() {
server_initial_state.push(PartialPdu::state(
String::new(),
&RoomServerAclEventContent::new(true, allow_list, vec![]),
));
} else if let Some(deny_list) = services.server.config.default_room_acl_deny.clone() {
server_initial_state.push(PartialPdu::state(
String::new(),
&RoomServerAclEventContent::new(true, vec!["*".to_owned()], deny_list),
));
}
for pdu in server_initial_state {
services
.rooms
.timeline
.build_and_append_pdu(pdu, sender_user, Some(&room_id), &state_lock)
.boxed()
.await?;
}
// 7. Events listed in initial_state
for event in &body.initial_state { for event in &body.initial_state {
let mut partial_pdu = event let mut partial_pdu = event
.deserialize_as_unchecked::<PartialPdu>() .deserialize_as_unchecked::<PartialPdu>()
@@ -481,7 +531,7 @@ pub(crate) async fn create_room_route(
.await?; .await?;
} }
// 7. Events implied by name and topic // 8. Events implied by name and topic
if let Some(name) = &body.name { if let Some(name) = &body.name {
services services
.rooms .rooms
@@ -510,7 +560,7 @@ pub(crate) async fn create_room_route(
.await?; .await?;
} }
// 8. Events implied by invite (and TODO: invite_3pid) // 9. Events implied by invite (and TODO: invite_3pid)
drop(state_lock); drop(state_lock);
for recipient_user in &invitees { for recipient_user in &invitees {
if let Err(e) = if let Err(e) =
@@ -536,10 +586,7 @@ pub(crate) async fn create_room_route(
if services.server.config.admin_room_notices { if services.server.config.admin_room_notices {
services services
.admin .admin
.send_text(&format!( .send_text(&format!("{sender_user} made {room_id} public to the room directory"))
"{sender_user} made {} public to the room directory",
&room_id
))
.await; .await;
} }
info!("{sender_user} made {0} public to the room directory", &room_id); info!("{sender_user} made {0} public to the room directory", &room_id);
+419 -271
View File
@@ -2,47 +2,267 @@ use std::cmp::max;
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Err, Error, Event, Result, debug, err, info, Err, Error, Event, Result, debug,
debug::DebugInspect,
err, error,
info::room_version::UNSTABLE_ROOM_VERSIONS,
matrix::{StateKey, pdu::PartialPdu}, matrix::{StateKey, pdu::PartialPdu},
}; };
use futures::{FutureExt, StreamExt}; use futures::{FutureExt, StreamExt};
use ruma::{ use ruma::{
CanonicalJsonObject, RoomId, RoomVersionId, OwnedEventId, OwnedRoomId, RoomId, UserId,
api::{client::room::upgrade_room, error::ErrorKind}, api::{client::room::upgrade_room, error::ErrorKind},
assign, assign,
events::{ events::{
StateEventType, TimelineEventType, StateEventType,
room::{ room::{
create::PreviousRoom, create::{PreviousRoom, RoomCreateEventContent},
member::{MembershipState, RoomMemberEventContent}, member::{MembershipState, RoomMemberEventContent},
power_levels::RoomPowerLevelsEventContent, power_levels::RoomPowerLevelsEventContent,
tombstone::RoomTombstoneEventContent, tombstone::RoomTombstoneEventContent,
}, },
space::child::{RedactedSpaceChildEventContent, SpaceChildEventContent}, space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
}, },
int, int,
room_version_rules::RoomIdFormatVersion, room_version_rules::RoomIdFormatVersion,
}; };
use serde_json::{json, value::to_raw_value}; use serde_json::value::to_raw_value;
use crate::router::Ruma; use crate::router::Ruma;
/// Recommended transferable state events list from the spec /// Recommended transferable state events list from the spec
const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 11] = &[ const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 11] = &[
StateEventType::RoomAvatar, StateEventType::RoomServerAcl,
StateEventType::RoomEncryption, StateEventType::RoomEncryption,
StateEventType::RoomName,
StateEventType::RoomAvatar,
StateEventType::RoomTopic,
StateEventType::RoomGuestAccess, StateEventType::RoomGuestAccess,
StateEventType::RoomHistoryVisibility, StateEventType::RoomHistoryVisibility,
StateEventType::RoomJoinRules, StateEventType::RoomJoinRules,
StateEventType::RoomName,
StateEventType::RoomPowerLevels, StateEventType::RoomPowerLevels,
StateEventType::RoomServerAcl, StateEventType::SpaceParent,
StateEventType::RoomTopic,
// Not explicitly recommended in spec, but very useful.
StateEventType::SpaceChild, StateEventType::SpaceChild,
StateEventType::SpaceParent, // TODO: m.room.policy?
]; ];
/// Updates spaces that are marked as parents of old_room_id to instead point to
/// the new room ID.
///
/// See: https://github.com/matrix-org/matrix-spec-proposals/pull/4168
async fn update_parents(
services: &crate::State,
sender: &UserId,
old_room_id: &RoomId,
new_room_id: &RoomId,
) -> Result {
// Fetch the spaces which this room claims are its parents.
// In rooms that reference the old room via m.space.child events...
let parents = services
.rooms
.state_accessor
.room_state_keys(old_room_id, &StateEventType::SpaceParent)
.await
.debug_inspect(|k| debug!(?old_room_id, "Parents: {k:?}"))?;
for raw_parent_id in parents {
let parent_id = RoomId::parse(&raw_parent_id)?;
if !services
.rooms
.state_cache
.is_joined(sender, &parent_id)
.await
{
debug!(%parent_id, "Skipping space as sender is not joined");
continue; // Skip updating rooms the sender isn't in.
}
let state_lock = services.rooms.state.mutex.lock(parent_id.as_str()).await;
// We're now fetching state from the *space* that has the old room as a *child*.
// Follow along. This will be on the test.
let Ok(child) = services
.rooms
.state_accessor
.room_state_get_content::<SpaceChildEventContent>(
&parent_id,
&StateEventType::SpaceChild,
old_room_id.as_str(),
)
.await
.debug_inspect_err(|e| {
error!(
?parent_id,
old_room_id=?old_room_id,
new_room_id=?new_room_id,
%e,
"failed to fetch m.space.child from parent"
);
})
else {
// If the space does not have a child event for this room, we can skip it
continue;
};
// ...the upgrading server SHOULD send a new m.space.child event with state_key
// set to the new room's ID, copying the order and suggested fields from the
// content of the m.space.child with state_key of the previous room ID.
services
.rooms
.timeline
.build_and_append_pdu(
PartialPdu::state(
new_room_id.as_str(),
&assign!(
SpaceChildEventContent::new(vec![sender.server_name().to_owned()]),
{
order: child.order,
suggested: child.suggested,
}
),
),
sender,
Some(&parent_id),
&state_lock,
)
.boxed()
.await
.debug_inspect_err(|e| {
error!(
?parent_id,
old_room_id=?old_room_id,
new_room_id=?new_room_id,
%e,
"failed to send m.space.child to parent during room upgrade"
);
})
.ok();
drop(state_lock);
}
Ok(())
}
/// If the room being upgraded is a space, replace all m.space.parent references
/// in its children to point at the newly upgraded room ID, so that they point
/// at the new space.
///
/// See: https://github.com/matrix-org/matrix-spec-proposals/pull/4168
async fn update_children(
services: &crate::State,
sender: &UserId,
old_room_id: &RoomId,
new_room_id: &RoomId,
) -> Result {
// Fetch the children of this space.
// Note that this might not actually be a space, but just a room that has
// children.
// In rooms that reference the old room via m.space.parent events...
// NOTE: Doing that would be expensive. We'll instead fetch rooms which the
// space claims are children.
let parents = services
.rooms
.state_accessor
.room_state_keys(old_room_id, &StateEventType::SpaceChild)
.await
.debug_inspect(|k| debug!(?old_room_id, "Children: {k:?}"))?;
for raw_child_id in parents {
let child_id = RoomId::parse(&raw_child_id)?;
if !services
.rooms
.state_cache
.is_joined(sender, &child_id)
.await
{
debug!(%child_id, "Skipping child room as sender is not joined");
continue;
}
let state_lock = services.rooms.state.mutex.lock(child_id.as_str()).await;
// We're now fetching state from the *child* that has the old space as a
// *parent*. Follow along. This will also be on the test.
let Ok(ref parent) = services
.rooms
.state_accessor
.room_state_get_content::<SpaceParentEventContent>(
&child_id,
&StateEventType::SpaceParent,
old_room_id.as_str(),
)
.await
.debug_inspect_err(|e| {
error!(
?child_id,
old_room_id=?old_room_id,
new_room_id=?new_room_id,
%e,
"failed to fetch m.space.parent from child"
);
})
else {
// If the child does not have a parent event for this room, we can skip it.
continue;
};
// ... the upgrading server SHOULD send a new m.space.parent event with
// state_key set to the new room's ID.
services
.rooms
.timeline
.build_and_append_pdu(
PartialPdu::state(
new_room_id.as_str(),
&assign!(SpaceParentEventContent::new(vec![sender.server_name().to_owned()]), { canonical: parent.canonical }),
),
sender,
Some(&child_id),
&state_lock,
)
.boxed()
.await
.debug_inspect_err(|e| error!(
child_id=?child_id,
old_room_id=?old_room_id,
new_room_id=?new_room_id,
%e,
"failed to send updated m.space.parent to child during room upgrade"
))
.ok();
// If the previous m.space.parent event has canonical set to true in content,
// homeservers SHOULD update the old state event to set canonical to false,
// while setting it to true in the newly-sent m.space.parent event.
if parent.canonical {
services
.rooms
.timeline
.build_and_append_pdu(
PartialPdu::state(
old_room_id.as_str(),
&assign!(parent.clone(), {canonical: false}),
),
sender,
Some(&child_id),
&state_lock,
)
.boxed()
.await
.debug_inspect_err(|e| {
error!(
child_id=?child_id,
old_room_id=?old_room_id,
new_room_id=?new_room_id,
%e,
"failed to send non-canonical m.space.parent to child room"
);
})
.ok();
}
drop(state_lock);
}
Ok(())
}
/// # `POST /_matrix/client/r0/rooms/{roomId}/upgrade` /// # `POST /_matrix/client/r0/rooms/{roomId}/upgrade`
/// ///
/// Upgrades the room. /// Upgrades the room.
@@ -57,10 +277,14 @@ pub(crate) async fn upgrade_room_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<upgrade_room::v3::Request>, body: Ruma<upgrade_room::v3::Request>,
) -> Result<upgrade_room::v3::Response> { ) -> Result<upgrade_room::v3::Response> {
// TODO[v12]: Handle additional creators let sender_user = body.sender_user();
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if !services.server.supported_room_version(&body.new_version) { let (supported, forbid_unstable, is_unstable) = (
services.server.supported_room_version(&body.new_version),
!services.config.allow_unstable_room_versions,
UNSTABLE_ROOM_VERSIONS.contains(&body.new_version),
);
if !supported || (forbid_unstable && is_unstable) {
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::UnsupportedRoomVersion, ErrorKind::UnsupportedRoomVersion,
"This server does not support that room version.", "This server does not support that room version.",
@@ -77,17 +301,15 @@ pub(crate) async fn upgrade_room_route(
return Err!(Request(Forbidden("Upgrading the admin room this way is not allowed."))); return Err!(Request(Forbidden("Upgrading the admin room this way is not allowed.")));
} }
// First, check if the user has permission to upgrade the room (send tombstone // 1. Check that the user has permission to send m.room.tombstone events in the
// event) // room.
let old_room_state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await; let old_room_state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await;
// Check tombstone permission by attempting to create (but not send) the event // Check tombstone permission by attempting to create (but not send) the event.
// Note that this does internally call the policy server with a fake room ID, services
// which may not be good?
let tombstone_test_result = services
.rooms .rooms
.timeline .timeline
.create_hash_and_sign_event( .create_event(
PartialPdu::state( PartialPdu::state(
StateKey::new(), StateKey::new(),
&RoomTombstoneEventContent::new( &RoomTombstoneEventContent::new(
@@ -99,157 +321,104 @@ pub(crate) async fn upgrade_room_route(
Some(&body.room_id), Some(&body.room_id),
&old_room_state_lock, &old_room_state_lock,
) )
.await; .await
.map_err(|_| {
if let Err(_e) = tombstone_test_result { err!(Request(Forbidden("You do not have permission to upgrade this room.")))
return Err!(Request(Forbidden("User does not have permission to upgrade this room."))); })?;
}
drop(old_room_state_lock);
// Create a replacement room // Create a replacement room
let room_version_rules = body let new_version_rules = body
.new_version .new_version
.rules() .rules()
.expect("new room version should have defined rules"); .expect("new room version should have defined rules");
let replacement_room_owned = if room_version_rules.room_id_format == RoomIdFormatVersion::V2 {
Some(RoomId::new_v1(services.globals.server_name())) let last_event = if new_version_rules
} else { .authorization
.room_create_event_id_as_room_id
{
None None
};
let replacement_room: Option<&RoomId> = replacement_room_owned.as_ref().map(AsRef::as_ref);
let replacement_room_tmp = match replacement_room {
| Some(v) => v,
| None => &RoomId::new_v1(services.globals.server_name()),
};
let _short_id = services
.rooms
.short
.get_or_create_shortroomid(replacement_room_tmp)
.await;
// For pre-v12 rooms, send tombstone before creating replacement room
let tombstone_event_id = if room_version_rules.room_id_format != RoomIdFormatVersion::V2 {
let state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await;
// Send a m.room.tombstone event to the old room to indicate that it is not
// intended to be used any further
let tombstone_event_id = services
.rooms
.timeline
.build_and_append_pdu(
PartialPdu::state(
StateKey::new(),
&RoomTombstoneEventContent::new(
"This room has been replaced".to_owned(),
replacement_room.unwrap().to_owned(),
),
),
sender_user,
Some(&body.room_id),
&state_lock,
)
.boxed()
.await?;
// Change lock to replacement room
drop(state_lock);
Some(tombstone_event_id)
} else { } else {
None Some(
services
.rooms
.state
.get_forward_extremities(&body.room_id)
.collect::<Vec<OwnedEventId>>()
.await[0]
.clone(),
)
}; };
let state_lock = services let old_create_event: RoomCreateEventContent = services
.rooms
.state
.mutex
.lock(replacement_room_tmp.as_str())
.await;
// Get the old room creation event
let mut create_event_content: CanonicalJsonObject = services
.rooms .rooms
.state_accessor .state_accessor
.room_state_get_content(&body.room_id, &StateEventType::RoomCreate, "") .room_state_get_content(&body.room_id, &StateEventType::RoomCreate, "")
.await .await
.map_err(|_| err!(Database("Found room without m.room.create event.")))?; .map_err(|_| err!(Database("Found room without m.room.create event.")))?;
let create_event_content = if new_version_rules.authorization.use_room_create_sender {
// Use the m.room.tombstone event as the predecessor RoomCreateEventContent::new_v1(sender_user.to_owned())
} else {
let predecessor = { RoomCreateEventContent::new_v11()
#[allow(deprecated, reason = "Clients still use event_id even though it's deprecated")] };
Some(assign!(PreviousRoom::new(body.room_id.clone()), { #[allow(deprecated)]
event_id: tombstone_event_id, let create_event_content = {
})) assign!(
create_event_content,
{
additional_creators: if new_version_rules.authorization.additional_room_creators {
body.additional_creators.clone()
} else { Vec::new() },
creator: if new_version_rules.authorization.use_room_create_sender {
None
} else { Some(sender_user.to_owned()) },
predecessor: Some(assign!(PreviousRoom::new(body.room_id.clone()), {
event_id: last_event,
})),
room_type: old_create_event.room_type.clone(),
room_version: body.new_version.clone(),
}
)
}; };
// Send a m.room.create event containing a predecessor field and the applicable let replacement_room_id: Option<OwnedRoomId> =
// room_version if new_version_rules.room_id_format == RoomIdFormatVersion::V2 {
{ None
use RoomVersionId::*; } else {
match body.new_version { Some(RoomId::new_v1(services.globals.server_name()))
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => { };
create_event_content.insert(
"creator".into(),
json!(&sender_user).try_into().map_err(|e| {
info!("Error forming creation event: {e}");
Error::BadRequest(ErrorKind::BadJson, "Error forming creation event")
})?,
);
},
| _ => {
// "creator" key no longer exists in V11 rooms
create_event_content.remove("creator");
},
// TODO(hydra): additional_creators
}
}
create_event_content.insert(
"room_version".into(),
json!(&body.new_version)
.try_into()
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?,
);
create_event_content.insert(
"predecessor".into(),
json!(predecessor)
.try_into()
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?,
);
// Validate creation event content
if serde_json::from_str::<CanonicalJsonObject>(
to_raw_value(&create_event_content)
.expect("Error forming creation event")
.get(),
)
.is_err()
{
return Err(Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"));
}
let new_room_state_lock = if let Some(new_room_id) = replacement_room_id.as_ref() {
services.rooms.state.mutex.lock(new_room_id.as_str()).await
} else {
// NOTE: Using a hardcoded room ID for the temporary mutex means only one room
// can be created at a time. This is actually beneficial, as it reduces the
// risk of concurrent in-flight collisions.
services.rooms.state.mutex.lock("!new-room").await
};
debug!("Upgrading {} to room version {}", &body.room_id, &body.new_version);
let create_event_id = services let create_event_id = services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu( .build_and_append_pdu(
PartialPdu { PartialPdu::state(StateKey::new(), &create_event_content),
event_type: TimelineEventType::RoomCreate,
content: to_raw_value(&create_event_content)
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(StateKey::new()),
redacts: None,
timestamp: None,
},
sender_user, sender_user,
replacement_room, replacement_room_id.as_deref(),
&state_lock, &new_room_state_lock,
) )
.boxed() .boxed()
.await?; .await?;
let create_id = create_event_id.as_str().replace('$', "!"); drop(new_room_state_lock);
let (replacement_room, state_lock) = // re-acquire a new lock with the new room ID.
if room_version_rules.room_id_format == RoomIdFormatVersion::V2 { // We don't actually need a state lock for sending the m.room.create event, but
let parsed_room_id = RoomId::parse(&create_id)?; // we get one anyway because the function requires it and I can't be bothered
// refactoring it.
let (replacement_room_id, new_room_state_lock) =
if new_version_rules.room_id_format == RoomIdFormatVersion::V2 {
let parsed_room_id = RoomId::new_v2(
create_event_id
.as_str()
.strip_prefix("$")
.expect("event ID must start with $ sigil"),
)?;
let lock = services let lock = services
.rooms .rooms
.state .state
@@ -258,9 +427,13 @@ pub(crate) async fn upgrade_room_route(
.await; .await;
(Some(parsed_room_id), lock) (Some(parsed_room_id), lock)
} else { } else {
(replacement_room.map(ToOwned::to_owned), state_lock) let new_room_id =
replacement_room_id.expect("replacement room id should be known by now");
let lock = services.rooms.state.mutex.lock(new_room_id.as_str()).await;
(Some(new_room_id), lock)
}; };
debug!("Upgraded {} to {}", &body.room_id, replacement_room_id.as_deref().unwrap());
// Join the new room // Join the new room
services services
.rooms .rooms
@@ -274,13 +447,13 @@ pub(crate) async fn upgrade_room_route(
}), }),
), ),
sender_user, sender_user,
replacement_room.as_deref(), replacement_room_id.as_deref(),
&state_lock, &new_room_state_lock,
) )
.boxed() .boxed()
.await?; .await?;
// Replicate transferable state events to the new room // 3. Replicate transferable state events to the new room
for event_type in TRANSFERABLE_STATE_EVENTS { for event_type in TRANSFERABLE_STATE_EVENTS {
let state_keys = services let state_keys = services
.rooms .rooms
@@ -297,26 +470,45 @@ pub(crate) async fn upgrade_room_route(
| Ok(v) => v.content().to_owned(), | Ok(v) => v.content().to_owned(),
| Err(_) => continue, // Skipping missing events. | Err(_) => continue, // Skipping missing events.
}; };
if event_content.get() == "{}" {
// If the event content is empty, we skip it
continue;
}
// If this is a power levels event, and the new room version has creators, // If this is a power levels event, and the new room version has creators,
// we need to make sure they dont appear in the users block of power levels. // we need to make sure they dont appear in the users block of power levels.
if *event_type == StateEventType::RoomPowerLevels { if *event_type == StateEventType::RoomPowerLevels {
// TODO(v12): additional creators let creators = body
let creators = vec![sender_user]; .additional_creators
.clone()
.iter()
.chain(std::iter::once(&sender_user.to_owned()))
.map(ToOwned::to_owned)
.collect::<Vec<_>>();
let mut power_levels_event_content: RoomPowerLevelsEventContent = let mut power_levels_event_content: RoomPowerLevelsEventContent =
serde_json::from_str(event_content.get()).map_err(|_| { serde_json::from_str(event_content.get()).map_err(|_| {
err!(Request(BadJson("Power levels event content is not valid"))) err!(Request(BadJson("Power levels event content is not valid")))
})?; })?;
for creator in creators { for creator in creators {
power_levels_event_content.users.remove(creator); if new_version_rules
.authorization
.explicitly_privilege_room_creators
{
power_levels_event_content.users.remove(&creator);
} else {
power_levels_event_content.users.insert(
creator.clone(),
max(
int!(100),
power_levels_event_content
.users
.get(&creator)
.copied()
.unwrap_or_default(),
),
);
}
} }
event_content = to_raw_value(&power_levels_event_content) event_content = to_raw_value(&power_levels_event_content)
.expect("event is valid, we just deserialized and modified it"); .expect("event is valid, we just deserialized and modified it");
} }
debug!(%event_type, ?state_key, "Transferring state event to new room");
services services
.rooms .rooms
.timeline .timeline
@@ -328,15 +520,15 @@ pub(crate) async fn upgrade_room_route(
..Default::default() ..Default::default()
}, },
sender_user, sender_user,
replacement_room.as_deref(), replacement_room_id.as_deref(),
&state_lock, &new_room_state_lock,
) )
.boxed() .boxed()
.await?; .await?;
} }
} }
// Moves any local aliases to the new room // 4. Move any local aliases to the new room
let mut local_aliases = services let mut local_aliases = services
.rooms .rooms
.alias .alias
@@ -344,6 +536,7 @@ pub(crate) async fn upgrade_room_route(
.boxed(); .boxed();
while let Some(alias) = local_aliases.next().await { while let Some(alias) = local_aliases.next().await {
debug!(?alias, "Migrating alias");
services services
.rooms .rooms
.alias .alias
@@ -352,11 +545,31 @@ pub(crate) async fn upgrade_room_route(
services.rooms.alias.set_alias( services.rooms.alias.set_alias(
&alias, &alias,
replacement_room.as_ref().unwrap(), replacement_room_id.as_deref().unwrap(),
sender_user, sender_user,
)?; )?;
} }
// 5. Send a `m.room.tombstone` event to the old room to indicate that it is not
// intended to be used any further.
debug!(target=?body.room_id, "Sending tombstone to old room");
services
.rooms
.timeline
.build_and_append_pdu(
PartialPdu::state(
StateKey::new(),
&RoomTombstoneEventContent::new(
"This room has been replaced".to_owned(),
replacement_room_id.clone().unwrap(),
),
),
sender_user,
Some(&body.room_id),
&old_room_state_lock,
)
.await?;
// Get the old room power levels // Get the old room power levels
let mut power_levels = services let mut power_levels = services
.rooms .rooms
@@ -378,8 +591,10 @@ pub(crate) async fn upgrade_room_route(
power_levels.events_default = new_level; power_levels.events_default = new_level;
power_levels.invite = new_level; power_levels.invite = new_level;
// Modify the power levels in the old room to prevent sending of events and // 6. Modify the power levels in the old room to prevent sending of events and
// inviting new users // inviting new users
// Spec dictates that this is allowed to fail.
debug!(target=?body.room_id, ?new_level, "Raising power level in old room to lock it");
services services
.rooms .rooms
.timeline .timeline
@@ -390,117 +605,50 @@ pub(crate) async fn upgrade_room_route(
), ),
sender_user, sender_user,
Some(&body.room_id), Some(&body.room_id),
&state_lock, &old_room_state_lock,
) )
.boxed() .boxed()
.await?; .await
.ok();
drop(state_lock); // MSC4168: Update spaces that reference this room to point at the new room.
debug!("Updating parent spaces");
// For v12 rooms, send tombstone AFTER creating replacement room update_parents(
if room_version_rules.room_id_format == RoomIdFormatVersion::V2 { &services,
let old_room_state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await; sender_user,
// For v12 rooms, no event reference in predecessor due to cyclic dependency - &body.room_id,
// could best effort one maybe? replacement_room_id.as_deref().unwrap(),
services )
.rooms .await
.timeline .inspect_err(|e| {
.build_and_append_pdu( error!(
PartialPdu::state( old_room_id=?body.room_id,
StateKey::new(), new_room_id=?replacement_room_id.as_deref().unwrap(),
&RoomTombstoneEventContent::new( %e,
"This room has been replaced".to_owned(), "failed to update parent spaces during room upgrade"
replacement_room.as_ref().unwrap().to_owned(),
),
),
sender_user,
Some(&body.room_id),
&old_room_state_lock,
)
.await?;
drop(old_room_state_lock);
}
// Check if the old room has a space parent, and if so, whether we should update
// it (m.space.parent, room_id)
let parents = services
.rooms
.state_accessor
.room_state_keys(&body.room_id, &StateEventType::SpaceParent)
.await?;
for raw_space_id in parents {
let space_id = RoomId::parse(&raw_space_id)?;
let Ok(child) = services
.rooms
.state_accessor
.room_state_get_content::<SpaceChildEventContent>(
&space_id,
&StateEventType::SpaceChild,
body.room_id.as_str(),
)
.await
else {
// If the space does not have a child event for this room, we can skip it
continue;
};
debug!(
"Updating space {space_id} child event for room {} to {}",
&body.room_id,
replacement_room.as_ref().unwrap()
); );
// First, drop the space's child event })
let state_lock = services.rooms.state.mutex.lock(space_id.as_str()).await; .ok();
debug!("Removing space child event for room {} in space {space_id}", &body.room_id);
services // MSC4168: Update child rooms to point at the new space, where possible
.rooms debug!("Updating space children");
.timeline update_children(
.build_and_append_pdu( &services,
PartialPdu { sender_user,
event_type: StateEventType::SpaceChild.into(), &body.room_id,
content: to_raw_value(&RedactedSpaceChildEventContent::new()) replacement_room_id.as_deref().unwrap(),
.expect("event is valid, we just created it"), )
state_key: Some(body.room_id.clone().as_str().into()), .await
..Default::default() .inspect_err(|e| {
}, error!(
sender_user, old_room_id=?body.room_id,
Some(&space_id), new_room_id=?replacement_room_id.as_deref().unwrap(),
&state_lock, %e,
) "failed to update space children during room upgrade"
.boxed()
.await
.ok();
// Now, add a new child event for the replacement room
debug!(
"Adding space child event for room {} in space {space_id}",
replacement_room.as_ref().unwrap()
); );
services })
.rooms .ok();
.timeline
.build_and_append_pdu(
PartialPdu::state(
replacement_room.as_ref().unwrap().as_str(),
&assign!(SpaceChildEventContent::new(vec![sender_user.server_name().to_owned()]), {
order: child.order,
suggested: child.suggested,
}),
),
sender_user,
Some(&space_id),
&state_lock,
)
.boxed()
.await
.ok();
debug!(
"Finished updating space {space_id} child event for room {} to {}",
&body.room_id,
replacement_room.as_ref().unwrap()
);
drop(state_lock);
}
// Return the replacement room id // Return the replacement room id
Ok(upgrade_room::v3::Response::new(replacement_room.as_ref().unwrap().to_owned())) Ok(upgrade_room::v3::Response::new(replacement_room_id.unwrap()))
} }
+16
View File
@@ -3,6 +3,7 @@ use conduwuit::{Err, Result};
use ruma::{ use ruma::{
api::client::discovery::{ api::client::discovery::{
discover_homeserver::{self, HomeserverInfo}, discover_homeserver::{self, HomeserverInfo},
discover_policy_server,
discover_support::{self, Contact, ContactRole}, discover_support::{self, Contact, ContactRole},
}, },
assign, assign,
@@ -114,3 +115,18 @@ pub(crate) async fn well_known_support(
Ok(assign!(discover_support::Response::with_contacts(contacts), { support_page })) Ok(assign!(discover_support::Response::with_contacts(contacts), { support_page }))
} }
/// # `GET /.well-known/matrix/policy_server`
///
/// Advertises the policy server's public key, allowing clients to discover the
/// values to be set in m.room.policy. Introduced in spec v1.18.
pub(crate) async fn well_known_policy_server(
State(services): State<crate::State>,
_body: Ruma<discover_policy_server::Request>,
) -> Result<discover_policy_server::Response> {
if let Some(key) = services.config.well_known.policy_server_public_key.clone() {
Ok(discover_policy_server::Response::new(key))
} else {
Err!(Request(NotFound("No policy server available.")))
}
}
+1
View File
@@ -183,6 +183,7 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
.ruma_route(&client::put_suspended_status) .ruma_route(&client::put_suspended_status)
.ruma_route(&client::well_known_support) .ruma_route(&client::well_known_support)
.ruma_route(&client::well_known_client) .ruma_route(&client::well_known_client)
.ruma_route(&client::well_known_policy_server)
.ruma_route(&client::get_rtc_transports) .ruma_route(&client::get_rtc_transports)
.ruma_route(&client::room_initial_sync_route) .ruma_route(&client::room_initial_sync_route)
.route("/_conduwuit/server_version", get(client::conduwuit_server_version)) .route("/_conduwuit/server_version", get(client::conduwuit_server_version))
+32 -8
View File
@@ -9,6 +9,7 @@ use ruma::{
AccessToken, AccessTokenOptional, AppserviceToken, AppserviceTokenOptional, AccessToken, AccessTokenOptional, AppserviceToken, AppserviceTokenOptional,
AuthScheme, NoAccessToken, NoAuthentication, AuthScheme, NoAccessToken, NoAuthentication,
}, },
client,
federation::authentication::ServerSignatures, federation::authentication::ServerSignatures,
}, },
}; };
@@ -85,10 +86,21 @@ impl CheckAuth for ServerSignatures {
let keys: PubKeyMap = [(output.origin.as_str().into(), keys)].into(); let keys: PubKeyMap = [(output.origin.as_str().into(), keys)].into();
match output.verify_request(request, destination, &keys) { match output.verify_request(request, destination, &keys) {
| Ok(()) => Ok(Auth { | Ok(()) => {
origin: Some(output.origin.clone()), if services
..Default::default() .moderation
}), .is_remote_server_forbidden(&output.origin)
{
return Err!(Request(Forbidden(
"You are blocked from federating with this server."
)));
}
Ok(Auth {
origin: Some(output.origin.clone()),
..Default::default()
})
},
| Err(err) => | Err(err) =>
Err!(Request(Unauthorized(warn!("Failed to verify X-Matrix header: {err}")))), Err!(Request(Unauthorized(warn!("Failed to verify X-Matrix header: {err}")))),
} }
@@ -116,10 +128,9 @@ impl CheckAuth for AccessToken {
.await .await
.is_ok_and(std::convert::identity) .is_ok_and(std::convert::identity)
{ {
if !(route == TypeId::of::<ruma::api::client::session::logout::v3::Request>() if !(route == TypeId::of::<client::session::logout::v3::Request>()
|| route || route == TypeId::of::<client::session::logout_all::v3::Request>())
== TypeId::of::<ruma::api::client::session::logout_all::v3::Request>( {
)) {
return Err!(Request(Unauthorized("Your account is locked."))); return Err!(Request(Unauthorized("Your account is locked.")));
} }
} }
@@ -258,6 +269,19 @@ impl CheckAuth for NoAccessToken {
err!(Request(Unauthorized(warn!("Failed to extract authorization: {}", err)))) err!(Request(Unauthorized(warn!("Failed to extract authorization: {}", err))))
})?; })?;
// Check special access restrictions
if (route == TypeId::of::<client::profile::get_avatar_url::v3::Request>()
|| route == TypeId::of::<client::profile::get_display_name::v3::Request>()
|| route == TypeId::of::<client::profile::get_profile_field::v3::Request>()
|| route == TypeId::of::<client::profile::get_profile::v3::Request>())
&& services.config.require_auth_for_profile_requests
&& token.is_none()
{
return Err!(Request(Unauthorized(
"This server requires authentication to access user profiles."
)));
}
<AccessTokenOptional as CheckAuth>::verify(services, token, request, query, route).await <AccessTokenOptional as CheckAuth>::verify(services, token, request, query, route).await
} }
} }
+9
View File
@@ -22,6 +22,15 @@ pub(crate) async fn get_event_route(
.await .await
.map_err(|_| err!(Request(NotFound("Event not found."))))?; .map_err(|_| err!(Request(NotFound("Event not found."))))?;
if services
.rooms
.pdu_metadata
.is_event_rejected(&body.event_id)
.await
{
return Err!(Request(NotFound("Event not found.")));
}
let room_id: &RoomId = event let room_id: &RoomId = event
.get("room_id") .get("room_id")
.and_then(|val| val.as_str()) .and_then(|val| val.as_str())
+9
View File
@@ -26,6 +26,15 @@ pub(crate) async fn get_event_authorization_route(
.check() .check()
.await?; .await?;
if services
.rooms
.pdu_metadata
.is_event_rejected(&body.event_id)
.await
{
return Err!(Request(NotFound("Event not found.")));
}
if !services if !services
.rooms .rooms
.state_cache .state_cache
+9
View File
@@ -78,6 +78,15 @@ pub(crate) async fn get_missing_events_route(
body.room_id body.room_id
))); )));
} }
if services
.rooms
.pdu_metadata
.is_event_rejected(pdu.event_id())
.await
{
debug!(%next_event_id, "event rejected, not traversing");
continue;
}
if !services if !services
.rooms .rooms
+9
View File
@@ -24,6 +24,15 @@ pub(crate) async fn get_room_state_route(
.check() .check()
.await?; .await?;
if services
.rooms
.pdu_metadata
.is_event_rejected(&body.event_id)
.await
{
return Err!(Request(NotFound("Event not found.")));
}
if !services if !services
.rooms .rooms
.state_cache .state_cache
+9
View File
@@ -25,6 +25,15 @@ pub(crate) async fn get_room_state_ids_route(
.check() .check()
.await?; .await?;
if services
.rooms
.pdu_metadata
.is_event_rejected(&body.event_id)
.await
{
return Err!(Request(NotFound("Event not found.")));
}
if !services if !services
.rooms .rooms
.state_cache .state_cache
+35
View File
@@ -2,6 +2,7 @@ use std::env::consts::OS;
use either::Either; use either::Either;
use figment::Figment; use figment::Figment;
use ruma::events::room::server_acl::RoomServerAclEventContent;
use super::DEPRECATED_KEYS; use super::DEPRECATED_KEYS;
use crate::{Config, Err, Result, Server, debug, debug_info, debug_warn, error, warn}; use crate::{Config, Err, Result, Server, debug, debug_info, debug_warn, error, warn};
@@ -254,6 +255,40 @@ pub fn check(config: &Config) -> Result {
)); ));
} }
match (&config.default_room_acl_allow, &config.default_room_acl_deny) {
| (Some(_), Some(_)) => {
return Err!(Config(
"default_room_acl_deny",
"Cannot provide a value for both default_room_acl_allow and \
default_room_acl_deny."
));
},
| (Some(allow), None) => {
if !RoomServerAclEventContent::new(true, allow.clone(), vec![])
.is_allowed(&config.server_name)
{
return Err!(Config(
"default_room_acl_allow",
"The default room Access Control List does not allow this server in the \
rooms it creates. Note that when using an allow list, servers are denied \
unless they match an allow value."
));
}
},
| (None, Some(deny)) => {
if !RoomServerAclEventContent::new(true, vec!["*".to_owned()], deny.clone())
.is_allowed(&config.server_name)
{
return Err!(Config(
"default_room_acl_deny",
"The default room Access Control List denies this server access to the \
rooms it creates."
));
}
},
| _ => (),
}
Ok(()) Ok(())
} }
+40 -25
View File
@@ -21,6 +21,7 @@ use regex::RegexSet;
use ruma::{ use ruma::{
OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, RoomVersionId, OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, RoomVersionId,
api::client::{discovery::discover_support::ContactRole, rtc::RtcTransport}, api::client::{discovery::discover_support::ContactRole, rtc::RtcTransport},
serde::Base64,
}; };
use serde::{Deserialize, Serialize, de::IgnoredAny}; use serde::{Deserialize, Serialize, de::IgnoredAny};
use url::Url; use url::Url;
@@ -475,20 +476,18 @@ pub struct Config {
#[serde(default = "default_federation_timeout")] #[serde(default = "default_federation_timeout")]
pub federation_timeout: u64, pub federation_timeout: u64,
/// MSC4284 Policy server request timeout (seconds). Generally policy /// Policy server request timeout (seconds). Generally policy
/// servers should respond near instantly, however may slow down under /// servers should respond near instantly, however may slow down under
/// load. If a policy server doesn't respond in a short amount of time, the /// load. If a policy server doesn't respond in a short amount of time, the
/// room it is configured in may become unusable if this limit is set too /// room it is configured in may become unusable if this limit is set too
/// high. 10 seconds is a good default, however dropping this to 3-5 seconds /// high. 30 seconds is a good default, however lower values may be
/// can be acceptable. /// acceptable if temporary send failures are an okay trade-off.
/// ///
/// Please be aware that policy requests are *NOT* currently re-tried, so if
/// a spam check request fails, the event will be assumed to be not spam,
/// which in some cases may result in spam being sent to or received from
/// the room that would typically be prevented.
/// ///
/// About policy servers: https://matrix.org/blog/2025/04/introducing-policy-servers/ /// About policy servers: https://matrix.org/blog/2025/04/introducing-policy-servers/
/// default: 10 /// (Stabilized in Matrix v1.18)
///
/// default: 30
#[serde(default = "default_policy_server_request_timeout")] #[serde(default = "default_policy_server_request_timeout")]
pub policy_server_request_timeout: u64, pub policy_server_request_timeout: u64,
@@ -760,6 +759,28 @@ pub struct Config {
#[serde(default = "default_default_room_version")] #[serde(default = "default_default_room_version")]
pub default_room_version: RoomVersionId, pub default_room_version: RoomVersionId,
/// A default allow value for the Access Control List when creating a room.
///
/// If a list is provided, new rooms will be created with
/// a m.room.server_acl event. Only servers which match one of the patterns
/// in the list will be permitted to participate in the room.
///
/// ACLs in existing rooms will not be updated automatically. This is not
/// a substitute for moderation bots.
pub default_room_acl_allow: Option<Vec<String>>,
/// A default deny value for the Access Control List when creating a room.
///
/// If a list is provided, new rooms will be created with
/// a m.room.server_acl event. Servers which match one of the patterns
/// in the list will be NOT permitted to participate in the room.
///
/// This config cannot be used if the default_room_acl_allow config is used.
///
/// ACLs in existing rooms will not be updated automatically. This is not
/// a substitute for moderation bots.
pub default_room_acl_deny: Option<Vec<String>>,
/// display: nested /// display: nested
#[serde(default)] #[serde(default)]
pub well_known: WellKnownConfig, pub well_known: WellKnownConfig,
@@ -1814,19 +1835,6 @@ pub struct Config {
#[serde(default)] #[serde(default)]
pub block_non_admin_invites: bool, pub block_non_admin_invites: bool,
/// Enable or disable making requests to MSC4284 Policy Servers.
/// It is recommended you keep this enabled unless you experience frequent
/// connectivity issues, such as in a restricted networking environment.
#[serde(default = "true_fn")]
pub enable_msc4284_policy_servers: bool,
/// Enable running locally generated events through configured MSC4284
/// policy servers. You may wish to disable this if your server is
/// single-user for a slight speed benefit in some rooms, but otherwise
/// should leave it enabled.
#[serde(default = "true_fn")]
pub policy_server_check_own_events: bool,
/// Allow admins to enter commands in rooms other than "#admins" (admin /// Allow admins to enter commands in rooms other than "#admins" (admin
/// room) by prefixing your message with "\!admin" or "\\!admin" followed up /// room) by prefixing your message with "\!admin" or "\\!admin" followed up
/// a normal continuwuity admin command. The reply will be publicly visible /// a normal continuwuity admin command. The reply will be publicly visible
@@ -2168,6 +2176,10 @@ pub struct WellKnownConfig {
/// Will be included alongside any contact information /// Will be included alongside any contact information
pub support_page: Option<Url>, pub support_page: Option<Url>,
/// The ed25519 public key for the policy server available at this server's
/// name. Must be unpadded base64.
pub policy_server_public_key: Option<Base64<ruma::serde::base64::Standard>>,
/// Role string for server support contacts, to be served as part of the /// Role string for server support contacts, to be served as part of the
/// MSC1929 server support endpoint at /.well-known/matrix/support. /// MSC1929 server support endpoint at /.well-known/matrix/support.
/// ///
@@ -2318,8 +2330,10 @@ pub struct SmtpConfig {
/// - `address@domain.org` to not use a name /// - `address@domain.org` to not use a name
pub sender: Mailbox, pub sender: Mailbox,
/// Whether to require that users provide an email address when they /// Whether to allow public registration with an email address.
/// register. ///
/// Note that, if this option is enabled, anyone will be able to register an
/// account with just an email address.
/// ///
/// If either this option or `require_email_for_token_registration` are set, /// If either this option or `require_email_for_token_registration` are set,
/// users will not be allowed to remove their email address. /// users will not be allowed to remove their email address.
@@ -2329,7 +2343,8 @@ pub struct SmtpConfig {
pub require_email_for_registration: bool, pub require_email_for_registration: bool,
/// Whether to require that users who register with a registration token /// Whether to require that users who register with a registration token
/// provide an email address. /// provide an email address. This option is independent of
/// `require_email_for_registration`.
/// ///
/// default: false /// default: false
#[serde(default)] #[serde(default)]
@@ -2512,7 +2527,7 @@ fn default_federation_conn_timeout() -> u64 { 10 }
fn default_federation_timeout() -> u64 { 60 } fn default_federation_timeout() -> u64 { 60 }
fn default_policy_server_request_timeout() -> u64 { 10 } fn default_policy_server_request_timeout() -> u64 { 30 }
fn default_federation_idle_timeout() -> u64 { 25 } fn default_federation_idle_timeout() -> u64 { 25 }
+17 -5
View File
@@ -6,7 +6,10 @@ mod serde;
use std::{any::Any, borrow::Cow, convert::Infallible, error::Error as _, sync::PoisonError}; use std::{any::Any, borrow::Cow, convert::Infallible, error::Error as _, sync::PoisonError};
use ruma::api::error::{ErrorKind, RetryAfter::Delay};
pub use self::{err::visit, log::*}; pub use self::{err::visit, log::*};
use crate::Error::BadRequest;
#[derive(thiserror::Error)] #[derive(thiserror::Error)]
pub enum Error { pub enum Error {
@@ -89,7 +92,7 @@ pub enum Error {
#[error("Arithmetic operation failed: {0}")] #[error("Arithmetic operation failed: {0}")]
Arithmetic(Cow<'static, str>), Arithmetic(Cow<'static, str>),
#[error("{0:?}: {1}")] #[error("{0:?}: {1}")]
BadRequest(ruma::api::error::ErrorKind, &'static str), //TODO: remove BadRequest(ErrorKind, &'static str), //TODO: remove
#[error("{0}")] #[error("{0}")]
BadServerResponse(Cow<'static, str>), BadServerResponse(Cow<'static, str>),
#[error(transparent)] #[error(transparent)]
@@ -117,7 +120,7 @@ pub enum Error {
#[error("from {0}: {1}")] #[error("from {0}: {1}")]
Redaction(ruma::OwnedServerName, ruma::canonical_json::RedactionError), Redaction(ruma::OwnedServerName, ruma::canonical_json::RedactionError),
#[error("{0:?}: {1}")] #[error("{0:?}: {1}")]
Request(ruma::api::error::ErrorKind, Cow<'static, str>, http::StatusCode), Request(ErrorKind, Cow<'static, str>, http::StatusCode),
#[error(transparent)] #[error(transparent)]
Ruma(#[from] ruma::api::error::Error), Ruma(#[from] ruma::api::error::Error),
#[error(transparent)] #[error(transparent)]
@@ -164,13 +167,13 @@ impl Error {
/// Returns the Matrix error code / error kind /// Returns the Matrix error code / error kind
#[inline] #[inline]
pub fn kind(&self) -> ruma::api::error::ErrorKind { pub fn kind(&self) -> ErrorKind {
use ruma::api::error::ErrorKind::{Unknown, Unrecognized}; use ruma::api::error::ErrorKind::{Unknown, Unrecognized};
match self { match self {
| Self::Federation(_, error) | Self::Ruma(error) => | Self::Federation(_, error) | Self::Ruma(error) =>
response::ruma_error_kind(error).clone(), response::ruma_error_kind(error).clone(),
| Self::BadRequest(kind, ..) | Self::Request(kind, ..) => kind.clone(), | BadRequest(kind, ..) | Self::Request(kind, ..) => kind.clone(),
| Self::FeatureDisabled(..) => Unrecognized, | Self::FeatureDisabled(..) => Unrecognized,
| _ => Unknown, | _ => Unknown,
} }
@@ -200,6 +203,15 @@ impl Error {
/// Result where Ok(None) is instead Err(e) if e.is_not_found(). /// Result where Ok(None) is instead Err(e) if e.is_not_found().
#[inline] #[inline]
pub fn is_not_found(&self) -> bool { self.status_code() == http::StatusCode::NOT_FOUND } pub fn is_not_found(&self) -> bool { self.status_code() == http::StatusCode::NOT_FOUND }
pub fn retry_after(&self) -> Option<std::time::Duration> {
if let BadRequest(ErrorKind::LimitExceeded(limit_data), ..) = self {
if let Some(Delay(after)) = limit_data.retry_after {
return Some(after);
}
}
None
}
} }
impl std::fmt::Debug for Error { impl std::fmt::Debug for Error {
@@ -260,7 +272,7 @@ impl std::fmt::Display for FormattedReqwestError {
write!(f, "{real_error}") write!(f, "{real_error}")
} }
} else { } else {
write!(f, "Request error: {}", &self.0) write!(f, "Request error: {}", self.0)
} }
} }
} }
+1
View File
@@ -42,5 +42,6 @@ pub fn unstable_features() -> BTreeMap<String, bool> {
("org.matrix.simplified_msc3575".to_owned(), true), /* Simplified Sliding sync (https://github.com/matrix-org/matrix-spec-proposals/pull/4186) */ ("org.matrix.simplified_msc3575".to_owned(), true), /* Simplified Sliding sync (https://github.com/matrix-org/matrix-spec-proposals/pull/4186) */
("uk.timedout.msc4323".to_owned(), true), /* agnostic suspend (https://github.com/matrix-org/matrix-spec-proposals/pull/4323) */ ("uk.timedout.msc4323".to_owned(), true), /* agnostic suspend (https://github.com/matrix-org/matrix-spec-proposals/pull/4323) */
("org.matrix.msc4155".to_owned(), true), /* invite filtering (https://github.com/matrix-org/matrix-spec-proposals/pull/4155) */ ("org.matrix.msc4155".to_owned(), true), /* invite filtering (https://github.com/matrix-org/matrix-spec-proposals/pull/4155) */
("computer.gingershaped.msc4466".to_owned(), true), /* profile change propagation (https://github.com/matrix-org/matrix-spec-proposals/pull/4466) */
]) ])
} }
+2 -2
View File
@@ -284,11 +284,11 @@ fn is_within_bounds() {
use utils::time::{TimeDirection, is_within_bounds}; use utils::time::{TimeDirection, is_within_bounds};
let now = SystemTime::now(); let now = SystemTime::now();
let yesterday = now - Duration::from_secs(86400); let yesterday = now - Duration::from_hours(24);
assert!(is_within_bounds(yesterday, now, TimeDirection::Before)); assert!(is_within_bounds(yesterday, now, TimeDirection::Before));
assert!(!is_within_bounds(yesterday, now, TimeDirection::After)); assert!(!is_within_bounds(yesterday, now, TimeDirection::After));
let tomorrow = now + Duration::from_secs(86400); let tomorrow = now + Duration::from_hours(24);
assert!(is_within_bounds(tomorrow, now, TimeDirection::After)); assert!(is_within_bounds(tomorrow, now, TimeDirection::After));
assert!(!is_within_bounds(tomorrow, now, TimeDirection::Before)); assert!(!is_within_bounds(tomorrow, now, TimeDirection::Before));
+5
View File
@@ -311,6 +311,11 @@ pub(super) static MAPS: &[Descriptor] = &[
key_size_hint: Some(48), key_size_hint: Some(48),
..descriptor::RANDOM_SMALL ..descriptor::RANDOM_SMALL
}, },
Descriptor {
name: "rejectedeventids",
key_size_hint: Some(48),
..descriptor::RANDOM_SMALL
},
Descriptor { Descriptor {
name: "statehash_shortstatehash", name: "statehash_shortstatehash",
val_size_hint: Some(8), val_size_hint: Some(8),
+1 -1
View File
@@ -44,7 +44,7 @@ impl crate::Service for Service {
db, db,
server: args.server.clone(), server: args.server.clone(),
bad_event_ratelimiter: Arc::new(SyncRwLock::new(HashMap::new())), bad_event_ratelimiter: Arc::new(SyncRwLock::new(HashMap::new())),
admin_alias: OwnedRoomAliasId::try_from(format!("#admins:{}", &args.server.name)) admin_alias: OwnedRoomAliasId::try_from(format!("#admins:{}", args.server.name))
.expect("#admins:server_name is valid alias name"), .expect("#admins:server_name is valid alias name"),
server_user: UserId::parse_with_server_name( server_user: UserId::parse_with_server_name(
String::from("conduit"), String::from("conduit"),
+1 -1
View File
@@ -37,7 +37,7 @@ pub struct PasswordReset<'a> {
} }
impl MessageTemplate for PasswordReset<'_> { impl MessageTemplate for PasswordReset<'_> {
fn subject(&self) -> String { format!("Password reset request for {}", &self.user_id) } fn subject(&self) -> String { format!("Password reset request for {}", self.user_id) }
} }
#[derive(Template)] #[derive(Template)]
+1 -1
View File
@@ -48,7 +48,7 @@ impl DatabaseTokenInfo {
impl std::fmt::Display for DatabaseTokenInfo { impl std::fmt::Display for DatabaseTokenInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Token created by {} and used {} times. ", &self.creator, self.uses)?; write!(f, "Token created by {} and used {} times. ", self.creator, self.uses)?;
if let Some(expires) = &self.expires { if let Some(expires) = &self.expires {
write!(f, "{expires}.")?; write!(f, "{expires}.")?;
} else { } else {
+1 -1
View File
@@ -35,7 +35,7 @@ pub struct ValidToken {
impl std::fmt::Display for ValidToken { impl std::fmt::Display for ValidToken {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "`{}` --- {}", self.token, &self.source) write!(f, "`{}` --- {}", self.token, self.source)
} }
} }
@@ -215,6 +215,17 @@ pub async fn handle_incoming_pdu<'a>(
.get_room_create_event(room_id) .get_room_create_event(room_id)
.await; .await;
let start_time = Instant::now();
self.federation_handletime
.write()
.insert(room_id.into(), (event_id.to_owned(), start_time));
defer! {{
self.federation_handletime
.write()
.remove(room_id);
}};
let (incoming_pdu, val) = self let (incoming_pdu, val) = self
.handle_outlier_pdu(origin, create_event, event_id, room_id, value, false) .handle_outlier_pdu(origin, create_event, event_id, room_id, value, false)
.await?; .await?;
@@ -281,17 +292,6 @@ pub async fn handle_incoming_pdu<'a>(
.await?; .await?;
// Done with prev events, now handling the incoming event // Done with prev events, now handling the incoming event
let start_time = Instant::now();
self.federation_handletime
.write()
.insert(room_id.into(), (event_id.to_owned(), start_time));
defer! {{
self.federation_handletime
.write()
.remove(room_id);
}};
self.upgrade_outlier_to_timeline_pdu(incoming_pdu, val, create_event, origin, room_id) self.upgrade_outlier_to_timeline_pdu(incoming_pdu, val, create_event, origin, room_id)
.boxed() .boxed()
.await .await
@@ -37,8 +37,6 @@ where
// 1. Remove unsigned field // 1. Remove unsigned field
value.remove("unsigned"); value.remove("unsigned");
// TODO: For RoomVersion6 we must check that Raw<..> is canonical do we anywhere?: https://matrix.org/docs/spec/rooms/v6#canonical-json
// 2. Check signatures, otherwise drop // 2. Check signatures, otherwise drop
// 3. check content hash, redact if doesn't match // 3. check content hash, redact if doesn't match
let room_version_rules = get_room_version_rules(create_event)?; let room_version_rules = get_room_version_rules(create_event)?;
@@ -90,6 +88,15 @@ where
let mut auth_events: HashMap<OwnedEventId, PduEvent> = HashMap::new(); let mut auth_events: HashMap<OwnedEventId, PduEvent> = HashMap::new();
for aid in pdu_event.auth_events() { for aid in pdu_event.auth_events() {
if self.services.pdu_metadata.is_event_rejected(aid).await {
debug_warn!(
"Rejecting incoming event {} which depends on rejected auth event {aid}",
event_id,
);
self.services.pdu_metadata.mark_event_rejected(event_id);
return Err!(Request(InvalidParam("Event has rejected auth event: {aid}")));
}
if let Ok(auth_event) = self.services.timeline.get_pdu(aid).await { if let Ok(auth_event) = self.services.timeline.get_pdu(aid).await {
check_room_id(room_id, &auth_event)?; check_room_id(room_id, &auth_event)?;
trace!("Found auth event {aid} for outlier event {event_id} locally"); trace!("Found auth event {aid} for outlier event {event_id} locally");
@@ -133,6 +140,8 @@ where
.filter(|id| !auth_events.contains_key(*id)) .filter(|id| !auth_events.contains_key(*id))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !still_missing.is_empty() { if !still_missing.is_empty() {
// Don't reject: this could be a temporary condition
// TODO: use get_missing_events?
return Err!(Request(InvalidParam( return Err!(Request(InvalidParam(
"Could not fetch all auth events for outlier event {event_id}, still missing: \ "Could not fetch all auth events for outlier event {event_id}, still missing: \
{still_missing:?}" {still_missing:?}"
@@ -163,6 +172,10 @@ where
v.insert(auth_event); v.insert(auth_event);
}, },
| hash_map::Entry::Occupied(_) => { | hash_map::Entry::Occupied(_) => {
self.services
.outlier
.add_pdu_outlier(pdu_event.event_id(), &incoming_pdu);
self.services.pdu_metadata.mark_event_rejected(event_id);
return Err!(Request(InvalidParam( return Err!(Request(InvalidParam(
"Auth event's type and state_key combination exists multiple times: {}, {}", "Auth event's type and state_key combination exists multiple times: {}, {}",
auth_event.kind, auth_event.kind,
@@ -177,6 +190,10 @@ where
auth_events_by_key.get(&(StateEventType::RoomCreate, String::new().into())), auth_events_by_key.get(&(StateEventType::RoomCreate, String::new().into())),
Some(_) | None Some(_) | None
) { ) {
self.services.pdu_metadata.mark_event_rejected(event_id);
self.services
.outlier
.add_pdu_outlier(pdu_event.event_id(), &incoming_pdu);
return Err!(Request(InvalidParam("Incoming event refers to wrong create event."))); return Err!(Request(InvalidParam("Incoming event refers to wrong create event.")));
} }
@@ -185,6 +202,7 @@ where
ready(auth_events_by_key.get(&key).map(ToOwned::to_owned)) ready(auth_events_by_key.get(&key).map(ToOwned::to_owned))
}; };
// PDU check: 3
let auth_check = state_res::event_auth::auth_check( let auth_check = state_res::event_auth::auth_check(
&room_version_rules, &room_version_rules,
&pdu_event, &pdu_event,
@@ -196,7 +214,13 @@ where
.map_err(|e| err!(Request(Forbidden("Auth check failed: {e:?}"))))?; .map_err(|e| err!(Request(Forbidden("Auth check failed: {e:?}"))))?;
if !auth_check { if !auth_check {
return Err!(Request(Forbidden("Auth check failed"))); self.services.pdu_metadata.mark_event_rejected(event_id);
self.services
.outlier
.add_pdu_outlier(pdu_event.event_id(), &incoming_pdu);
return Err!(Request(Forbidden(
"Event authorisation fails based on event's claimed auth events"
)));
} }
trace!("Validation successful."); trace!("Validation successful.");
+5
View File
@@ -19,6 +19,7 @@ use ruma::{
OwnedEventId, OwnedRoomId, RoomId, events::room::create::RoomCreateEventContent, OwnedEventId, OwnedRoomId, RoomId, events::room::create::RoomCreateEventContent,
room_version_rules::RoomVersionRules, room_version_rules::RoomVersionRules,
}; };
use tokio::sync::Notify;
use crate::{Dep, globals, rooms, sending, server_keys}; use crate::{Dep, globals, rooms, sending, server_keys};
@@ -26,6 +27,7 @@ pub struct Service {
pub mutex_federation: RoomMutexMap, pub mutex_federation: RoomMutexMap,
pub federation_handletime: SyncRwLock<HandleTimeMap>, pub federation_handletime: SyncRwLock<HandleTimeMap>,
services: Services, services: Services,
server_shutdown: Notify,
} }
struct Services { struct Services {
@@ -72,6 +74,7 @@ impl crate::Service for Service {
timeline: args.depend::<rooms::timeline::Service>("rooms::timeline"), timeline: args.depend::<rooms::timeline::Service>("rooms::timeline"),
server: args.server.clone(), server: args.server.clone(),
}, },
server_shutdown: Notify::new(),
})) }))
} }
@@ -86,6 +89,8 @@ impl crate::Service for Service {
} }
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) } fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
fn interrupt(&self) { self.server_shutdown.notify_waiters(); }
} }
impl Service { impl Service {
+340 -216
View File
@@ -3,193 +3,338 @@
//! This module implements a check against a room-specific policy server, as //! This module implements a check against a room-specific policy server, as
//! described in the relevant Matrix spec proposal (see: https://github.com/matrix-org/matrix-spec-proposals/pull/4284). //! described in the relevant Matrix spec proposal (see: https://github.com/matrix-org/matrix-spec-proposals/pull/4284).
use std::{collections::BTreeMap, sync::LazyLock, time::Duration}; use std::{collections::BTreeMap, time::Duration};
use conduwuit::{ use conduwuit::{
Err, Event, PduEvent, Result, debug, debug_error, debug_info, debug_warn, implement, trace, Err, Error, Event, PduEvent, Result, debug, debug_error, debug_info, debug_warn, error,
warn, implement, info, state_res::EventTypeExt, trace, utils::to_canonical_object, warn,
}; };
use http::StatusCode;
use ruma::{ use ruma::{
CanonicalJsonObject, CanonicalJsonValue, KeyId, OwnedKeyId, RoomId, ServerName, SigningKeyId, CanonicalJsonObject, CanonicalJsonValue, KeyId, RoomId, ServerName, SigningKeyAlgorithm,
events::StateEventType, api::{error::ErrorKind, federation::policy::sign_event},
}; canonical_json::redact,
use ruminuwuity::policy::{ events::{StateEventType, room::policy::RoomPolicyEventContent},
event::RoomPolicyEventContent, policy_check::unstable::Request as PolicyCheckRequest, room_version_rules::{RedactionRules, RoomVersionRules},
policy_sign::unstable::Request as PolicySignRequest, serde::{Base64, base64::Standard},
signatures::{to_canonical_json_string_for_signing, verify_canonical_json_bytes},
}; };
use serde_json::value::RawValue; use serde_json::value::RawValue;
use tokio::time::sleep;
static POLICY_EVENT_TYPE_UNSTABLE: LazyLock<StateEventType> = const POLICY_SERVER_KEY_ID_ED25519: &str = "ed25519:policy_server";
LazyLock::new(|| StateEventType::from("org.matrix.msc4284.policy"));
/// Asks a remote policy server if the event is allowed. /// Checks that the given policy server signed the event with the given public
/// key.
/// ///
/// If the event is the `org.matrix.msc4284.policy` configuration state event, /// Note: The caller MUST ensure event_id (and any other keys) are stripped
/// BEFORE passing to this function - no mutation is performed beyond redacting.
///
/// Parameters:
///
/// - via: The policy server that should've signed the event.
/// - ps_key: The public key part of the policy server's signing key.
/// - pdu_json: The raw PDU JSON object (will be cloned).
/// - redaction_rules: The redaction rules of the room version
///
/// Returns: `true` if the signature is present, `false` if it is not (including
/// if the signatures object is malformed).
pub(super) fn verify_policy_signature(
via: &ServerName,
ps_key: &Base64<Standard, Vec<u8>>,
pdu_json: &CanonicalJsonObject,
redaction_rules: &RedactionRules,
) -> bool {
#[cfg(debug_assertions)]
{
let pretty = serde_json::to_string(pdu_json).unwrap();
trace!(data=%pretty, "Preparing to check policy server signature");
};
let Some(canonical_json) = redact(pdu_json.clone(), redaction_rules, None)
.ok()
.and_then(|r| to_canonical_object(r).ok())
else {
debug_warn!("Failed to redact event");
return false;
};
let Some(signature) = extract_signature(pdu_json, via, POLICY_SERVER_KEY_ID_ED25519) else {
debug!("No (valid) policy server signature present on event");
return false;
};
trace!(%signature, "Verifying policy server signature");
let Ok(canonical_str) = to_canonical_json_string_for_signing(&canonical_json) else {
debug_warn!("Could not convert canonical json object into string");
return false;
};
verify_canonical_json_bytes(
&SigningKeyAlgorithm::Ed25519,
ps_key.as_bytes(),
signature.as_bytes(),
canonical_str.as_bytes(),
)
.inspect_err(|e| debug_error!("Policy server verification failed: {e}"))
.is_ok()
}
pub(super) fn extract_signature(
pdu_json: &CanonicalJsonObject,
server_name: &ServerName,
key_id: &str,
) -> Option<Base64<Standard, Vec<u8>>> {
pdu_json
.get("signatures")?
.as_object()?
.get(server_name.as_str())?
.as_object()?
.get(key_id)?
.as_str()
.and_then(|signature| Base64::<Standard>::parse(signature).ok())
}
/// Verifies the existing policy server signature, and/or fetches a new one
/// immediately.
///
/// If `incoming` is `true`, the event is checked for an existing signature. If
/// it has a valid one, `Ok` is returned. If it does not have a valid signature,
/// the function falls through to fetching a new one (which may be a soft-fail
/// in a future version).
///
/// If the event is the `m.room.policy` configuration state event,
/// this check is skipped. Similarly, if there is no policy server configured in /// this check is skipped. Similarly, if there is no policy server configured in
/// the PDU's room, or the configured server is not present in the room, the /// the PDU's room, the configuration event is malformed, or the configured
/// check is also skipped. /// server is not present in the room, the check is also skipped.
/// ///
/// If the policy server marks the event as spam, Ok(false) is returned, /// If the policy server marks the event as spam, the relevant error is
/// otherwise Ok(true) allows the event. If the policy server cannot be /// returned. Otherwise, the incoming PDU JSON is mutated to include the new
/// contacted for whatever reason, Err(e) is returned, which generally is a /// policy server signature. Transient errors such as rate-limits are handled,
/// fail-open operation. /// so any error returned by this function should be treated as final.
#[implement(super::Service)] #[implement(super::Service)]
#[tracing::instrument(skip(self, pdu, pdu_json, room_id), level = "info")] #[tracing::instrument(skip(self, pdu, pdu_json, room_version_rules), level = "info")]
pub async fn ask_policy_server( pub async fn policy_server_allows_event(
&self, &self,
pdu: &PduEvent, pdu: &PduEvent,
pdu_json: &mut CanonicalJsonObject, pdu_json: &mut CanonicalJsonObject,
room_id: &RoomId, room_id: &RoomId,
room_version_rules: &RoomVersionRules,
incoming: bool, incoming: bool,
) -> Result<bool> { ) -> Result<()> {
if !self.services.server.config.enable_msc4284_policy_servers { assert!(
trace!("policy server checking is disabled"); !pdu_json.contains_key("event_id"),
return Ok(true); // don't ever contact policy servers "event_id should be removed from the JSON before calling policy_server_allows_event"
);
if pdu.event_type().with_state_key("") == (StateEventType::RoomPolicy, "".into()) {
return Ok(());
} }
let ps = match self
if *pdu.event_type() == POLICY_EVENT_TYPE_UNSTABLE.clone().into() {
debug!(
room_id = %room_id,
event_type = ?pdu.event_type(),
"Skipping spam check for policy server meta-event"
);
return Ok(true);
}
let Ok(policyserver) = self
.services .services
.state_accessor .state_accessor
.room_state_get_content(room_id, &POLICY_EVENT_TYPE_UNSTABLE, "") .room_state_get_content::<RoomPolicyEventContent>(
room_id,
&StateEventType::RoomPolicy,
"",
)
.await .await
.inspect_err(|e| {
if !e.is_not_found() {
debug_error!("failed to load room policy server state event: {e}");
}
})
.map(|c: RoomPolicyEventContent| c)
else {
debug!("room has no policy server configured");
return Ok(true);
};
if self.services.server.config.policy_server_check_own_events
&& !incoming
&& policyserver.public_key.is_none()
{ {
// don't contact policy servers for locally generated events, but only when the | Ok(s) => s,
// policy server does not require signatures | Err(e) =>
trace!("won't contact policy server for locally generated event"); return if e.is_not_found() || e.kind() == ErrorKind::BadJson {
return Ok(true); debug!(%e, "no policy server configured");
} Ok(())
} else {
let via = match policyserver.via { Err!("failed to load m.room.policy state event: {e}")
| Some(ref via) => ServerName::parse(via)?, },
| None => {
trace!("No policy server configured for room {room_id}");
return Ok(true);
},
}; };
let Some(ps_key) = ps.public_keys.get(&SigningKeyAlgorithm::Ed25519) else {
debug!(
"room has a policy server configured, but no valid public keys; skipping spam check"
);
return Ok(());
};
if !self if !self
.services .services
.state_cache .state_cache
.server_in_room(&via, room_id) .server_in_room(&ps.via, room_id)
.await .await
{ {
debug!( debug!(
via = %via, via = %ps.via,
"Policy server is not in the room, skipping spam check" "Policy server is not in the room, skipping spam check"
); );
return Ok(true); return Ok(());
} }
if incoming {
if verify_policy_signature(&ps.via, ps_key, pdu_json, &room_version_rules.redaction) {
debug!(
via = %ps.via,
"Event is incoming and has a valid policy server signature"
);
return Ok(());
}
// N.B. In a future room version, this will be a soft failure specifically.
debug_info!(
via = %ps.via,
"Event is incoming but does not have a valid policy server signature; asking policy \
server to sign it now"
);
}
if ps.via == self.services.globals.server_name()
&& !self.services.server.config.federation_loopback
{
error!(
%ps.via,
%room_id,
"Cannot ask ourselves for a policy signature if `federation_loopback=false`",
);
return Ok(());
}
let outgoing = self let outgoing = self
.services .services
.sending .sending
.convert_to_outgoing_federation_event(pdu_json.clone()) .convert_to_outgoing_federation_event(pdu_json.clone())
.await; .await;
if policyserver.public_key.is_some() {
if !incoming { debug_info!(
%ps.via,
"Asking policy server to sign event"
);
self.fetch_policy_server_signature(pdu, pdu_json, &ps.via, outgoing, room_id, ps_key, 0)
.await?;
// Verify that the policy server signature was made with the same public key as
// is in the state event, not just that it was signed.
if verify_policy_signature(&ps.via, ps_key, pdu_json, &room_version_rules.redaction) {
Ok(())
} else {
Err(Error::Request(
ErrorKind::Unknown,
"Policy server signature was made with a different key to the one advertised".into(),
StatusCode::BAD_GATEWAY,
))
}
}
/// Handles an error returned by the policy server. If the error is one that
/// should be returned to the user, it is propagated, otherwise the request may
/// be retried (for example, when rate-limited).
#[allow(clippy::too_many_arguments)]
#[implement(super::Service)]
async fn handle_policy_server_error(
&self,
error: Error,
pdu: &PduEvent,
pdu_json: &mut CanonicalJsonObject,
via: &ServerName,
outgoing: Box<RawValue>,
room_id: &RoomId,
policy_server_key: &Base64<Standard, Vec<u8>>,
retries: u8,
timeout: Duration,
) -> Result<()> {
match error.status_code() {
| StatusCode::OK => unreachable!("ok response passed to handle_policy_server_error"),
| StatusCode::BAD_REQUEST => {
if matches!(error.kind(), ErrorKind::Forbidden) {
warn!(
via = %via,
event_id = %pdu.event_id(),
%room_id,
error = ?error,
"Policy server marked the event as spam"
);
return Err(error);
}
error!(
via = %via,
event_id = %pdu.event_id(),
%room_id,
error = ?error.to_string(),
"Policy server could not understand our request",
);
Err!(BadServerResponse("Error communicating with policy server"))
},
| StatusCode::FORBIDDEN => {
Err!(Request(Forbidden(
"Policy server refused to sign the event due to the room ACL"
)))
},
| StatusCode::NOT_FOUND => {
debug_info!( debug_info!(
via = %via, via = %via,
outgoing = ?pdu_json, event_id = %pdu.event_id(),
"Getting policy server signature on event" %room_id,
"Policy server is not actually a policy server or is not protecting this room: {}",
error.message()
); );
return self Ok(())
.fetch_policy_server_signature(pdu, pdu_json, &via, outgoing, room_id)
.await;
}
// for incoming events, is it signed by <via> with the key
// "ed25519:policy_server"?
if let Some(CanonicalJsonValue::Object(sigs)) = pdu_json.get("signatures") {
if let Some(CanonicalJsonValue::Object(server_sigs)) = sigs.get(via.as_str()) {
let wanted_key_id: OwnedKeyId<ruma::SigningKeyAlgorithm, ruma::Base64PublicKey> =
SigningKeyId::parse("ed25519:policy_server")?;
if let Some(CanonicalJsonValue::String(_sig_value)) =
server_sigs.get(wanted_key_id.as_str())
{
// TODO: verify signature
}
}
}
debug!(
"Event is not local and has no policy server signature, performing legacy spam check"
);
}
debug_info!(
via = %via,
"Checking event for spam with policy server via legacy check"
);
let mut request = PolicyCheckRequest::new(pdu.event_id().to_owned());
request.pdu = Some(outgoing);
let response = tokio::time::timeout(
Duration::from_secs(self.services.server.config.policy_server_request_timeout),
self.services.sending.send_federation_request(&via, request),
)
.await;
let response = match response {
| Ok(Ok(response)) => {
debug!("Response from policy server: {:?}", response);
response
}, },
| Ok(Err(e)) => { | StatusCode::TOO_MANY_REQUESTS => {
if let Some(retry_after) = error.retry_after() {
if retries >= 5 {
warn!(
via = %via,
event_id = %pdu.event_id(),
room_id = %room_id,
retries,
"Policy server rate-limited us too many times; giving up"
);
return Err(error); // Error should be passed to c2s
}
let saturated = retry_after.min(timeout);
// ^ don't wait more than 60 seconds
info!(
via = %via,
event_id = %pdu.event_id(),
room_id = %room_id,
retry_after = %saturated.as_secs(),
retries,
"Policy server rate-limited us; retrying after {retry_after:?}"
);
tokio::select! {
() = self.server_shutdown.notified() => (),
() = sleep(saturated) => (),
}
if !self.services.server.running() {
return Err(error);
}
return Box::pin(self.fetch_policy_server_signature(
pdu,
pdu_json,
via,
outgoing,
room_id,
policy_server_key,
retries.saturating_add(1),
))
.await;
}
warn!( warn!(
via = %via, via = %via,
event_id = %pdu.event_id(), event_id = %pdu.event_id(),
room_id = %room_id, room_id = %room_id,
"Failed to contact policy server: {e}" retries,
"Policy server rate-limited us without giving a retry window; giving up"
); );
// Network or policy server errors are treated as non-fatal: event is allowed by Err(error)
// default.
return Err(e);
}, },
| Err(elapsed) => { | _ => Err!(BadServerResponse(
warn!( "Unexpected response from policy server: {}/{:?}",
%via, error.status_code(),
event_id = %pdu.event_id(), error.kind()
%room_id, )),
%elapsed,
"Policy server request timed out after 10 seconds"
);
return Err!("Request to policy server timed out");
},
};
trace!("Recommendation from policy server was {}", response.recommendation);
if response.recommendation == "spam" {
warn!(
via = %via,
event_id = %pdu.event_id(),
room_id = %room_id,
"Event was marked as spam by policy server",
);
return Ok(false);
} }
Ok(true)
} }
/// Asks a remote policy server for a signature on this event. /// Asks a remote policy server for a signature on this event.
/// If the policy server signs this event, the original data is mutated. /// If the policy server signs this event, the original data is mutated.
/// Otherwise, the error is handled and potentially returned.
#[allow(clippy::too_many_arguments)]
#[implement(super::Service)] #[implement(super::Service)]
#[tracing::instrument(skip_all, fields(event_id=%pdu.event_id(), via=%via), level = "info")] #[tracing::instrument(skip_all, fields(event_id=%pdu.event_id(), via=%via), level = "info")]
pub async fn fetch_policy_server_signature( pub async fn fetch_policy_server_signature(
@@ -199,31 +344,36 @@ pub async fn fetch_policy_server_signature(
via: &ServerName, via: &ServerName,
outgoing: Box<RawValue>, outgoing: Box<RawValue>,
room_id: &RoomId, room_id: &RoomId,
) -> Result<bool> { policy_server_key: &Base64<Standard, Vec<u8>>,
retries: u8,
) -> Result<()> {
let timeout = Duration::from_secs(self.services.server.config.policy_server_request_timeout);
debug!("Requesting policy server signature"); debug!("Requesting policy server signature");
let response = tokio::time::timeout( let response = tokio::time::timeout(
Duration::from_secs(self.services.server.config.policy_server_request_timeout), timeout,
self.services self.services
.sending .sending
.send_federation_request(via, PolicySignRequest::new(outgoing)), .send_federation_request(via, sign_event::v1::Request::new(outgoing.clone())),
) )
.await; .await;
let response = match response { let response = match response {
| Ok(Ok(response)) => { | Ok(Ok(response)) => response,
debug!("Response from policy server: {:?}", response);
response
},
| Ok(Err(e)) => { | Ok(Err(e)) => {
warn!( debug_error!("Error from policy server: {:?}", e);
via = %via, return self
event_id = %pdu.event_id(), .handle_policy_server_error(
room_id = %room_id, e,
"Failed to contact policy server: {e}" pdu,
); pdu_json,
// Network or policy server errors are treated as non-fatal: event is allowed by via,
// default. outgoing,
return Err(e); room_id,
policy_server_key,
retries,
timeout,
)
.await;
}, },
| Err(elapsed) => { | Err(elapsed) => {
warn!( warn!(
@@ -231,76 +381,50 @@ pub async fn fetch_policy_server_signature(
event_id = %pdu.event_id(), event_id = %pdu.event_id(),
%room_id, %room_id,
%elapsed, %elapsed,
"Policy server request timed out after 10 seconds" "Policy server signature request timed out"
); );
return Err!("Request to policy server timed out"); return Err!(Request(Forbidden("Policy server did not respond in time")));
}, },
}; };
if response.signatures.is_none() {
debug!("Policy server refused to sign event"); let Some(signatures) = response.signatures.get(via) else {
return Ok(false); // NOTE: Legacy policy servers return a `200 {}` to indicate that the event was
// flagged as spam. We'll make a distinction in the error message in case
// it's unexpected.
return Err!(Request(Forbidden("Policy server did not sign the event")));
};
if response.signatures.len() > 1 {
warn!(?response.signatures, "Misbehaving policy server: returned signatures for extraneous servers");
// TODO: This should return an error but doesn't because some servers do
// this. It's safe for us to not explode for now because we only care
// about the signature for `via`, but ideally we'd want to enforce
// this more strictly in the future.
} }
let sigs: ruma::Signatures<ruma::OwnedServerName, ruma::ServerSigningKeyVersion> =
response.signatures.unwrap(); let Some(signature) = signatures
if !sigs.contains_key(via) { .get(&KeyId::parse(POLICY_SERVER_KEY_ID_ED25519).expect("policy server key ID is valid"))
debug_warn!( else {
"Policy server returned signatures, but did not include the expected server name \ return Err!(BadServerResponse(
'{}': {:?}", "Policy server did not return a signature with the expected key ID",
via, ));
sigs };
); if signatures.len() > 1 {
return Ok(false); info!(?signatures, "Misbehaving policy server: returned extraneous signatures");
} }
let keypairs = sigs.get(via).unwrap();
let wanted_key_id = KeyId::parse("ed25519:policy_server")?; pdu_json
if !keypairs.contains_key(&wanted_key_id) {
debug_warn!(
"Policy server returned signature, but did not use the key ID \
'ed25519:policy_server'."
);
return Ok(false);
}
let signatures_entry = pdu_json
.entry("signatures".to_owned()) .entry("signatures".to_owned())
.or_insert_with(|| CanonicalJsonValue::Object(BTreeMap::default())); .or_insert_with(|| CanonicalJsonValue::Object(BTreeMap::default()))
.as_object_mut()
if let CanonicalJsonValue::Object(signatures_map) = signatures_entry { .expect("`signatures` field must be an object")
let sig_value = keypairs.get(&wanted_key_id).unwrap().to_owned(); .entry(via.to_string())
.or_insert_with(|| CanonicalJsonValue::Object(BTreeMap::default()))
match signatures_map.get_mut(via.as_str()) { .as_object_mut()
| Some(CanonicalJsonValue::Object(inner_map)) => { .expect("`signatures[via]` field must be an object")
trace!("inserting PS signature: {}", sig_value); .insert(
inner_map.insert( POLICY_SERVER_KEY_ID_ED25519.to_owned(),
"ed25519:policy_server".to_owned(), CanonicalJsonValue::String(signature.clone()),
CanonicalJsonValue::String(sig_value),
);
},
| Some(_) => {
debug_warn!(
"Existing `signatures[{}]` field is not an object; cannot insert policy \
signature",
via
);
return Ok(false);
},
| None => {
let mut inner = BTreeMap::new();
inner.insert(
"ed25519:policy_server".to_owned(),
CanonicalJsonValue::String(sig_value.clone()),
);
trace!(
"created new signatures object for {via} with the signature {}",
sig_value
);
signatures_map.insert(via.as_str().to_owned(), CanonicalJsonValue::Object(inner));
},
}
} else {
debug_warn!(
"Existing `signatures` field is not an object; cannot insert policy signature"
); );
return Ok(false); debug_info!("Policy server allowed event");
} Ok(())
Ok(true)
} }
@@ -5,7 +5,7 @@ use std::{
}; };
use conduwuit::{ use conduwuit::{
Result, debug, err, implement, Result, debug, err, error, implement,
matrix::{Event, StateMap}, matrix::{Event, StateMap},
trace, trace,
utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt, TryWidebandExt}, utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt, TryWidebandExt},
@@ -121,6 +121,7 @@ where
.state_resolution(room_version_rules, fork_states.iter(), &auth_chain_sets) .state_resolution(room_version_rules, fork_states.iter(), &auth_chain_sets)
.boxed() .boxed()
.await .await
.inspect_err(|e| error!("State resolution failed: {e:?}"))
else { else {
return Ok(None); return Ok(None);
}; };
@@ -1,14 +1,20 @@
use std::{borrow::Borrow, collections::BTreeMap, iter::once, sync::Arc, time::Instant}; use std::{borrow::Borrow, sync::Arc, time::Instant};
use conduwuit::{ use conduwuit::{
Err, Result, debug, debug_info, err, implement, info, is_equal_to, Err, Result, debug, debug_info, err, implement, info, is_equal_to,
matrix::{Event, EventTypeExt, PduEvent, StateKey, state_res}, matrix::{Event, EventTypeExt, PduEvent, StateKey, state_res},
trace, trace,
utils::stream::{BroadbandExt, ReadyExt}, utils::{
IterStream,
stream::{BroadbandExt, ReadyExt},
},
warn, warn,
}; };
use futures::{FutureExt, StreamExt, future::ready}; use futures::{FutureExt, StreamExt, future::ready};
use ruma::{CanonicalJsonValue, RoomId, ServerName, events::StateEventType}; use ruma::{
CanonicalJsonObject, RoomId, ServerName, api::error::ErrorKind, events::StateEventType,
};
use tokio::join;
use super::get_room_version_rules; use super::get_room_version_rules;
use crate::rooms::{ use crate::rooms::{
@@ -20,7 +26,7 @@ use crate::rooms::{
pub(super) async fn upgrade_outlier_to_timeline_pdu<Pdu>( pub(super) async fn upgrade_outlier_to_timeline_pdu<Pdu>(
&self, &self,
incoming_pdu: PduEvent, incoming_pdu: PduEvent,
val: BTreeMap<String, CanonicalJsonValue>, mut val: CanonicalJsonObject,
create_event: &Pdu, create_event: &Pdu,
origin: &ServerName, origin: &ServerName,
room_id: &RoomId, room_id: &RoomId,
@@ -38,13 +44,18 @@ where
return Ok(Some(pduid)); return Ok(Some(pduid));
} }
if self let (rejected, soft_failed) = join!(
.services self.services
.pdu_metadata .pdu_metadata
.is_event_soft_failed(incoming_pdu.event_id()) .is_event_rejected(incoming_pdu.event_id()),
.await self.services
{ .pdu_metadata
return Err!(Request(InvalidParam("Event has been soft failed"))); .is_event_soft_failed(incoming_pdu.event_id())
);
if rejected {
return Err!(Request(InvalidParam("Event has been rejected")));
} else if soft_failed {
return Err!(Request(InvalidParam("Event has been soft-failed")));
} }
debug!( debug!(
@@ -95,6 +106,7 @@ where
event_id = %incoming_pdu.event_id, event_id = %incoming_pdu.event_id,
"Running initial auth check" "Running initial auth check"
); );
// PDU check: 5
let auth_check = state_res::event_auth::auth_check( let auth_check = state_res::event_auth::auth_check(
&room_version_rules, &room_version_rules,
&incoming_pdu, &incoming_pdu,
@@ -106,7 +118,12 @@ where
.map_err(|e| err!(Request(Forbidden("Auth check failed: {e:?}"))))?; .map_err(|e| err!(Request(Forbidden("Auth check failed: {e:?}"))))?;
if !auth_check { if !auth_check {
return Err!(Request(Forbidden("Event has failed auth check with state at the event."))); self.services
.pdu_metadata
.mark_event_rejected(incoming_pdu.event_id());
return Err!(Request(Forbidden(
"Event authorisation fails based on the state before the event"
)));
} }
debug!( debug!(
@@ -135,6 +152,7 @@ where
event_id = %incoming_pdu.event_id, event_id = %incoming_pdu.event_id,
"Running auth check with claimed state auth" "Running auth check with claimed state auth"
); );
// PDU check: 6
let auth_check = state_res::event_auth::auth_check( let auth_check = state_res::event_auth::auth_check(
&room_version_rules, &room_version_rules,
&incoming_pdu, &incoming_pdu,
@@ -144,6 +162,12 @@ where
) )
.await .await
.map_err(|e| err!(Request(Forbidden("Auth check failed: {e:?}"))))?; .map_err(|e| err!(Request(Forbidden("Auth check failed: {e:?}"))))?;
if !auth_check {
warn!(
event_id = %incoming_pdu.event_id,
"Event authorization fails based on the current state of the room"
);
}
// Soft fail check before doing state res // Soft fail check before doing state res
debug!( debug!(
@@ -153,16 +177,22 @@ where
let mut soft_fail = match (auth_check, incoming_pdu.redacts_id(&room_version_rules)) { let mut soft_fail = match (auth_check, incoming_pdu.redacts_id(&room_version_rules)) {
| (false, _) => true, | (false, _) => true,
| (true, None) => false, | (true, None) => false,
| (true, Some(redact_id)) => | (true, Some(redact_id)) => {
!self if !self
.services .services
.state_accessor .state_accessor
.user_can_redact(&redact_id, incoming_pdu.sender(), room_id, true) .user_can_redact(&redact_id, incoming_pdu.sender(), room_id, true)
.await?, .await?
{
warn!(redacts = %redact_id, "User is not allowed to redact event");
true
} else {
false
}
},
}; };
// 13. Use state resolution to find new room state // 13. Use state resolution to find new room state
// We start looking at current room state now, so lets lock the room // We start looking at current room state now, so lets lock the room
trace!( trace!(
room_id = %room_id, room_id = %room_id,
@@ -170,36 +200,6 @@ where
); );
let state_lock = self.services.state.mutex.lock(room_id).await; let state_lock = self.services.state.mutex.lock(room_id).await;
// Now we calculate the set of extremities this room has after the incoming
// event has been applied. We start with the previous extremities (aka leaves)
trace!("Calculating extremities");
let mut extremities: Vec<_> = self
.services
.state
.get_forward_extremities(room_id)
.ready_filter(|event_id| {
// Remove any that are referenced by this incoming event's prev_events
!incoming_pdu.prev_events().any(is_equal_to!(event_id))
})
.broad_filter_map(|event_id| async move {
// Only keep those extremities were not referenced yet
self.services
.pdu_metadata
.is_event_referenced(room_id, &event_id)
.await
.eq(&false)
.then_some(event_id)
})
.collect()
.await;
extremities.push(incoming_pdu.event_id().to_owned());
debug!(
"Retained {} extremities checked against {} prev_events",
extremities.len(),
incoming_pdu.prev_events().count()
);
let state_ids_compressed: Arc<CompressedState> = self let state_ids_compressed: Arc<CompressedState> = self
.services .services
.state_compressor .state_compressor
@@ -249,32 +249,38 @@ where
if !soft_fail { if !soft_fail {
// Don't call the below checks on events that have already soft-failed, there's // Don't call the below checks on events that have already soft-failed, there's
// no reason to re-calculate that. // no reason to re-calculate that.
// 14-pre. If the event is not a state event, ask the policy server about it // 14-pre. ask the policy server to sign the event, if possible
if incoming_pdu.state_key.is_none() { debug!(event_id = %incoming_pdu.event_id, "Checking policy server for event");
debug!(event_id = %incoming_pdu.event_id, "Checking policy server for event"); let tmp_evt_id = val.remove("event_id");
match self if let Err(e) = self
.ask_policy_server( .policy_server_allows_event(
&incoming_pdu, &incoming_pdu,
&mut incoming_pdu.to_canonical_object(), &mut val,
room_id, room_id,
true, &room_version_rules,
) true,
.await )
{ .await
| Ok(false) => { {
warn!( if matches!(e.kind(), ErrorKind::Forbidden) {
event_id = %incoming_pdu.event_id, info!(
"Event has been marked as spam by policy server" event_id = %incoming_pdu.event_id,
); error = %e,
soft_fail = true; "Event has been marked as spam by policy server: {}",
}, e.message(),
| _ => { );
debug!( soft_fail = true;
event_id = %incoming_pdu.event_id, } else {
"Event has passed policy server check or the policy server was unavailable." return Err(e);
);
},
} }
} else {
debug!(
event_id = %incoming_pdu.event_id,
"Event has passed policy server check."
);
}
if let Some(id) = tmp_evt_id {
val.insert("event_id".to_owned(), id);
} }
// Additionally, if this is a redaction for a soft-failed event, we soft-fail it // Additionally, if this is a redaction for a soft-failed event, we soft-fail it
@@ -294,60 +300,55 @@ where
.is_event_soft_failed(&redact_id) .is_event_soft_failed(&redact_id)
.await .await
{ {
warn!( info!(
redact_id = %redact_id, redact_id = %redact_id,
"Redaction is for a soft-failed event, soft failing the redaction" "Redaction is for a soft-failed event"
); );
soft_fail = true; soft_fail = true;
} }
} }
} }
// 14. Check if the event passes auth based on the "current state" of the room,
// if not soft fail it
if soft_fail {
info!(
event_id = %incoming_pdu.event_id,
"Soft failing event"
);
// assert!(extremities.is_empty(), "soft_fail extremities empty");
let extremities = extremities.iter().map(Borrow::borrow);
debug_assert!(extremities.clone().count() > 0, "extremities not empty");
self.services
.timeline
.append_incoming_pdu(
&incoming_pdu,
val,
extremities,
state_ids_compressed,
soft_fail,
&state_lock,
room_id,
)
.await?;
// Soft fail, we keep the event as an outlier but don't add it to the timeline
self.services
.pdu_metadata
.mark_event_soft_failed(incoming_pdu.event_id());
warn!(
event_id = %incoming_pdu.event_id,
"Event was soft failed"
);
return Err!(Request(InvalidParam("Event has been soft failed")));
}
// Now that the event has passed all auth it is added into the timeline.
// We use the `state_at_event` instead of `state_after` so we accurately
// represent the state for this event.
trace!("Appending pdu to timeline"); trace!("Appending pdu to timeline");
let extremities = extremities let mut extremities: Vec<_> = self
.iter() .services
.map(Borrow::borrow) .state
.chain(once(incoming_pdu.event_id())); .get_forward_extremities(room_id)
debug_assert!(extremities.clone().count() > 0, "extremities not empty"); .collect()
.await;
if !soft_fail {
// Per https://spec.matrix.org/unstable/server-server-api/#soft-failure, soft-failed events
// are not added as forward extremities.
// Now we calculate the set of extremities this room has after the incoming
// event has been applied. We start with the previous extremities (aka leaves)
trace!("Calculating extremities");
extremities = extremities
.into_iter()
.stream()
.ready_filter(|event_id| {
// Remove any that are referenced by this incoming event's prev_events
!incoming_pdu.prev_events().any(is_equal_to!(event_id))
})
.broad_filter_map(|event_id| async move {
// Only keep those extremities were not referenced yet
self.services
.pdu_metadata
.is_event_referenced(room_id, &event_id)
.await
.eq(&false)
.then_some(event_id)
})
.collect::<Vec<_>>()
.await;
extremities.push(incoming_pdu.event_id().to_owned());
debug!(
"Retained {} extremities checked against {} prev_events",
extremities.len(),
incoming_pdu.prev_events().count()
);
assert!(!extremities.is_empty(), "extremities must not empty");
}
let pdu_id = self let pdu_id = self
.services .services
@@ -355,20 +356,32 @@ where
.append_incoming_pdu( .append_incoming_pdu(
&incoming_pdu, &incoming_pdu,
val, val,
extremities, extremities.iter().map(Borrow::borrow),
state_ids_compressed, state_ids_compressed,
soft_fail, soft_fail,
&state_lock, &state_lock,
room_id, room_id,
) )
.await?; .await?;
if soft_fail {
self.services
.pdu_metadata
.mark_event_soft_failed(incoming_pdu.event_id());
info!(
elapsed = ?timer.elapsed(),
event_id = %incoming_pdu.event_id,
"Event was soft failed"
);
} else {
debug_info!(
elapsed = ?timer.elapsed(),
"Accepted",
);
}
// Event has passed all auth/stateres checks // Event has passed all auth/stateres checks
drop(state_lock); drop(state_lock);
debug_info!(
elapsed = ?timer.elapsed(),
"Accepted",
);
Ok(pdu_id) Ok(pdu_id)
} }
+7 -2
View File
@@ -34,7 +34,7 @@ use ruma::{
use crate::{ use crate::{
Dep, antispam, globals, Dep, antispam, globals,
rooms::{ rooms::{
metadata, outlier, short, metadata, outlier, pdu_metadata, short,
state::{self, RoomMutexGuard}, state::{self, RoomMutexGuard},
state_accessor, state_cache, state_accessor, state_cache,
state_compressor::{self, CompressedState, HashSetCompressStateEvent}, state_compressor::{self, CompressedState, HashSetCompressStateEvent},
@@ -54,6 +54,7 @@ struct Services {
globals: Dep<globals::Service>, globals: Dep<globals::Service>,
metadata: Dep<metadata::Service>, metadata: Dep<metadata::Service>,
outlier: Dep<outlier::Service>, outlier: Dep<outlier::Service>,
pdu_metadata: Dep<pdu_metadata::Service>,
sending: Dep<sending::Service>, sending: Dep<sending::Service>,
server_keys: Dep<server_keys::Service>, server_keys: Dep<server_keys::Service>,
short: Dep<short::Service>, short: Dep<short::Service>,
@@ -75,6 +76,7 @@ impl crate::Service for Service {
globals: args.depend::<globals::Service>("globals"), globals: args.depend::<globals::Service>("globals"),
metadata: args.depend::<metadata::Service>("rooms::metadata"), metadata: args.depend::<metadata::Service>("rooms::metadata"),
outlier: args.depend::<outlier::Service>("rooms::outlier"), outlier: args.depend::<outlier::Service>("rooms::outlier"),
pdu_metadata: args.depend::<pdu_metadata::Service>("rooms::pdu_metadata"),
sending: args.depend::<sending::Service>("sending"), sending: args.depend::<sending::Service>("sending"),
server_keys: args.depend::<server_keys::Service>("server_keys"), server_keys: args.depend::<server_keys::Service>("server_keys"),
short: args.depend::<short::Service>("rooms::short"), short: args.depend::<short::Service>("rooms::short"),
@@ -285,7 +287,7 @@ impl Service {
} }
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_remote_room", level = "info")] #[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_remote_room", level = "info")]
async fn join_remote_room( pub async fn join_remote_room(
&self, &self,
sender_user: &UserId, sender_user: &UserId,
room_id: &RoomId, room_id: &RoomId,
@@ -293,6 +295,7 @@ impl Service {
servers: &[OwnedServerName], servers: &[OwnedServerName],
state_lock: RoomMutexGuard, state_lock: RoomMutexGuard,
) -> Result { ) -> Result {
// public so the admin command force-join-room-remotely works
info!("Joining {room_id} over federation."); info!("Joining {room_id} over federation.");
let (make_join_response, remote_server) = self let (make_join_response, remote_server) = self
@@ -512,6 +515,7 @@ impl Service {
return state; return state;
} }
self.services.outlier.add_pdu_outlier(&event_id, &value); self.services.outlier.add_pdu_outlier(&event_id, &value);
self.services.pdu_metadata.clear_pdu_markers(&event_id);
if let Some(state_key) = &pdu.state_key { if let Some(state_key) = &pdu.state_key {
let shortstatekey = self let shortstatekey = self
.services .services
@@ -543,6 +547,7 @@ impl Service {
.ready_for_each(|(event_id, value)| { .ready_for_each(|(event_id, value)| {
trace!(%event_id, "Adding PDU as an outlier from send_join auth_chain"); trace!(%event_id, "Adding PDU as an outlier from send_join auth_chain");
self.services.outlier.add_pdu_outlier(&event_id, &value); self.services.outlier.add_pdu_outlier(&event_id, &value);
self.services.pdu_metadata.clear_pdu_markers(&event_id);
}) })
.await; .await;
+24
View File
@@ -26,6 +26,7 @@ pub(super) struct Data {
tofrom_relation: Arc<Map>, tofrom_relation: Arc<Map>,
referencedevents: Arc<Map>, referencedevents: Arc<Map>,
softfailedeventids: Arc<Map>, softfailedeventids: Arc<Map>,
rejectedeventids: Arc<Map>,
services: Services, services: Services,
} }
@@ -40,6 +41,7 @@ impl Data {
tofrom_relation: db["tofrom_relation"].clone(), tofrom_relation: db["tofrom_relation"].clone(),
referencedevents: db["referencedevents"].clone(), referencedevents: db["referencedevents"].clone(),
softfailedeventids: db["softfailedeventids"].clone(), softfailedeventids: db["softfailedeventids"].clone(),
rejectedeventids: db["rejectedeventids"].clone(),
services: Services { services: Services {
timeline: args.depend::<rooms::timeline::Service>("rooms::timeline"), timeline: args.depend::<rooms::timeline::Service>("rooms::timeline"),
}, },
@@ -118,7 +120,29 @@ impl Data {
self.softfailedeventids.insert(event_id, []); self.softfailedeventids.insert(event_id, []);
} }
pub(super) fn unmark_event_soft_failed(&self, event_id: &EventId) {
self.softfailedeventids.remove(event_id);
}
pub(super) async fn is_event_soft_failed(&self, event_id: &EventId) -> bool { pub(super) async fn is_event_soft_failed(&self, event_id: &EventId) -> bool {
self.softfailedeventids.get(event_id).await.is_ok() self.softfailedeventids.get(event_id).await.is_ok()
} }
pub(super) fn mark_event_rejected(&self, event_id: &EventId) {
self.rejectedeventids.insert(event_id, []);
}
pub(super) fn unmark_event_rejected(&self, event_id: &EventId) {
self.rejectedeventids.remove(event_id);
}
pub(super) async fn is_event_rejected(&self, event_id: &EventId) -> bool {
self.rejectedeventids.get(event_id).await.is_ok()
}
/// Removes any soft-fail or rejection markers applied to the target PDU
pub(super) fn clear_pdu_markers(&self, event_id: &EventId) {
self.unmark_event_rejected(event_id);
self.unmark_event_soft_failed(event_id);
}
} }
+24
View File
@@ -140,4 +140,28 @@ impl Service {
pub async fn is_event_soft_failed(&self, event_id: &EventId) -> bool { pub async fn is_event_soft_failed(&self, event_id: &EventId) -> bool {
self.db.is_event_soft_failed(event_id).await self.db.is_event_soft_failed(event_id).await
} }
pub async fn is_event_rejected(&self, event_id: &EventId) -> bool {
self.db.is_event_rejected(event_id).await
}
pub fn mark_event_rejected(&self, event_id: &EventId) {
self.db.mark_event_rejected(event_id);
}
pub fn unmark_event_soft_failed(&self, event_id: &EventId) {
self.db.unmark_event_soft_failed(event_id);
}
pub fn unmark_event_rejected(&self, event_id: &EventId) {
self.db.unmark_event_rejected(event_id);
}
/// Returns true if the event is neither soft-failed nor rejected.
pub async fn is_event_accepted(&self, event_id: &EventId) -> bool {
!self.db.is_event_rejected(event_id).await
&& !self.db.is_event_soft_failed(event_id).await
}
pub fn clear_pdu_markers(&self, event_id: &EventId) { self.db.clear_pdu_markers(event_id); }
} }
+1 -9
View File
@@ -53,15 +53,7 @@ where
.await?; .await?;
if soft_fail { if soft_fail {
self.services // Nothing else to do with a soft-failed event.
.pdu_metadata
.mark_as_referenced(room_id, pdu.prev_events.iter().map(AsRef::as_ref));
// self.services
// .state
// .set_forward_extremities(room_id, new_room_leaves, state_lock)
// .await;
return Ok(None); return Ok(None);
} }
+16 -19
View File
@@ -9,7 +9,6 @@ use conduwuit_core::{
state_res, state_res,
}, },
utils::{self, IterStream, ReadyExt, stream::TryIgnore}, utils::{self, IterStream, ReadyExt, stream::TryIgnore},
warn,
}; };
use futures::{StreamExt, TryStreamExt, future, future::ready}; use futures::{StreamExt, TryStreamExt, future, future::ready};
use ruma::{ use ruma::{
@@ -303,8 +302,6 @@ pub async fn create_hash_and_sign_event(
pdu.event_id = gen_event_id(&pdu_json, &room_version_rules)?; pdu.event_id = gen_event_id(&pdu_json, &room_version_rules)?;
pdu_json.insert("event_id".into(), CanonicalJsonValue::String(pdu.event_id.clone().into())); pdu_json.insert("event_id".into(), CanonicalJsonValue::String(pdu.event_id.clone().into()));
// Verify that the *full* PDU isn't over 64KiB. // Verify that the *full* PDU isn't over 64KiB.
// Ruma only validates that it's under 64KiB before signing and hashing.
// Has to be cloned to prevent mutating pdu_json itself :(
if !pdu_fits(&mut pdu_json.clone()) { if !pdu_fits(&mut pdu_json.clone()) {
// feckin huge PDU mate // feckin huge PDU mate
return Err!(Request(TooLarge("Message/PDU is too long (exceeds 65535 bytes)"))); return Err!(Request(TooLarge("Message/PDU is too long (exceeds 65535 bytes)")));
@@ -316,23 +313,23 @@ pub async fn create_hash_and_sign_event(
"Checking event in room {} with policy server", "Checking event in room {} with policy server",
pdu.room_id.as_ref().map_or("None", |id| id.as_str()) pdu.room_id.as_ref().map_or("None", |id| id.as_str())
); );
match self // We need to remove the event ID before getting a PS signature on the event.
.services // Note that we seemingly pointlessly add it above just to remove it here, but
// it's important to make sure the event ID isn't the field that makes the
// difference between an illegally-large event and one that is okay.
pdu_json.remove("event_id");
self.services
.event_handler .event_handler
.ask_policy_server(&pdu, &mut pdu_json, pdu.room_id().expect("has room ID"), false) .policy_server_allows_event(
.await &pdu,
{ &mut pdu_json,
| Ok(true) => {}, pdu.room_id().expect("has room ID"),
| Ok(false) => { &room_version_rules,
return Err!(Request(Forbidden(debug_warn!( false,
"Policy server marked this event as spam" )
)))); .await?;
}, pdu_json
| Err(e) => { .insert("event_id".into(), CanonicalJsonValue::String(pdu.event_id.clone().into()));
// fail open
warn!("Failed to check event with policy server: {e}");
},
}
} }
// Generate short event id // Generate short event id