Compare commits

...

120 Commits

Author SHA1 Message Date
Allinen d50e0f53d2 style: fixed EOF 2026-06-03 22:31:34 -04:00
Allinen 57e82f1783 fix: simplified comparison logic 2026-06-03 22:31:34 -04:00
Allinen 1d311073b9 feat: token expiration improvement 2026-06-03 22:31:34 -04:00
renovate c1486f425e chore(Nix): Updated flake hashes 2026-06-03 03:15:35 +00:00
Renovate Bot c80896dcb0 chore(deps): update rust to v1.96.0 2026-06-03 03:15:35 +00:00
Renovate Bot 77b12692bb chore(deps): update rust-non-major 2026-06-03 03:12:24 +00:00
Renovate Bot 57237e831a chore(deps): update rust-zerover-patch-updates 2026-06-03 03:11:53 +00:00
nex d62c48ebf7 chore: Update GitHub username in FUNDING.yml 2026-06-02 18:28:09 +00:00
timedout e2e85b962a style: Resolve lint failure 2026-06-02 16:35:32 +01:00
Helix K 788697d563 chore: News fragment 2026-06-02 16:35:32 +01:00
Helix K 64ecd762be chore: Remove MSC4373 support 2026-06-02 16:35:31 +01:00
Renovate Bot 5cb0db6f31 chore(deps): update github-actions-digest 2026-06-02 05:03:05 +00:00
Henry-Hiles 58e41d48c7 fix: grammar in delete all media command not found error 2026-06-01 13:40:44 -04:00
Renovate Bot 67466b015b chore(deps): update ghcr.io/renovatebot/renovate docker tag to v43.205.3 2026-06-01 05:03:05 +00:00
Renovate Bot 0ea68f27a2 chore(deps): update pre-commit hook crate-ci/typos to v1.47.0 2026-05-29 05:06:07 +00:00
Ginger a3e57dbab4 fix: Add unstable feature flag 2026-05-28 20:14:22 +00:00
Ginger 7ece15bb1a chore: News fragment 2026-05-28 20:14:22 +00:00
Ginger 336b32dead feat: Add support for MSC4466 2026-05-28 20:14:22 +00:00
timedout 1faa09b6ce fix: Don't ping presence for devices which claim to be offline
Fixes https://github.com/gomuks/gomuks/issues/722.

Reviewed-by: Ginger <ginger@gingershaped.computer>
2026-05-28 09:38:32 -04:00
Ginger d7a51c7107 refactor: Allow for client identities with no user ID 2026-05-27 12:15:16 -04:00
Ginger 30c9d6d2df chore: Clippy fixes 2026-05-26 18:26:02 +00:00
Ginger 74841b6711 refactor: Represent route auth information in the type system 2026-05-26 18:26:02 +00:00
timedout dabbdc7517 fix: Don't be so aggressive when validating policy server signatures 2026-05-26 16:16:48 +01: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
Renovate Bot 669efe092f chore(deps): update https://github.com/taiki-e/install-action digest to 184183c 2026-05-16 17:04:58 +00:00
31a05b9c 820485da57 fix: reliance on external constants being stable 2026-05-16 10:43:24 +00:00
Renovate Bot 466c98677c chore(deps): update rust-zerover-patch-updates 2026-05-15 05:04:30 +00:00
Renovate Bot 4d9cfc0afe chore(deps): update rust crate serde-saphyr to 0.0.26 2026-05-14 13:22:37 +00:00
Ginger ba2c123e82 feat: Remove support for server-side blurhashing 2026-05-14 13:22:17 +00:00
Renovate Bot 384ddc89d1 chore(deps): update https://github.com/softprops/action-gh-release action to v3 2026-05-13 23:13:38 +00:00
Renovate Bot a023d2d306 chore(deps): update https://github.com/cloudflare/wrangler-action action to v4 2026-05-13 23:13:13 +00:00
Renovate Bot 61b080d1ef chore(deps): update https://github.com/regclient/actions digest to c70ad64 2026-05-13 23:12:32 +00:00
Renovate Bot 00d7d4a54f chore(deps): update https://github.com/taiki-e/install-action digest to 3235f89 2026-05-13 23:10:50 +00:00
stratself c4a35e0f4d docs(livekit): Let users see troubleshoot command first for docker
Signed-off-by: stratself <stratself@noreply.forgejo.ellis.link>
2026-05-13 23:10:34 +00:00
stratself 86cb9b331a docs(dns): Rephrase some stuff
* Rename "Using a forwarder" to "Recursion vs forwarding"
* Un-highlight DoT and put it in the unbound.conf snippet
* Small wording improvements for Technitium section
* Highlights dns_cache_entries=0 recommendation above others
2026-05-13 23:10:34 +00:00
stratself 277f85f0b0 docs(contributing): Capitalise commit messages following prek formatting 2026-05-13 23:10:34 +00:00
stratself 497ec44c94 docs: Link to maintenance pages for Generic and Docker guides 2026-05-13 23:10:34 +00:00
stratself 7c837cc694 docs(docker): Add logcheck command and disable federation for quick run 2026-05-13 23:10:34 +00:00
stratself 98fb766bc2 docs(livekit): Fix curl command, remove console formats, other changes
* Add hint to use browser DevTools for browser clients
* Include docker-compose logs command in Testing section
2026-05-13 23:10:34 +00:00
Ginger 6f83925a4f fix: Use correct service name in membership service 2026-05-13 08:53:15 -04:00
153 changed files with 2900 additions and 2514 deletions
@@ -44,7 +44,7 @@ runs:
- name: Login to builtin registry
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
with:
registry: ${{ env.BUILTIN_REGISTRY }}
username: ${{ inputs.registry_user }}
@@ -52,7 +52,7 @@ runs:
- name: Set up Docker Buildx
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
with:
# Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125)
driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }}
@@ -61,7 +61,7 @@ runs:
- name: Extract metadata (tags) for Docker
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
id: meta
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6
with:
flavor: |
latest=auto
@@ -67,7 +67,7 @@ runs:
uses: ./.forgejo/actions/rust-toolchain
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
with:
# Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125)
driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }}
@@ -75,11 +75,11 @@ runs:
- name: Set up QEMU
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4
- name: Login to builtin registry
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
with:
registry: ${{ env.BUILTIN_REGISTRY }}
username: ${{ inputs.registry_user }}
@@ -87,7 +87,7 @@ runs:
- name: Extract metadata (labels, annotations) for Docker
id: meta
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6
with:
images: ${{ inputs.images }}
# default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509
@@ -17,7 +17,7 @@ inputs:
llvm-version:
description: 'LLVM version to install'
required: false
default: '20'
default: '21'
outputs:
llvm-version:
+1 -1
View File
@@ -71,7 +71,7 @@ runs:
- name: Install timelord-cli and git-warp-time
if: steps.check-binaries.outputs.need-install == 'true'
uses: https://github.com/taiki-e/install-action@b5fddbb5361bce8a06fb168c9d403a6cc552b084 # v2
uses: https://github.com/taiki-e/install-action@e49978b799e49ff429d162b7a30601a569ab6538 # v2
with:
tool: git-warp-time,timelord-cli@3.0.1
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
const labelsToAdd = new Set();
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');
}
if (file.startsWith('.forgejo/')) {
+2 -2
View File
@@ -56,7 +56,7 @@ jobs:
- name: Deploy to Cloudflare Pages (Production)
if: github.ref == 'refs/heads/main' && vars.CLOUDFLARE_PROJECT_NAME != ''
uses: https://github.com/cloudflare/wrangler-action@9acf94ace14e7dc412b076f2c5c20b8ce93c79cd # v3
uses: https://github.com/cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4
with:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
@@ -64,7 +64,7 @@ jobs:
- name: Deploy to Cloudflare Pages (Preview)
if: github.ref != 'refs/heads/main' && vars.CLOUDFLARE_PROJECT_NAME != ''
uses: https://github.com/cloudflare/wrangler-action@9acf94ace14e7dc412b076f2c5c20b8ce93c79cd # v3
uses: https://github.com/cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4
with:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+1 -1
View File
@@ -121,7 +121,7 @@ jobs:
- name: 🚀 Deploy to Cloudflare Pages
if: vars.CLOUDFLARE_PROJECT_NAME != ''
id: deploy
uses: https://github.com/cloudflare/wrangler-action@9acf94ace14e7dc412b076f2c5c20b8ce93c79cd # v3
uses: https://github.com/cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4
with:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+1 -1
View File
@@ -55,7 +55,7 @@ jobs:
# repositories: continuwuity
- name: Install regsync
uses: https://github.com/regclient/actions/regsync-installer@f3c6d87835906c175eb6ccfc18b348b69bb447e7 # main
uses: https://github.com/regclient/actions/regsync-installer@c70ad64367908075211b10dcd2ab9fad4bfa1816 # main
- name: Check what images need mirroring
run: |
+3 -3
View File
@@ -62,7 +62,7 @@ jobs:
registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
- name: Build and push Docker image by digest
id: build
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
with:
context: .
file: "docker/Dockerfile"
@@ -149,7 +149,7 @@ jobs:
registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
- name: Build and push max-perf Docker image by digest
id: build
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
with:
context: .
file: "docker/Dockerfile"
@@ -216,7 +216,7 @@ jobs:
path: binaries
merge-multiple: true
- name: Create Release and Upload
uses: https://github.com/softprops/action-gh-release@v2
uses: https://github.com/softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3
with:
draft: true
files: binaries/*
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
name: Renovate
runs-on: ubuntu-latest
container:
image: ghcr.io/renovatebot/renovate:43.140.0@sha256:61303c28b10a491c559529fb6f41745850e4755a43a54c04c3ae6848d6eaf5cc
image: ghcr.io/renovatebot/renovate:43.205.3@sha256:38fdc6d337a0f830131660aab4654ff2805828ca60b03fbfc89eb425bdcd560c
options: --tmpfs /tmp:exec
steps:
- name: Checkout
+1 -1
View File
@@ -1,4 +1,4 @@
github: [JadedBlueEyes, nexy7574, gingershaped]
github: [JadedBlueEyes, timedoutuk, gingershaped]
custom:
- https://timedout.uk/donate.html
- https://jade.ellis.link/sponsors
+1 -1
View File
@@ -24,7 +24,7 @@ repos:
- id: check-added-large-files
- repo: https://github.com/crate-ci/typos
rev: v1.46.1
rev: v1.47.0
hooks:
- id: typos
- id: typos
+3 -3
View File
@@ -137,9 +137,9 @@ ### Commit Messages
Examples:
```
feat: add user authentication
fix(database): resolve connection pooling issue
docs: update installation instructions
feat: Add user authentication
fix(database): Resolve connection pooling issue
docs: Update installation instructions
```
The project uses the `committed` hook to validate commit messages in pre-commit. This ensures all commits follow the conventional format.
Generated
+315 -620
View File
File diff suppressed because it is too large Load Diff
+12 -21
View File
@@ -39,10 +39,10 @@ features = ["ffi", "std", "union"]
version = "1.1.0"
[workspace.dependencies.ctor]
version = "0.13.0"
version = "1.0.6"
[workspace.dependencies.dtor]
version = "0.13.0"
version = "1.0.0"
[workspace.dependencies.cargo_toml]
version = "0.22"
@@ -164,7 +164,7 @@ features = ["raw_value"]
# Used for appservice registration files
[workspace.dependencies.serde-saphyr]
version = "0.0.25"
version = "0.0.26"
# Used to load forbidden room/user regex from config
[workspace.dependencies.serde_regex]
@@ -180,7 +180,7 @@ version = "0.5.3"
features = ["alloc", "rand"]
default-features = false
# Used to generate thumbnails for images & blurhashes
# Used to generate thumbnails for images
[workspace.dependencies.image]
version = "0.25.5"
default-features = false
@@ -191,14 +191,6 @@ features = [
"webp",
]
[workspace.dependencies.blurhash]
version = "0.2.3"
default-features = false
features = [
"fast-linear-to-srgb",
"image",
]
# logging
[workspace.dependencies.log]
version = "0.4.27"
@@ -352,7 +344,7 @@ version = "1.1.1"
[workspace.dependencies.ruma]
# version = "0.14.1"
git = "https://github.com/ruma/ruma.git"
rev = "9c9dccc93f054bbd28f23f630223fffa6289ecbc"
rev = "3ecd80b92794d2d93f657a7b3db62d4be237526b"
features = [
"appservice-api-c",
"client-api",
@@ -364,7 +356,6 @@ features = [
"ring-compat",
"compat-upload-signatures",
"compat-optional-txn-pdus",
"unstable-msc2448",
"unstable-msc2666",
"unstable-msc2867",
"unstable-msc2870",
@@ -382,18 +373,18 @@ features = [
"unstable-msc4195",
"unstable-msc4203",
"unstable-msc4310",
"unstable-msc4373",
"unstable-msc4380",
"unstable-msc4143",
"unstable-msc4293",
"unstable-msc4406",
"unstable-msc4439",
"unstable-msc4466",
"unstable-extensible-events",
]
[workspace.dependencies.rust-rocksdb]
git = "https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1"
rev = "31fb8f772c7afcdc0061ab6a40cfa3a1be2fccd9"
rev = "0a25ff92f7c09b55eec496b9c192c7d5136ab2b8"
default-features = false
features = [
"multi-threaded-cf",
@@ -413,20 +404,20 @@ default-features = false
# optional opentelemetry, performance measurements, flamegraphs, etc for performance measurements and monitoring
[workspace.dependencies.opentelemetry]
version = "0.31.0"
version = "0.32.0"
[workspace.dependencies.tracing-flame]
version = "0.2.0"
[workspace.dependencies.tracing-opentelemetry]
version = "0.32.0"
version = "0.33.0"
[workspace.dependencies.opentelemetry_sdk]
version = "0.31.0"
version = "0.32.0"
features = ["rt-tokio"]
[workspace.dependencies.opentelemetry-otlp]
version = "0.31.0"
version = "0.32.0"
features = ["http", "grpc-tonic", "trace", "logs", "metrics"]
@@ -543,7 +534,7 @@ version = "2.1.1"
features = ["std"]
[workspace.dependencies.minicbor-serde]
version = "0.6.0"
version = "0.7.0"
features = ["std"]
[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 @@
Devices which set their presence as "offline" will no longer be considered for presence updates. Contributed by @timedout.
+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 @@
Support for server-side blurhashing (part of MSC2448) has been removed.
+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.
+1
View File
@@ -0,0 +1 @@
Remove support for MSC4373, as the MSC is now closed. Contributed by @vel.
+40 -42
View File
@@ -372,21 +372,18 @@
#
#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
# 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
# high. 10 seconds is a good default, however dropping this to 3-5 seconds
# can be acceptable.
# high. 30 seconds is a good default, however lower values may be
# 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/
# (Stabilized in Matrix v1.18)
#
#policy_server_request_timeout = 10
#policy_server_request_timeout = 30
# Federation client idle connection pool timeout (seconds).
#
@@ -624,6 +621,30 @@
#
#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
# Jaeger exporter. Traces will be sent via OTLP to a collector (such as
# Jaeger) that supports the OpenTelemetry Protocol.
@@ -1570,19 +1591,6 @@
#
#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
# room) by prefixing your message with "\!admin" or "\\!admin" followed up
# a normal continuwuity admin command. The reply will be publicly visible
@@ -1849,6 +1857,11 @@
#
#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
# MSC1929 server support endpoint at /.well-known/matrix/support.
#
@@ -1874,24 +1887,6 @@
#
#support_pgp_key =
[global.blurhashing]
# blurhashing x component, 4 is recommended by https://blurha.sh/
#
#components_x = 4
# blurhashing y component, 3 is recommended by https://blurha.sh/
#
#components_y = 3
# Max raw size that the server will blurhash, this is the size of the
# image after converting it to raw data, it should be higher than the
# upload limit but not too high. The higher it is the higher the
# potential load will be for clients requesting blurhashes. The default
# is 33.55MB. Setting it to 0 disables blurhashing.
#
#blurhash_max_raw_size = 33554432
[global.matrix_rtc]
# A list of MatrixRTC foci (transports) which will be served via the
@@ -1977,8 +1972,10 @@
#
#sender =
# Whether to require that users provide an email address when they
# register.
# Whether to allow public registration with an email address.
#
# 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,
# users will not be allowed to remove their email address.
@@ -1986,6 +1983,7 @@
#require_email_for_registration = false
# 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
+3 -3
View File
@@ -1,5 +1,5 @@
ARG RUST_VERSION=1
ARG DEBIAN_VERSION=bookworm
ARG DEBIAN_VERSION=trixie
FROM --platform=$BUILDPLATFORM docker.io/tonistiigi/xx AS xx
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
# rustc -vV
ARG LLVM_VERSION=21
ARG LLVM_VERSION=22
# ENV RUSTUP_TOOLCHAIN=${RUST_VERSION}
# 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 \
apt-get update && apt-get install -y \
pkg-config make jq \
wget curl git software-properties-common \
wget curl git lsb-release gpg \
file
# golang cmake
+13 -9
View File
@@ -74,13 +74,11 @@ ## Unbound
- Increase `discard-timeout` to something like `4800` to wait longer for upstream resolvers, as recursion can take a long time to respond to some domains. Continuwuity default to `dns_timeout = 10` seconds, so dropping requests early would lead to unnecessary retries and/or failures.
### Using a forwarder (optional)
### Recursion versus forwarding
Unbound by default employs **recursive resolution** and contacts many servers around the world. If this is not performant enough, consider forwarding your queries to public resolvers to benefit from their CDNs and get faster responses.
Unbound by default employs **recursive resolution** and contacts many servers around the world. While this allows updated and authoritative answers and are generally viable for most users, sometimes these recursive queries can be too slow to fully resolve. As an alternative, you can consider **forwarding** your queries to public resolvers, and benefit from faster responses from their CDNs.
However, most popular upstreams (such as Google DNS or Quad9) employ IP ratelimiting, so a generous cache is still needed to avoid making too many queries.
DNS-over-TLS forwarders may also be used should you need on-the-wire encryption, but TLS overhead causes some speed penalties.
Do note that most popular upstreams (such as Google DNS or Quad9) employ IP ratelimiting, so a generous cache is still needed to avoid making too many queries.
If you want to use forwarders, configure it as follows:
@@ -99,6 +97,8 @@ # Use cloudflare public resolvers as an example
# forward-addr: 2606:4700:4700::1111@53
# alternatively, use DNS-over-TLS for forwarders.
# this will encrypt traffic between you and the forwarder,
# but takes more time due to TLS overhead.
# forward-zone:
# name: "."
# forward-tls-upstream: yes
@@ -133,9 +133,11 @@ ### dnsmasq
[arch-linux-dnsmasq]: https://wiki.archlinux.org/title/Dnsmasq
### Technitium
### Technitium DNS
[Technitium][technitium] supports recursion as well as a myriad of forwarding protocols, allows saving cache to disk natively, and does work well with Continuwuity. Its default configurations however ratelimits single-IP requests by a lot, and hence must be changed. You may consult this [community guide][technitium-continuwuity] for more details on setting up a dedicated Technitium for Continuwuity.
[Technitium DNS Server][technitium] supports recursion as well as a myriad of forwarding protocols, allows saving cache to disk natively, and does work well with Continuwuity. Its out-of-the-box configs however ratelimits single-IP requests by a lot, and hence must be changed.
You may consult this [community guide][technitium-continuwuity] for more details on setting up and fine-tuning a dedicated Technitium instance for Continuwuity.
[technitium]: https://github.com/TechnitiumSoftware/DnsServer
[technitium-continuwuity]: https://muoi.me/~stratself/articles/technitium-continuwuity/
@@ -150,11 +152,13 @@ ## Testing
## Further steps
- (Recommended) Set **`dns_cache_entries = 0`** inside Continuwuity and fully rely on the more performant external resolver.
It is recommended to set **`dns_cache_entries = 0`** inside Continuwuity to fully rely on the external resolver. While Continuwuity does have an internal cache, it can run into reliability issues if you're federating with many domains.
Additionally, you can also make the following improvements:
- Consider employing **persistent cache to disk**, so your resolver can still run without hassle after a restart. Unbound, via [Cache DB module][unbound-cachedb], can use Redis as a storage backend for this feature.
- Consider [enabling **Serve Stale**][unbound-serve-stale] functionality to serve expired data beyond DNS TTLs. Since most Matrix homeservers have static IPs, this should help improve federation with them especially when upstream resolvers have timed out. For dnsproxy, this corresponds to its [optimistic caching options][dnsproxy-usage].
- Consider [enabling **Serve Stale**][unbound-serve-stale] functionality to serve expired data beyond DNS TTLs. Since most Matrix homeservers have static IPs, this should still allow federating with them when upstream resolvers have timed out. For dnsproxy, this corresponds to its [optimistic caching options][dnsproxy-usage].
- If you still experience DNS performance issues, another step could be to **disable DNSSEC** (which is computationally expensive) at a cost of slightly decreased security. On Unbound this is done by commenting out `trust-anchors` config options and removing the `validator` module.
+19 -15
View File
@@ -25,9 +25,9 @@ ### 2. Services
:::tip Generating the secrets
LiveKit provides a utility to generate secure random keys
```bash
~$ docker run --rm livekit/livekit-server:latest generate-keys
API Key: APIUxUnMnSkuFWV
API Secret: t93ZVjPeoEdyx7Wbet3kG4L3NGZIZVEFvqe0UuiVc22A
docker run --rm livekit/livekit-server:latest generate-keys
# API Key: APIUxUnMnSkuFWV
# API Secret: t93ZVjPeoEdyx7Wbet3kG4L3NGZIZVEFvqe0UuiVc22A
```
:::
@@ -262,14 +262,14 @@ # livekit.yaml
## Testing
To test that LiveKit is successfully integrated with Continuwuity, you will need to replicate its [Token Exchange Flow](https://github.com/element-hq/lk-jwt-service#%EF%B8%8F-how-it-works--token-exchange-flow).
To test that LiveKit is successfully integrated with Continuwuity, you will need to replicate its [Token Exchange Flow](https://github.com/element-hq/lk-jwt-service#%EF%B8%8F-how-it-works--token-exchange-flow). Follow the steps below while checking Docker logs (`docker-compose logs --follow`), in order to help [troubleshooting](#troubleshooting) any issues.
First, you will need an access token for your current login session. These can be found in your client's settings or obtained via [this website](https://timedout.uk/mxtoken.html).
Then, using that token, fetch the discovery endpoints for MatrixRTC services
Then, using that token, fetch the discovery endpoints for MatrixRTC services:
```bash
curl -X POST -H "Authorization: Bearer <session-access-token>" \
curl -H "Authorization: Bearer <session-access-token>" \
https://matrix.example.com/_matrix/client/unstable/org.matrix.msc4143/rtc/transports
```
@@ -318,7 +318,7 @@ ## Testing
You can then send this payload to the lk-jwt-service:
```bash
~$ curl -X POST -d @payload.json https://livekit.example.com/get_token
curl -X POST -d @payload.json https://livekit.example.com/get_token
```
The lk-jwt-service will, after checking against Continuwuity, answer with a `jwt` token to create a LiveKit media room:
@@ -331,22 +331,31 @@ ## Testing
## Troubleshooting
To debug any issues, you can place a call or redo the Testing instructions, and check the container logs for any specific errors. Use `docker-compose logs --follow` to follow them in real-time.
To debug any issues, you can place a call or redo the Testing instructions, and check the container logs for any specific errors. Use `docker-compose logs --follow` to follow these logs in real-time.
### Common errors in Element Call UI
- `MISSING_MATRIX_RTC_FOCUS`: LiveKit is missing from Continuwuity's config file
- "Waiting for media" popup always showing: a LiveKit URL has been configured in Continuwuity, but your client cannot connect to it for some reason
For browser-based clients, you can also inspect connections using DevTools' Networking tab, to see which requests are erroring out.
### Docker loopback networking issues
Some distros do not allow Docker containers to connect to its host's public IP by default. This would cause `lk-jwt-service` to fail connecting to `livekit` or `continuwuity` on the same host. As a result, you would see connection refused/connection timeouts log entries in the JWT service, even when `LIVEKIT_URL` has been configured correctly.
You can also test that this is the case by cURLing from a sidecar container:
```bash
docker run --rm --net container:lk-jwt-service docker.io/curlimages/curl https://livekit.example.com
# --- some errors ---
```
To alleviate this, you can try one of the following workarounds:
- Use `network_mode: host` for the `lk-jwt-service` container (instead of the default bridge networking).
- Add an `extra_hosts` file mapping livekit's (and continuwuity's) domain name to a localhost address:
- Add an `extra_hosts` file mapping livekit's (and continuwuity's) domain name to a locally-reachable address:
```diff
# in docker-compose.yaml
@@ -360,12 +369,7 @@ ### Docker loopback networking issues
- (**untested, use at your own risk**) Implement an iptables workaround as shown [here](https://forums.docker.com/t/unable-to-connect-to-host-service-from-inside-docker-container/145749/6).
After implementing the changes and restarting your compose, you can test whether the connection works by cURLing from a sidecar container:
```bash
~$ docker run --rm --net container:lk-jwt-service docker.io/curlimages/curl https://livekit.example.com
OK
```
After implementing the changes and restarting your compose, `lk-jwt-service` should now connect to your other services. The sidecar container test above should now return an `OK` from LiveKit.
### Workaround for non-federating servers
+9 -6
View File
@@ -185,13 +185,15 @@ ## Testing
Test that your setup works by following these [instructions](./generic.mdx#how-do-i-know-it-works)
Check your container logs using `docker-compose logs --follow` to debug any issues. See the [Troubleshooting](../troubleshooting.mdx) page for common errors and how to fix them.
## Other deployment methods
### Docker - Quick Run
:::note For testing only
The instructions below are only meant for a quick demo of Continuwuity.
For production deployment, we recommend using [Docker Compose](#docker-compose)
:::warning For testing only
The instructions below are only meant for a quick demo of Continuwuity with **federation disabled**.
For production deployment, we recommend using [Docker Compose](#docker-compose).
:::
Get a working Continuwuity server with an admin user in four steps:
@@ -211,7 +213,7 @@ ### Docker - Quick Run
-e CONTINUWUITY_SERVER_NAME="example.com" \
-e CONTINUWUITY_DATABASE_PATH="/var/lib/continuwuity" \
-e CONTINUWUITY_ADDRESS="0.0.0.0" \
-e CONTINUWUITY_ALLOW_REGISTRATION="false" \
-e CONTINUWUITY_ALLOW_FEDERATION="false" \
--name continuwuity \
forgejo.ellis.link/continuwuation/continuwuity:latest \
/sbin/conduwuit
@@ -233,9 +235,9 @@ ### Docker - Quick Run
Pick your own username and password!
```
4. Configure your reverse proxy to forward HTTPS traffic to Continuwuity at port 8008. See [Docker Compose](#docker-compose) for examples.
4. Configure your reverse proxy to forward HTTPS traffic to Continuwuity at port 8008. See [Docker Compose](#docker-compose) or the [Generic instructions](./generic.mdx#setting-up-the-reverse-proxy) for examples.
Once configured, log in to your server with any Matrix client, and register for an account with the registration token from step 3. You'll automatically be invited to the admin room where you can [manage your server](../reference/admin).
Once configured, log in to your server with any Matrix client, and register for an account with the registration token from step 3. If you did not configure step 4., log in via the `http://<your_server_ip>:8008` address. You will be automatically invited to the admin room where you can [manage your server](../reference/admin).
### (Optional) Building Custom Images
@@ -269,4 +271,5 @@ ## Next steps
- For smooth federation, set up a caching resolver according to the [**DNS tuning guide**](../advanced/dns.mdx) (recommended)
- To set up Audio/Video communication, see the [**Calls**](../calls.mdx) page.
- Consult the [Maintenance](../maintenance.mdx) page for guidance on maintaining your homeserver.
- If you want to set up an appservice, take a look at the [**Appservice Guide**](../appservices.mdx).
+2 -1
View File
@@ -260,7 +260,7 @@ ## Starting Your Server
```
You can then open [a Matrix client][matrix-clients],
enter your homeserver address, and try to register with the provided token.
enter your homeserver address, and register with the provided token.
By default, the first user is the instance's first admin. They will be added
to the `#admin:example.com` room and be able to [issue admin commands](../reference/admin/index.md).
@@ -296,4 +296,5 @@ ## What's next?
- For smooth federation, set up a caching resolver according to the [**DNS tuning guide**](../advanced/dns.mdx) (recommended)
- For Audio/Video call functionality see the [**Calls**](../calls.md) page.
- Consult the [Maintenance](../maintenance.mdx) page for guidance on maintaining your homeserver.
- If you want to set up an appservice, take a look at the [**Appservice Guide**](../appservices.md).
+1 -1
View File
@@ -146,7 +146,7 @@ cargo clippy \
--locked \
--profile test \
--no-default-features \
--features=console,systemd,element_hacks,direct_tls,perf_measurements,brotli_compression,blurhashing \
--features=console,systemd,element_hacks,direct_tls,perf_measurements,brotli_compression \
--color=always \
-- \
-D warnings
Generated
+21 -21
View File
@@ -3,11 +3,11 @@
"advisory-db": {
"flake": false,
"locked": {
"lastModified": 1777645914,
"narHash": "sha256-P1T7QVQS13OvkXEuEhI91CLaQfyv6iqV9vW8IBLLDYg=",
"lastModified": 1779575509,
"narHash": "sha256-wXKYURZz76ZC5lbuDA1oVQA/MxSB3pSJ1raF1HG0oIc=",
"owner": "rustsec",
"repo": "advisory-db",
"rev": "d6ba1f7070ba91f45efe372d68eb648be67d0417",
"rev": "831c50f4a4304068f125e603add6a8839f08b3eb",
"type": "github"
},
"original": {
@@ -18,11 +18,11 @@
},
"crane": {
"locked": {
"lastModified": 1777335812,
"narHash": "sha256-bEg5xoAxAwsyfnGhkEX7RJViTIBIYPd8ISg4O1c0HFc=",
"lastModified": 1779130139,
"narHash": "sha256-BLrtr42azquO7MdGFU5a7KiMl3YpFlTeIXqy1fT5GlQ=",
"owner": "ipetkov",
"repo": "crane",
"rev": "5e0fb2f64edff2822249f21293b8304dedaaf676",
"rev": "edb38893982a3338972bb4a2ec7ce7c29ba10fd9",
"type": "github"
},
"original": {
@@ -39,11 +39,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1777624102,
"narHash": "sha256-thSyElkje577x/kAbP72nHlfiFc1a+tCudskLPHXe9s=",
"lastModified": 1779612045,
"narHash": "sha256-+7lfNVnmXJDkiRYHd5NoNwYoyUcc0LcXPaIJqjO7VWM=",
"owner": "nix-community",
"repo": "fenix",
"rev": "4d81601e0b73f20d81d066754ad0e7d1e7f75a06",
"rev": "d7be747f0a65af378de515fc3cee131bf99a008f",
"type": "github"
},
"original": {
@@ -74,11 +74,11 @@
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1775087534,
"narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
"lastModified": 1778716662,
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
"type": "github"
},
"original": {
@@ -89,11 +89,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1777268161,
"narHash": "sha256-bxrdOn8SCOv8tN4JbTF/TXq7kjo9ag4M+C8yzzIRYbE=",
"lastModified": 1779508470,
"narHash": "sha256-Ap9KJX+5xHIn3bPIpfNgT6MEXdAECECwo4/rmlQD74M=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "1c3fe55ad329cbcb28471bb30f05c9827f724c76",
"rev": "29916453413845e54a65b8a1cf996842300cd299",
"type": "github"
},
"original": {
@@ -105,11 +105,11 @@
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1774748309,
"narHash": "sha256-+U7gF3qxzwD5TZuANzZPeJTZRHS29OFQgkQ2kiTJBIQ=",
"lastModified": 1777168982,
"narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "333c4e0545a6da976206c74db8773a1645b5870a",
"rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14",
"type": "github"
},
"original": {
@@ -132,11 +132,11 @@
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1777583169,
"narHash": "sha256-dVJ4+wrRKc8oIgp3rLOFSq1obt/sCKlXy3h47qof/w0=",
"lastModified": 1779569060,
"narHash": "sha256-NSnk5D+3KEfRdbgPijs33N2RAKSG6A74SwfnynLcouo=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "aa64e4828a2bbba44463c1229a81c748d3cce583",
"rev": "987ea33645ab1c709b1df6823038abcb2fe8973e",
"type": "github"
},
"original": {
+7 -4
View File
@@ -5,11 +5,11 @@
liburing,
craneLib,
pkg-config,
callPackage,
rustPlatform,
cargoExtraArgs ? "",
rustflags ? "",
rocksdb ? callPackage ./rocksdb.nix { },
target_cpu ? null,
rocksdb,
profile ? "release",
}:
let
@@ -39,7 +39,10 @@ let
ROCKSDB_LIB_DIR = "${rocksdb}/lib";
CARGO_PROFILE = profile;
RUSTFLAGS = rustflags;
};
}
// (lib.optionalAttrs (target_cpu != null) {
TARGET_CPU = target_cpu;
});
};
in
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 = {
+8 -5
View File
@@ -15,6 +15,7 @@
rocksdb = pkgs.callPackage ./rocksdb.nix { };
default = pkgs.callPackage ./continuwuity.nix {
inherit self craneLib;
inherit (self'.packages) rocksdb;
# extra features via `cargoExtraArgs`
cargoExtraArgs = "-F http3";
# extra RUSTFLAGS via `rustflags`
@@ -22,11 +23,13 @@
rustflags = "--cfg reqwest_unstable";
};
# users may also override this with other cargo profiles to build for other feature sets
#
# other examples include:
#
# - release-high-perf
max-perf = self'.packages.default.override {
# for features configuration see `default` package which enables http3 by default
# example: different compilation profile and different target_cpu
max-perf-haswell = self'.packages.default.override {
# compiles explicitly for haswell arch cpus
target_cpu = "haswell";
# compiles slower but with more thorough optimizations
profile = "release-max-perf";
};
};
+7 -5
View File
@@ -1,5 +1,7 @@
{
stdenv,
# stdenv,
# enableJemalloc ? stdenv.hostPlatform.isLinux,
enableJemalloc ? false,
rocksdb,
fetchFromGitea,
rust-jemalloc-sys-unprefixed,
@@ -13,16 +15,16 @@
#
# [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17
jemalloc = rust-jemalloc-sys-unprefixed;
enableJemalloc = stdenv.hostPlatform.isLinux;
inherit enableJemalloc;
}).overrideAttrs
({
version = "continuwuity-v0.5.0-unstable-2026-03-27";
version = "continuwuity-v0.5.0-unstable-2026-05-19";
src = fetchFromGitea {
domain = "forgejo.ellis.link";
owner = "continuwuation";
repo = "rocksdb";
rev = "463f47afceebfe088f6922420265546bd237f249";
hash = "sha256-1ef75IDMs5Hba4VWEyXPJb02JyShy5k4gJfzGDhopRk=";
rev = "3756b2b905e13216d8b56bcc783d814e7b073aff";
hash = "sha256-rSv4fr2bf9JJwdodgeuPCuceeh7k97KVxrAOC0wyPQY=";
};
# 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";
# See also `rust-toolchain.toml`
sha256 = "sha256-sqSWJDUxc+zaz1nBWMAJKTAGBuGWP25GCftIOlCEAtA=";
sha256 = "sha256-mvUGEOHYJpn3ikC5hckneuGixaC+yGrkMM/liDIDgoU=";
};
in
{
+133 -133
View File
@@ -125,13 +125,13 @@
}
},
"node_modules/@rsbuild/core": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.0.5.tgz",
"integrity": "sha512-KajO50hbXb32S8MsyDh2f+xKcVeRy9Gfzdcy0JjpMLj22djHugly6jrGo7jH7ls9X6/TDcyCTncSuNK4+D2lTw==",
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.0.7.tgz",
"integrity": "sha512-LsBONEzsjzOAqO72ot39eI7g53zSfF9QuDXTu4ks8IUX+EZsxRSniQfc+1zVA6a6y3b9KUUtG96avoMLKbWklQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rspack/core": "~2.0.2",
"@rspack/core": "~2.0.4",
"@swc/helpers": "^0.5.21"
},
"bin": {
@@ -169,28 +169,28 @@
}
},
"node_modules/@rspack/binding": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.0.2.tgz",
"integrity": "sha512-0kZPplW9GWx8mfC6DfsaRY3QBIYPuUs42JfmSM6aSb8tMHZAXQeLeMB8M+h8i4SeI+aFtCgO6UuYGtyWf7+L+A==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.0.4.tgz",
"integrity": "sha512-/QeJDPUw/lWkBJESG264KA9u6/rAjvoJhKncU4rkTi5Ap45kue5HTgOzr0ufxKdd2Xl72fjFBuqlKmtFDD5LiQ==",
"dev": true,
"license": "MIT",
"optionalDependencies": {
"@rspack/binding-darwin-arm64": "2.0.2",
"@rspack/binding-darwin-x64": "2.0.2",
"@rspack/binding-linux-arm64-gnu": "2.0.2",
"@rspack/binding-linux-arm64-musl": "2.0.2",
"@rspack/binding-linux-x64-gnu": "2.0.2",
"@rspack/binding-linux-x64-musl": "2.0.2",
"@rspack/binding-wasm32-wasi": "2.0.2",
"@rspack/binding-win32-arm64-msvc": "2.0.2",
"@rspack/binding-win32-ia32-msvc": "2.0.2",
"@rspack/binding-win32-x64-msvc": "2.0.2"
"@rspack/binding-darwin-arm64": "2.0.4",
"@rspack/binding-darwin-x64": "2.0.4",
"@rspack/binding-linux-arm64-gnu": "2.0.4",
"@rspack/binding-linux-arm64-musl": "2.0.4",
"@rspack/binding-linux-x64-gnu": "2.0.4",
"@rspack/binding-linux-x64-musl": "2.0.4",
"@rspack/binding-wasm32-wasi": "2.0.4",
"@rspack/binding-win32-arm64-msvc": "2.0.4",
"@rspack/binding-win32-ia32-msvc": "2.0.4",
"@rspack/binding-win32-x64-msvc": "2.0.4"
}
},
"node_modules/@rspack/binding-darwin-arm64": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.0.2.tgz",
"integrity": "sha512-0o7lbgBBsDlICWdjIH0q3e0BsSco4GRiImHWVfZSVEG+q2+ykZJvSvYCVhPM1Co375Z0S3VMPa/8SjcY1FHwlw==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.0.4.tgz",
"integrity": "sha512-0Q1QXFEsZfDc4opiDnb8q50KlBbC2VovViDaYlMJZBzvjAo325mh3itXPfz7YZ31M+TxRE7TUiJXH3ltiV1Hdg==",
"cpu": [
"arm64"
],
@@ -202,9 +202,9 @@
]
},
"node_modules/@rspack/binding-darwin-x64": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.0.2.tgz",
"integrity": "sha512-tOwxZpoPlTlRs/w6UyUinXJ4TYRVHMlR7+eQxO1R3muKpixvhXQjtvoaY16HuFyTVky5F0IfOoWr3x9FEsgdLg==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.0.4.tgz",
"integrity": "sha512-oO5J2QYf7+H+aYRj85EiGrDOoDEE9EDDl7NgXv46iWvIF0wXowEHXqnjMFxHxRq2Vf6scT+0yYQX9blWcvMWAA==",
"cpu": [
"x64"
],
@@ -216,9 +216,9 @@
]
},
"node_modules/@rspack/binding-linux-arm64-gnu": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.0.2.tgz",
"integrity": "sha512-1ZD4YFhG1rmgqj+W8hfwHyKV8xDxGsc/3KgU0FwmiVEX7JfzhCkgBO/xlCG79kRKSrzuVzt4icO/G3cCKn0pag==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.0.4.tgz",
"integrity": "sha512-BEk6mIYBK4BihW9qXXITJORrVXecTlkRjrqhgefili4xjXtLdcUnxAm9sN/2oJ8m378n2h33qDh4gr2orPBFWQ==",
"cpu": [
"arm64"
],
@@ -233,9 +233,9 @@
]
},
"node_modules/@rspack/binding-linux-arm64-musl": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.0.2.tgz",
"integrity": "sha512-/PtTkM/DsDLjeuXTmeJeRfbjCDbcL9jvoVgZrgxYFZ28y2cdLvbChbW9uigOzs5dQEs1CIBQXMTTj7KhdBTuQg==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.0.4.tgz",
"integrity": "sha512-Hyt3z1RwNcSMIoaoWLN4Hb/696/O5JPukf8rXQASvf2UkC+X3ij7tr+8lMSYi3Zysi1QL375CnT4fNoABEW0JA==",
"cpu": [
"arm64"
],
@@ -250,9 +250,9 @@
]
},
"node_modules/@rspack/binding-linux-x64-gnu": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.0.2.tgz",
"integrity": "sha512-bBjsZxMHRaPo6X9SokApm6ucs+UhXtAJFyJJyuk2BH4XJsLeCU9Dz1vMwioeohFbJUUeTASVPm6/BL+RhSaunw==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.0.4.tgz",
"integrity": "sha512-xHorBPBZAg0Pn9Q0k9dWZ9euowieDxcSOzQ9JhTCmhDY6wZH5M/kCBFlCs/OQeW5/NUArW3x3MwEdO/0QJHMxg==",
"cpu": [
"x64"
],
@@ -267,9 +267,9 @@
]
},
"node_modules/@rspack/binding-linux-x64-musl": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.0.2.tgz",
"integrity": "sha512-HjlpInqzabDNkhVsUJpsHPqa9QYVWBViJoyWNjzXCAW0vKMDvwaphyUvokSinX8FGTlZi/sr5UEaHJo6XtQ35g==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.0.4.tgz",
"integrity": "sha512-QLxEGUXofF0kVNU12Y2NT3Qi9lGs+WbnYpapVeb+2IXtrAXJfU7Rhy7lAp5GLMzYMQNrKKL9SVnTWKbODbNW9Q==",
"cpu": [
"x64"
],
@@ -284,9 +284,9 @@
]
},
"node_modules/@rspack/binding-wasm32-wasi": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.0.2.tgz",
"integrity": "sha512-YaRYNFLJRpkGfYjSWR7n9f+nQKtrlmrrffpAn/blc2geHcRvXoBc5SCs1idPtsLhj7H9qWWhs7ucjyHy4csWFg==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.0.4.tgz",
"integrity": "sha512-YhN8HkiH46ONU9tm5dyoXDImDWGpU7E4SPqGI4OyAVF0445uIChurIUmTIOYcD6cg81GGeIjozWJOcb635Dcqw==",
"cpu": [
"wasm32"
],
@@ -300,9 +300,9 @@
}
},
"node_modules/@rspack/binding-win32-arm64-msvc": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.0.2.tgz",
"integrity": "sha512-d/3kTEKq+asLjRFPO96t+wfWiM7DLN76VQEPDD9bc1kdsZXlVJBuvyXfsgK8bbEvKplWXYcSsokhmEnuXrLOpg==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.0.4.tgz",
"integrity": "sha512-MUlYIz82xQRN0aoiXXyEBrNVUwiOSSFRi7nuCgUKduaSdlbPWzCY31IdtOygZ06LVp5JIGUEtyqSrjQq4FrMRw==",
"cpu": [
"arm64"
],
@@ -314,9 +314,9 @@
]
},
"node_modules/@rspack/binding-win32-ia32-msvc": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.0.2.tgz",
"integrity": "sha512-161cWineq3RW+Jdm1FAfSpXeUtYWvhB3kAbm46vNT9h/YYz+spwsFMvveAZ1nsVSVL0IC5lDBGUte7yUAY8K2g==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.0.4.tgz",
"integrity": "sha512-D7UcIFMzlY2yhhyuW4Ej15gBWmTwUM5DxuObl3Kv31qRv/pmV3MsqUeG5m2dqLbUxzqPH87qnp0cArbkJQ1b+w==",
"cpu": [
"ia32"
],
@@ -328,9 +328,9 @@
]
},
"node_modules/@rspack/binding-win32-x64-msvc": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.0.2.tgz",
"integrity": "sha512-y7Q0S1FE+OlkL5GMqLG0PwxrPw6E1r892KhGrGKE1Vdufe5YTEx6xTPxzZ+b7N2KPD7s9G1/iJmWHQxb1+Bjkg==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.0.4.tgz",
"integrity": "sha512-MnYKPfdrAEbtpKg/1SZ6cNtzreIRyQJK4APbxLLPXENdTH5QXQkaTjLMKEeJcJ51FRhI/+yNpOUm2oTHdCQ1Og==",
"cpu": [
"x64"
],
@@ -342,13 +342,13 @@
]
},
"node_modules/@rspack/core": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.0.2.tgz",
"integrity": "sha512-VM3UHOo26uC+4QSqY5tU1ybI7KuXY5rTof8nhFOaBY9SYau0Smvr+hMSAPmrmHwknB6dXT8yaNVxrj7I+qxE1Q==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.0.4.tgz",
"integrity": "sha512-OuxdQeeKWQpNvFBRDOcnoSaQvp6E4APM/6JJMM/k0p6oL1TEFQVGdNu3VGY4mRAsebnNBXapMVMhj+v66Bn2pg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rspack/binding": "2.0.2"
"@rspack/binding": "2.0.4"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
@@ -383,17 +383,17 @@
}
},
"node_modules/@rspress/core": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.11.tgz",
"integrity": "sha512-4YBOFmSMFv5GWrCa80qSIW8VxqZQQS/PknVq2r7Hb7kgfB38Fzciopn3hjb3hNwI4TTRbsi/Jev2HyRWD4bYAQ==",
"version": "2.0.13",
"resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.13.tgz",
"integrity": "sha512-lbaBA5eqh7wKdH98TUQEI+SfS3Z6YgaNCup3X+ttrYVLOrxN8PJvbedo6fFAcl+wP3XLy6D0pcnnzAgu8y3tdg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@mdx-js/mdx": "^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",
"@rspress/shared": "2.0.11",
"@rspress/shared": "2.0.13",
"@shikijs/rehype": "^4.0.2",
"@types/unist": "^3.0.3",
"@unhead/react": "^2.1.15",
@@ -411,8 +411,8 @@
"react-dom": "^19.2.6",
"react-lazy-with-preload": "^2.2.1",
"react-reconciler": "0.33.0",
"react-render-to-markdown": "19.0.1",
"react-router-dom": "^7.15.0",
"react-render-to-markdown": "19.1.0",
"react-router-dom": "^7.15.1",
"rehype-external-links": "^3.0.0",
"rehype-raw": "^7.0.0",
"remark-cjk-friendly": "^2.0.1",
@@ -436,9 +436,9 @@
}
},
"node_modules/@rspress/plugin-client-redirects": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.11.tgz",
"integrity": "sha512-DI9vod5mGccg57c19CuFpN3mGP1FEEueOUnEUz1UHXSyXg9YTj+ox7Xla4jUUzAzoPVGiWSSsfbtCTwdoxAsbg==",
"version": "2.0.13",
"resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.13.tgz",
"integrity": "sha512-dP753ASTvH6eDtSEulcqq2lE/kTSdOWSCw0nzvXG+7atTWTHDp6z47uH3CGD8E78cBuKyEi4OH+U7V0EtCTc0Q==",
"dev": true,
"license": "MIT",
"engines": {
@@ -449,9 +449,9 @@
}
},
"node_modules/@rspress/plugin-sitemap": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.11.tgz",
"integrity": "sha512-046LCHgbJXdaPipWB2SWMjZcAtIrOjXGZOD92xlTjhZ74D7Mk1Nod1MQdtOEoISWedcHdgpUVXMDbB1doKBpPQ==",
"version": "2.0.13",
"resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.13.tgz",
"integrity": "sha512-JtkNlxNuA7BzknKIrLvLQkSk0XVi7OXzrE76ma/cLvleccNWr8LGrHtrac4IrDr+HauK4WKTM2JaHGGHUdOUKw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -462,26 +462,26 @@
}
},
"node_modules/@rspress/shared": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.11.tgz",
"integrity": "sha512-7l5Pso4s597utJyisVEnd7n/40h053nfE8DwGQMeS8RLGtSwVgxFwNHsSrvQEGtFlLrg2aWWSITqnAVO1wfTew==",
"version": "2.0.13",
"resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.13.tgz",
"integrity": "sha512-LmDfr7+MDNWRBbxcNoWkW68A35oRonpDJq2Jlx3L8GCzG4sAsyd6Yw0DebTWAxx7hVOXGMf37nEf1J4aOLEZfg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rsbuild/core": "^2.0.5",
"@rsbuild/core": "^2.0.7",
"@shikijs/rehype": "^4.0.2",
"unified": "^11.0.5"
}
},
"node_modules/@shikijs/core": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.0.2.tgz",
"integrity": "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.1.0.tgz",
"integrity": "sha512-jLJtSJeuFffqX6/inRE1zqU5aFv2hrszvYgq3OjbAgFRZiWv7abKMDdQzYxuSDfmUPQozZvI/kuy6VMTvnvqTQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/primitive": "4.0.2",
"@shikijs/types": "4.0.2",
"@shikijs/primitive": "4.1.0",
"@shikijs/types": "4.1.0",
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4",
"hast-util-to-html": "^9.0.5"
@@ -491,28 +491,28 @@
}
},
"node_modules/@shikijs/engine-javascript": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.0.2.tgz",
"integrity": "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.1.0.tgz",
"integrity": "sha512-YquhawCUgaBfhsS72e2Y/dI59gCBNPHu3fEO/tvLaXrTssxZrY5ddjtNLTwndrMgPo8b3IscE+xoICDzpTmlFQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/types": "4.0.2",
"@shikijs/types": "4.1.0",
"@shikijs/vscode-textmate": "^10.0.2",
"oniguruma-to-es": "^4.3.4"
"oniguruma-to-es": "^4.3.6"
},
"engines": {
"node": ">=20"
}
},
"node_modules/@shikijs/engine-oniguruma": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.0.2.tgz",
"integrity": "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.1.0.tgz",
"integrity": "sha512-axLpjVs45YBvvINa+dJF+NPW+KtFkNXsFr4SDw2BMj9GdeMnGxVB9PQb2xXlJYovslt/nz6giedAyOANkfc7hg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/types": "4.0.2",
"@shikijs/types": "4.1.0",
"@shikijs/vscode-textmate": "^10.0.2"
},
"engines": {
@@ -520,26 +520,26 @@
}
},
"node_modules/@shikijs/langs": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.0.2.tgz",
"integrity": "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.1.0.tgz",
"integrity": "sha512-nwOMruEkbgdZfQ/b8CgpNBVOpvG1k0N5tbmgiFeqsan401+x3ILqlzZJowSla4Agmq4hG2Uf2wh5jLTEhR8VSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/types": "4.0.2"
"@shikijs/types": "4.1.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/@shikijs/primitive": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.0.2.tgz",
"integrity": "sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.1.0.tgz",
"integrity": "sha512-zx2/2Uwj2q9X3KSyYREEhXO23xBw5WUhP4orK2lE4r+t9JGITmEe0JH+wPmJhqHpOT2bRRs6lAL945+LDvOAGw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/types": "4.0.2",
"@shikijs/types": "4.1.0",
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4"
},
@@ -548,16 +548,16 @@
}
},
"node_modules/@shikijs/rehype": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-4.0.2.tgz",
"integrity": "sha512-cmPlKLD8JeojasNFoY64162ScpEdEdQUMuVodPCrv1nx1z3bjmGwoKWDruQWa/ejSznImlaeB0Ty6Q3zPaVQAA==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-4.1.0.tgz",
"integrity": "sha512-HQwltCcO2/UiFz44/8whyji4rP1VghLu++MgvQn+lQA8/gvuycGkay8DH8o8VAOvLBDKGOkBEw7cC1Cm33GObQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/types": "4.0.2",
"@shikijs/types": "4.1.0",
"@types/hast": "^3.0.4",
"hast-util-to-string": "^3.0.1",
"shiki": "4.0.2",
"shiki": "4.1.0",
"unified": "^11.0.5",
"unist-util-visit": "^5.1.0"
},
@@ -566,22 +566,22 @@
}
},
"node_modules/@shikijs/themes": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.0.2.tgz",
"integrity": "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.1.0.tgz",
"integrity": "sha512-emCcTnUM7yO2wltYbaxm+yLvcCI4+h8XBKc4KmJ7EZUXoSGjcCHifkI//R4OFit9ewpg7H2/9tjOuXrT2v/Knw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/types": "4.0.2"
"@shikijs/types": "4.1.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/@shikijs/types": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.0.2.tgz",
"integrity": "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.1.0.tgz",
"integrity": "sha512-3EQWX54fMpniOrDblzAhiwiJwpiTMW6+B9DWyUd9ska483tbayFYuw47UxwuPknI31bKnySfVQ/QW+jFL4rFdA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -631,9 +631,9 @@
}
},
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
"dev": true,
"license": "MIT"
},
@@ -682,9 +682,9 @@
"license": "MIT"
},
"node_modules/@types/react": {
"version": "19.2.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"version": "19.2.15",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz",
"integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -700,9 +700,9 @@
"license": "MIT"
},
"node_modules/@ungap/structured-clone": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz",
"integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==",
"dev": true,
"license": "ISC"
},
@@ -1150,9 +1150,9 @@
"license": "Apache-2.0"
},
"node_modules/get-east-asian-width": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz",
"integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==",
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
"integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2809,9 +2809,9 @@
}
},
"node_modules/react-render-to-markdown": {
"version": "19.0.1",
"resolved": "https://registry.npmjs.org/react-render-to-markdown/-/react-render-to-markdown-19.0.1.tgz",
"integrity": "sha512-BPv48o+ubcu2JyUDIktvJXFqLIZqR7hA4mvGu1eFIofz9fogT2me9UvXwRvqvGs9jEtNaJkxZIUKUX0oiK4hDA==",
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react-render-to-markdown/-/react-render-to-markdown-19.1.0.tgz",
"integrity": "sha512-dF9b3tO41ezqdmHP8X92kbHbMexJ6iC7iHw4ykC8fwiO7DgpFc9PhMoKlI+BcPzRxGcWgQSdrixVB9RykhjJpQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2822,9 +2822,9 @@
}
},
"node_modules/react-router": {
"version": "7.15.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.0.tgz",
"integrity": "sha512-HW9vYwuM8f4yx66Izy8xfrzCM+SBJluoZcCbww9A1TySax11S5Vgw6fi3ZjMONw9J4gQwngL7PzkyIpJJpJ7RQ==",
"version": "7.15.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.1.tgz",
"integrity": "sha512-R8rl9HhgikFYoPJymnUtPXWbnDb3oget6lQnfIoupbt61aT9aOhRkDsY2XRhZRyX1Z/8a5sL74fXmFNm3NRK5A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2845,13 +2845,13 @@
}
},
"node_modules/react-router-dom": {
"version": "7.15.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.15.0.tgz",
"integrity": "sha512-VcrVg64Fo8nwBvDscajG8gRTLIuTC6N50nb22l2HOOV4PTOHgoGp8mUjy9wLiHYoYTSYI36tUnXZgasSRFZorQ==",
"version": "7.15.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.15.1.tgz",
"integrity": "sha512-AzF62gjY6U9rkMq4RfP/r2EVtQ7DMfNMjyOp/flLTCrtRylLiK4wT4pSq6O8rOXZ2eXdZYJPEYe+ifomiv+Igg==",
"dev": true,
"license": "MIT",
"dependencies": {
"react-router": "7.15.0"
"react-router": "7.15.1"
},
"engines": {
"node": ">=20.0.0"
@@ -3164,18 +3164,18 @@
"license": "MIT"
},
"node_modules/shiki": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-4.0.2.tgz",
"integrity": "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-4.1.0.tgz",
"integrity": "sha512-l/ABZPUR5v70jI10EzqfMS/I96vjSGv2y0ihUV+WYFzv0EfvW4s54m0Lg8wCrrL+2IkwBzFTuxkZjPf8b2NX9Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/core": "4.0.2",
"@shikijs/engine-javascript": "4.0.2",
"@shikijs/engine-oniguruma": "4.0.2",
"@shikijs/langs": "4.0.2",
"@shikijs/themes": "4.0.2",
"@shikijs/types": "4.0.2",
"@shikijs/core": "4.1.0",
"@shikijs/engine-javascript": "4.1.0",
"@shikijs/engine-oniguruma": "4.1.0",
"@shikijs/langs": "4.1.0",
"@shikijs/themes": "4.1.0",
"@shikijs/types": "4.1.0",
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4"
},
+19 -3
View File
@@ -5,7 +5,7 @@
"osvVulnerabilityAlerts": true,
"lockFileMaintenance": {
"enabled": true,
"schedule": ["at any time"]
"schedule": ["* * * * 0,6"]
},
"platformAutomerge": true,
"nix": {
@@ -66,6 +66,17 @@
"matchUpdateTypes": ["minor", "patch"],
"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",
"matchManagers": ["npm"],
@@ -83,7 +94,10 @@
"matchPackageNames": ["crate-ci/typos"],
"matchUpdateTypes": ["minor", "patch"],
"automerge": true,
"automergeStrategy": "fast-forward"
"automergeStrategy": "fast-forward",
"schedule": [
"* 0-7 * * 3"
]
},
{
"description": "Auto-merge renovatebot docker image updates",
@@ -91,7 +105,9 @@
"matchPackageNames": ["ghcr.io/renovatebot/renovate"],
"automerge": true,
"automergeStrategy": "fast-forward",
"extends": ["schedule:earlyMondays"]
"schedule": [
"* 0-7 * * 1"
]
}
],
"customManagers": [
+1 -1
View File
@@ -10,7 +10,7 @@
[toolchain]
profile = "minimal"
channel = "1.92.0"
channel = "1.96.0"
components = [
# For rust-analyzer
"rust-src",
+229 -9
View File
@@ -1,5 +1,5 @@
use std::{
collections::HashMap,
collections::{HashMap, HashSet},
fmt::Write,
iter::once,
time::{Instant, SystemTime},
@@ -22,7 +22,7 @@
use lettre::message::Mailbox;
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId,
OwnedRoomOrAliasId, OwnedServerName, RoomId, RoomVersionId,
OwnedRoomOrAliasId, OwnedServerName, RoomId, RoomVersionId, UInt,
api::federation::event::get_room_state, events::AnyStateEvent, serde::Raw,
};
use service::rooms::{
@@ -69,6 +69,205 @@ pub(super) async fn get_auth_chain(&self, event_id: OwnedEventId) -> Result {
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]
pub(super) async fn parse_pdu(&self) -> Result {
if self.body.len() < 2
@@ -111,15 +310,31 @@ pub(super) async fn get_pdu(&self, event_id: OwnedEventId) -> Result {
outlier = true;
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 {
| Err(_) => return Err!("PDU not found locally."),
| Ok(json) => {
let text = serde_json::to_string_pretty(&json)?;
let msg = if outlier {
"Outlier (Rejected / Soft Failed) PDU found in our database"
let msg = if rejected {
"Rejected PDU:"
} else if soft_failed {
"Soft-failed PDU:"
} else if outlier {
"Outlier PDU:"
} else {
"PDU found in our database"
"PDU:"
};
write!(self, "{msg}\n```json\n{text}\n```")
},
@@ -614,6 +829,10 @@ pub(super) async fn force_set_room_state_from_server(
.await;
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
.outlier
.add_pdu_outlier(&event_id, &value);
self.services
.rooms
.pdu_metadata
.clear_pdu_markers(&event_id);
}
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)
.await?;
info!(
"Updating joined counts for room just in case (e.g. we may have found a difference in \
the room's m.room.member state"
);
info!("Updating joined counts for room");
self.services
.rooms
.state_cache
+10 -1
View File
@@ -17,12 +17,21 @@ pub enum DebugCommand {
message: Vec<String>,
},
/// Get the auth_chain of a PDU
/// Loads the auth_chain of a PDU, reporting how long it took.
GetAuthChain {
/// An event ID (the $ character followed by the base64 reference hash)
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
///
/// The PDU event is only checked for validity and is not added to the
+14 -8
View File
@@ -6,18 +6,24 @@
#[admin_command]
pub(super) async fn issue_token(&self, expires: super::TokenExpires) -> Result {
let expires = {
if expires.immortal {
None
} else if let Some(max_uses) = expires.max_uses {
Some(TokenExpires::AfterUses(max_uses))
} else if expires.once {
Some(TokenExpires::AfterUses(1))
} else if let Some(max_age) = expires
let max_uses = expires.max_uses;
let max_age = expires
.max_age
.as_deref()
.map(|max_age| utils::time::timepoint_from_now(utils::time::parse_duration(max_age)?))
.transpose()?
.transpose()?;
if expires.immortal {
None
} else if let Some(max_uses) = max_uses
&& let Some(max_age) = max_age
{
Some(TokenExpires::AfterTimeOrUses { max_uses, max_age })
} else if let Some(max_uses) = max_uses {
Some(TokenExpires::AfterUses(max_uses))
} else if expires.once {
Some(TokenExpires::AfterUses(1))
} else if let Some(max_age) = max_age {
Some(TokenExpires::AfterTime(max_age))
} else {
unreachable!();
+5 -5
View File
@@ -29,23 +29,23 @@ pub enum TokenCommand {
}
#[derive(Debug, Args)]
#[group(required = true, multiple = false)]
#[group(required = true, multiple = true)]
pub struct TokenExpires {
/// The maximum number of times this token is allowed to be used before it
/// expires.
#[arg(long)]
#[arg(long, conflicts_with_all = ["immortal", "once"])]
max_uses: Option<u64>,
/// The maximum age of this token (e.g. 30s, 5m, 7d). It will expire after
/// this much time has passed.
#[arg(long)]
#[arg(long, conflicts_with_all = ["immortal", "once"])]
max_age: Option<String>,
/// This token will never expire.
#[arg(long)]
#[arg(long, conflicts_with_all = ["once", "max_uses", "max_age"])]
immortal: bool,
/// A shortcut for `--max-uses 1`.
#[arg(long)]
#[arg(long, conflicts_with_all = ["immortal", "max_uses", "max_age"])]
once: bool,
}
+6 -1
View File
@@ -740,14 +740,19 @@ pub(super) async fn force_join_room(
&self,
user_id: String,
room_id: OwnedRoomOrAliasId,
via: Option<String>,
) -> Result {
let user_id = parse_local_user_id(self.services, &user_id)?;
let (room_id, servers) = self
let (room_id, mut servers) = self
.services
.rooms
.alias
.resolve_with_servers(&room_id, None)
.await?;
if let Some(via) = via.map(ServerName::parse).transpose()? {
servers.retain(|n| *n != via);
servers.insert(0, via);
}
assert!(
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.
ForceJoinRoom {
/// The user to join
user_id: String,
/// The room to join
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.
+1 -1
View File
@@ -13,7 +13,7 @@ pub(crate) async fn ban_room(
State(services): State<crate::State>,
body: Ruma<rooms::ban::v1::Request>,
) -> Result<rooms::ban::v1::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if !services.users.is_admin(sender_user).await {
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
}
+1 -1
View File
@@ -13,7 +13,7 @@ pub(crate) async fn list_rooms(
State(services): State<crate::State>,
body: Ruma<rooms::list::v1::Request>,
) -> Result<rooms::list::v1::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if !services.users.is_admin(sender_user).await {
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
}
+33 -19
View File
@@ -27,7 +27,7 @@
use service::{mailer::messages, uiaa::Identity, users::HashedPassword};
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
use crate::Ruma;
use crate::{Ruma, router::ClientIdentity};
pub(crate) mod register;
pub(crate) mod threepid;
@@ -75,13 +75,11 @@ pub(crate) async fn get_register_available_route(
return Err!(Request(UserInUse("User ID is not available.")));
}
if let Some(ref info) = body.appservice_info {
if !info.is_user_match(&user_id) {
return Err!(Request(Exclusive("Username is not in an appservice namespace.")));
}
}
if services.appservice.is_exclusive_user_id(&user_id).await {
if let Some(ClientIdentity::Appservice { appservice_info, .. }) = &body.identity
&& !appservice_info.is_user_match(&user_id)
{
return Err!(Request(Exclusive("Username is not in an appservice namespace.")));
} else if services.appservice.is_exclusive_user_id(&user_id).await {
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
}
@@ -111,7 +109,12 @@ pub(crate) async fn change_password_route(
ClientIp(client): ClientIp,
body: Ruma<change_password::v3::Request>,
) -> Result<change_password::v3::Response> {
let identity = if let Some(ref user_id) = body.sender_user {
let identity = if let Some(user_id) = body
.identity
.as_ref()
.map(ClientIdentity::expect_sender_user)
.transpose()?
{
// A signed-in user is trying to change their password, prompt them for their
// existing one
@@ -157,7 +160,12 @@ pub(crate) async fn change_password_route(
services
.users
.all_device_ids(&sender_user)
.ready_filter(|id| *id != body.sender_device())
.ready_filter(|id| {
body.identity
.as_ref()
.and_then(|identity| identity.sender_device())
.is_none_or(|sender_device| sender_device != *id)
})
.for_each(async |id| services.users.remove_device(&sender_user, &id).await)
.await;
@@ -173,7 +181,12 @@ pub(crate) async fn change_password_route(
.await
.ok()
.as_ref()
.is_some_and(|pusher_device| pusher_device != body.sender_device())
.is_some_and(|pusher_device| {
body.identity
.as_ref()
.and_then(|identity| identity.sender_device())
.is_none_or(|sender_device| sender_device != *pusher_device)
})
.then_some(pushkey)
})
.for_each(async |pushkey| {
@@ -187,7 +200,7 @@ pub(crate) async fn change_password_route(
if services.server.config.admin_room_notices {
services
.admin
.notice(&format!("User {} changed their password.", &sender_user))
.notice(&format!("User {sender_user} changed their password."))
.await;
}
@@ -241,9 +254,11 @@ pub(crate) async fn whoami_route(
State(_): State<crate::State>,
body: Ruma<whoami::v3::Request>,
) -> Result<whoami::v3::Response> {
Ok(assign!(whoami::v3::Response::new(body.sender_user().to_owned(), false), {
device_id: body.sender_device,
}))
Ok(
assign!(whoami::v3::Response::new(body.identity.expect_sender_user()?.to_owned(), false), {
device_id: body.identity.sender_device().map(ToOwned::to_owned),
}),
)
}
/// # `POST /_matrix/client/r0/account/deactivate`
@@ -266,9 +281,10 @@ pub(crate) async fn deactivate_route(
// Authentication for this endpoint is technically optional,
// but we require the user to be logged in
let sender_user = body
.sender_user
.identity
.as_ref()
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
.map(ClientIdentity::expect_sender_user)
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))??;
// Prompt the user to confirm with their password using UIAA
let _ = services
@@ -322,8 +338,6 @@ pub(crate) async fn check_registration_token_validity(
/// Runs through all the deactivation steps:
///
/// - Mark as deactivated
/// - Removing display name
/// - Removing avatar URL and blurhash
/// - Removing all profile data
/// - Leaving all rooms (and forgets all of them)
pub async fn full_user_deactivate(
+7 -9
View File
@@ -59,7 +59,7 @@ pub(crate) async fn register_route(
let allow_registration =
services.config.allow_registration || services.firstrun.is_first_run();
if !allow_registration && body.appservice_info.is_none() {
if !allow_registration && body.identity.is_none() {
info!(
?body.username,
?body.initial_device_display_name,
@@ -71,7 +71,7 @@ pub(crate) async fn register_route(
)));
}
let identity = if body.appservice_info.is_some() {
let identity = if body.identity.is_some() {
// Appservices can skip auth
None
} else {
@@ -107,7 +107,7 @@ pub(crate) async fn register_route(
// For appservice logins, make sure that the user ID is in the appservice's
// namespace
match body.appservice_info {
match body.identity {
| Some(ref info) =>
if !info.is_user_match(&user_id) && !emergency_mode_enabled {
return Err!(Request(Exclusive(
@@ -125,7 +125,7 @@ pub(crate) async fn register_route(
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
}
let password = if body.appservice_info.is_some() {
let password = if body.identity.is_some() {
None
} else if let Some(password) = body.password.as_deref() {
Some(HashedPassword::new(password)?)
@@ -140,9 +140,7 @@ pub(crate) async fn register_route(
let mut displayname = user_id.localpart().to_owned();
// Apply the new user displayname suffix, if it's set
if !services.globals.new_user_displayname_suffix().is_empty()
&& body.appservice_info.is_none()
{
if !services.globals.new_user_displayname_suffix().is_empty() && body.identity.is_none() {
write!(displayname, " {}", services.server.config.new_user_displayname_suffix)?;
}
@@ -209,7 +207,7 @@ pub(crate) async fn register_route(
let device_display_name = body.initial_device_display_name.as_deref().unwrap_or("");
if body.appservice_info.is_none() {
if body.identity.is_none() {
if !device_display_name.is_empty() {
let notice = format!(
"New user \"{user_id}\" registered on this server from IP {client} and device \
@@ -255,7 +253,7 @@ pub(crate) async fn register_route(
}
}
if body.appservice_info.is_none() && !services.server.config.auto_join_rooms.is_empty() {
if body.identity.is_none() && !services.server.config.auto_join_rooms.is_empty() {
for room in &services.server.config.auto_join_rooms {
let Ok(room_id) = services.rooms.alias.resolve(room).await else {
error!(
+17 -10
View File
@@ -13,7 +13,7 @@
};
use service::{mailer::messages, uiaa::Identity};
use crate::Ruma;
use crate::{Ruma, router::ClientIdentity};
/// # `GET _matrix/client/v3/account/3pid`
///
@@ -22,7 +22,7 @@ pub(crate) async fn third_party_route(
State(services): State<crate::State>,
body: Ruma<get_3pids::v3::Request>,
) -> Result<get_3pids::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let mut threepids = vec![];
if let Some(email) = services
@@ -53,6 +53,14 @@ pub(crate) async fn request_3pid_management_token_via_email_route(
State(services): State<crate::State>,
body: Ruma<request_3pid_management_token_via_email::v3::Request>,
) -> Result<request_3pid_management_token_via_email::v3::Response> {
// Authentication for this endpoint is technically optional,
// but we require the user to be logged in
let sender_user = body
.identity
.as_ref()
.map(ClientIdentity::expect_sender_user)
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))??;
if !services.threepid.email_requirement().may_change() {
return Err!(Request(Forbidden("You may not change your email address.")));
}
@@ -76,7 +84,7 @@ pub(crate) async fn request_3pid_management_token_via_email_route(
Mailbox::new(None, email),
|verification_link| messages::ChangeEmail {
server_name: services.config.server_name.as_str(),
user_id: body.sender_user.as_deref(),
user_id: Some(sender_user),
verification_link,
},
&body.client_secret,
@@ -107,8 +115,6 @@ pub(crate) async fn add_3pid_route(
State(services): State<crate::State>,
body: Ruma<add_3pid::v3::Request>,
) -> Result<add_3pid::v3::Response> {
let sender_user = body.sender_user();
if !services.threepid.email_requirement().may_change() {
return Err!(Request(Forbidden("You may not change your email address.")));
}
@@ -116,7 +122,10 @@ pub(crate) async fn add_3pid_route(
// Require password auth to add an email
let _ = services
.uiaa
.authenticate_password(&body.auth, Some(Identity::from_user_id(sender_user)))
.authenticate_password(
&body.auth,
Some(Identity::from_user_id(body.identity.expect_sender_user()?)),
)
.await?;
let email = services
@@ -127,7 +136,7 @@ pub(crate) async fn add_3pid_route(
services
.threepid
.associate_localpart_email(sender_user.localpart(), &email)
.associate_localpart_email(body.identity.expect_sender_user()?.localpart(), &email)
.await?;
Ok(add_3pid::v3::Response::new())
@@ -138,8 +147,6 @@ pub(crate) async fn delete_3pid_route(
State(services): State<crate::State>,
body: Ruma<delete_3pid::v3::Request>,
) -> Result<delete_3pid::v3::Response> {
let sender_user = body.sender_user();
if body.medium != Medium::Email {
return Ok(delete_3pid::v3::Response::new(ThirdPartyIdRemovalStatus::NoSupport));
}
@@ -150,7 +157,7 @@ pub(crate) async fn delete_3pid_route(
if services
.threepid
.disassociate_localpart_email(sender_user.localpart())
.disassociate_localpart_email(body.identity.expect_sender_user()?.localpart())
.await
.is_none()
{
+8 -8
View File
@@ -22,9 +22,9 @@ pub(crate) async fn set_global_account_data_route(
State(services): State<crate::State>,
body: Ruma<set_global_account_data::v3::Request>,
) -> Result<set_global_account_data::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if sender_user != body.user_id && body.appservice_info.is_none() {
if sender_user != body.user_id && !body.identity.is_appservice() {
return Err!(Request(Forbidden("You cannot set account data for other users.")));
}
@@ -47,9 +47,9 @@ pub(crate) async fn set_room_account_data_route(
State(services): State<crate::State>,
body: Ruma<set_room_account_data::v3::Request>,
) -> Result<set_room_account_data::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if sender_user != body.user_id && body.appservice_info.is_none() {
if sender_user != body.user_id && !body.identity.is_appservice() {
return Err!(Request(Forbidden("You cannot set account data for other users.")));
}
@@ -72,9 +72,9 @@ pub(crate) async fn get_global_account_data_route(
State(services): State<crate::State>,
body: Ruma<get_global_account_data::v3::Request>,
) -> Result<get_global_account_data::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if sender_user != body.user_id && body.appservice_info.is_none() {
if sender_user != body.user_id && !body.identity.is_appservice() {
return Err!(Request(Forbidden("You cannot get account data of other users.")));
}
@@ -94,9 +94,9 @@ pub(crate) async fn get_room_account_data_route(
State(services): State<crate::State>,
body: Ruma<get_room_account_data::v3::Request>,
) -> Result<get_room_account_data::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if sender_user != body.user_id && body.appservice_info.is_none() {
if sender_user != body.user_id && !body.identity.is_appservice() {
return Err!(Request(Forbidden("You cannot get account data of other users.")));
}
+6 -5
View File
@@ -12,10 +12,11 @@ pub(crate) async fn get_suspended_status(
State(services): State<crate::State>,
body: Ruma<get_suspended::v1::Request>,
) -> Result<get_suspended::v1::Response> {
let sender_user = body.sender_user();
let (admin, active) =
join(services.users.is_admin(sender_user), services.users.is_active(&body.user_id)).await;
let (admin, active) = join(
services.users.is_admin(body.identity.expect_sender_user()?),
services.users.is_active(&body.user_id),
)
.await;
if !admin {
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
}
@@ -37,7 +38,7 @@ pub(crate) async fn put_suspended_status(
State(services): State<crate::State>,
body: Ruma<set_suspended::v1::Request>,
) -> Result<set_suspended::v1::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let (sender_admin, active, target_admin) = join3(
services.users.is_admin(sender_user),
+6 -4
View File
@@ -11,7 +11,8 @@ pub(crate) async fn create_alias_route(
State(services): State<crate::State>,
body: Ruma<create_alias::v3::Request>,
) -> Result<create_alias::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
@@ -19,7 +20,7 @@ pub(crate) async fn create_alias_route(
services
.rooms
.alias
.appservice_checks(&body.room_alias, &body.appservice_info)
.appservice_checks(&body.room_alias, body.identity.appservice_info())
.await?;
// this isn't apart of alias_checks or delete alias route because we should
@@ -59,7 +60,8 @@ pub(crate) async fn delete_alias_route(
State(services): State<crate::State>,
body: Ruma<delete_alias::v3::Request>,
) -> Result<delete_alias::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
@@ -67,7 +69,7 @@ pub(crate) async fn delete_alias_route(
services
.rooms
.alias
.appservice_checks(&body.room_alias, &body.appservice_info)
.appservice_checks(&body.room_alias, body.identity.appservice_info())
.await?;
services
+2 -4
View File
@@ -1,5 +1,5 @@
use axum::extract::State;
use conduwuit::{Err, Result, err};
use conduwuit::{Err, Result};
use ruma::{
api::{appservice::ping, client::appservice::request_ping},
assign,
@@ -15,9 +15,7 @@ pub(crate) async fn appservice_ping(
State(services): State<crate::State>,
body: Ruma<request_ping::v1::Request>,
) -> Result<request_ping::v1::Response> {
let appservice_info = body.appservice_info.as_ref().ok_or_else(|| {
err!(Request(Forbidden("This endpoint can only be called by appservices.")))
})?;
let appservice_info = &body.identity;
if body.appservice_id != appservice_info.registration.id {
return Err!(Request(Forbidden(
+47 -26
View File
@@ -25,7 +25,7 @@ pub(crate) async fn create_backup_version_route(
) -> Result<create_backup_version::v3::Response> {
let version = services
.key_backups
.create_backup(body.sender_user(), &body.algorithm)?;
.create_backup(body.identity.expect_sender_user()?, &body.algorithm)?;
Ok(create_backup_version::v3::Response::new(version))
}
@@ -40,7 +40,7 @@ pub(crate) async fn update_backup_version_route(
) -> Result<update_backup_version::v3::Response> {
services
.key_backups
.update_backup(body.sender_user(), &body.version, &body.algorithm)
.update_backup(body.identity.expect_sender_user()?, &body.version, &body.algorithm)
.await?;
Ok(update_backup_version::v3::Response::new())
@@ -53,13 +53,15 @@ pub(crate) async fn get_latest_backup_info_route(
State(services): State<crate::State>,
body: Ruma<get_latest_backup_info::v3::Request>,
) -> Result<get_latest_backup_info::v3::Response> {
let sender_user = body.identity.expect_sender_user()?;
let (version, algorithm) = services
.key_backups
.get_latest_backup(body.sender_user())
.get_latest_backup(sender_user)
.await
.map_err(|_| err!(Request(NotFound("Key backup does not exist."))))?;
let (count, etag) = get_count_etag(&services, body.sender_user(), &version).await;
let (count, etag) = get_count_etag(&services, sender_user, &version).await;
Ok(get_latest_backup_info::v3::Response::new(algorithm, count, etag, version))
}
@@ -71,15 +73,17 @@ pub(crate) async fn get_backup_info_route(
State(services): State<crate::State>,
body: Ruma<get_backup_info::v3::Request>,
) -> Result<get_backup_info::v3::Response> {
let sender_user = body.identity.expect_sender_user()?;
let algorithm = services
.key_backups
.get_backup(body.sender_user(), &body.version)
.get_backup(sender_user, &body.version)
.await
.map_err(|_| {
err!(Request(NotFound("Key backup does not exist at version {:?}", body.version)))
})?;
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await;
let (count, etag) = get_count_etag(&services, sender_user, &body.version).await;
Ok(get_backup_info::v3::Response::new(algorithm, count, etag, body.version.clone()))
}
@@ -96,7 +100,7 @@ pub(crate) async fn delete_backup_version_route(
) -> Result<delete_backup_version::v3::Response> {
services
.key_backups
.delete_backup(body.sender_user(), &body.version)
.delete_backup(body.identity.expect_sender_user()?, &body.version)
.await;
Ok(delete_backup_version::v3::Response::new())
@@ -114,9 +118,11 @@ pub(crate) async fn add_backup_keys_route(
State(services): State<crate::State>,
body: Ruma<add_backup_keys::v3::Request>,
) -> Result<add_backup_keys::v3::Response> {
let sender_user = body.identity.expect_sender_user()?;
if services
.key_backups
.get_latest_backup_version(body.sender_user())
.get_latest_backup_version(sender_user)
.await
.is_ok_and(|version| version != body.version)
{
@@ -129,12 +135,12 @@ pub(crate) async fn add_backup_keys_route(
for (session_id, key_data) in &room.sessions {
services
.key_backups
.add_key(body.sender_user(), &body.version, room_id, session_id, key_data)
.add_key(sender_user, &body.version, room_id, session_id, key_data)
.await?;
}
}
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await;
let (count, etag) = get_count_etag(&services, sender_user, &body.version).await;
Ok(add_backup_keys::v3::Response::new(etag, count))
}
@@ -151,9 +157,11 @@ pub(crate) async fn add_backup_keys_for_room_route(
State(services): State<crate::State>,
body: Ruma<add_backup_keys_for_room::v3::Request>,
) -> Result<add_backup_keys_for_room::v3::Response> {
let sender_user = body.identity.expect_sender_user()?;
if services
.key_backups
.get_latest_backup_version(body.sender_user())
.get_latest_backup_version(sender_user)
.await
.is_ok_and(|version| version != body.version)
{
@@ -165,11 +173,11 @@ pub(crate) async fn add_backup_keys_for_room_route(
for (session_id, key_data) in &body.sessions {
services
.key_backups
.add_key(body.sender_user(), &body.version, &body.room_id, session_id, key_data)
.add_key(sender_user, &body.version, &body.room_id, session_id, key_data)
.await?;
}
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await;
let (count, etag) = get_count_etag(&services, sender_user, &body.version).await;
Ok(add_backup_keys_for_room::v3::Response::new(etag, count))
}
@@ -186,9 +194,11 @@ pub(crate) async fn add_backup_keys_for_session_route(
State(services): State<crate::State>,
body: Ruma<add_backup_keys_for_session::v3::Request>,
) -> Result<add_backup_keys_for_session::v3::Response> {
let sender_user = body.identity.expect_sender_user()?;
if services
.key_backups
.get_latest_backup_version(body.sender_user())
.get_latest_backup_version(sender_user)
.await
.is_ok_and(|version| version != body.version)
{
@@ -201,7 +211,7 @@ pub(crate) async fn add_backup_keys_for_session_route(
let mut ok_to_replace = true;
if let Some(old_key) = &services
.key_backups
.get_session(body.sender_user(), &body.version, &body.room_id, &body.session_id)
.get_session(sender_user, &body.version, &body.room_id, &body.session_id)
.await
.ok()
{
@@ -260,7 +270,7 @@ pub(crate) async fn add_backup_keys_for_session_route(
services
.key_backups
.add_key(
body.sender_user(),
sender_user,
&body.version,
&body.room_id,
&body.session_id,
@@ -269,7 +279,7 @@ pub(crate) async fn add_backup_keys_for_session_route(
.await?;
}
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await;
let (count, etag) = get_count_etag(&services, sender_user, &body.version).await;
Ok(add_backup_keys_for_session::v3::Response::new(etag, count))
}
@@ -283,7 +293,7 @@ pub(crate) async fn get_backup_keys_route(
) -> Result<get_backup_keys::v3::Response> {
let rooms = services
.key_backups
.get_all(body.sender_user(), &body.version)
.get_all(body.identity.expect_sender_user()?, &body.version)
.await;
Ok(get_backup_keys::v3::Response::new(rooms))
@@ -298,7 +308,7 @@ pub(crate) async fn get_backup_keys_for_room_route(
) -> Result<get_backup_keys_for_room::v3::Response> {
let sessions = services
.key_backups
.get_room(body.sender_user(), &body.version, &body.room_id)
.get_room(body.identity.expect_sender_user()?, &body.version, &body.room_id)
.await;
Ok(get_backup_keys_for_room::v3::Response::new(sessions))
@@ -313,7 +323,12 @@ pub(crate) async fn get_backup_keys_for_session_route(
) -> Result<get_backup_keys_for_session::v3::Response> {
let key_data = services
.key_backups
.get_session(body.sender_user(), &body.version, &body.room_id, &body.session_id)
.get_session(
body.identity.expect_sender_user()?,
&body.version,
&body.room_id,
&body.session_id,
)
.await
.map_err(|_| {
err!(Request(NotFound(debug_error!("Backup key not found for this user's session."))))
@@ -329,12 +344,14 @@ pub(crate) async fn delete_backup_keys_route(
State(services): State<crate::State>,
body: Ruma<delete_backup_keys::v3::Request>,
) -> Result<delete_backup_keys::v3::Response> {
let sender_user = body.identity.expect_sender_user()?;
services
.key_backups
.delete_all_keys(body.sender_user(), &body.version)
.delete_all_keys(sender_user, &body.version)
.await;
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await;
let (count, etag) = get_count_etag(&services, sender_user, &body.version).await;
Ok(delete_backup_keys::v3::Response::new(etag, count))
}
@@ -346,12 +363,14 @@ pub(crate) async fn delete_backup_keys_for_room_route(
State(services): State<crate::State>,
body: Ruma<delete_backup_keys_for_room::v3::Request>,
) -> Result<delete_backup_keys_for_room::v3::Response> {
let sender_user = body.identity.expect_sender_user()?;
services
.key_backups
.delete_room_keys(body.sender_user(), &body.version, &body.room_id)
.delete_room_keys(sender_user, &body.version, &body.room_id)
.await;
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await;
let (count, etag) = get_count_etag(&services, sender_user, &body.version).await;
Ok(delete_backup_keys_for_room::v3::Response::new(etag, count))
}
@@ -363,12 +382,14 @@ pub(crate) async fn delete_backup_keys_for_session_route(
State(services): State<crate::State>,
body: Ruma<delete_backup_keys_for_session::v3::Request>,
) -> Result<delete_backup_keys_for_session::v3::Response> {
let sender_user = body.identity.expect_sender_user()?;
services
.key_backups
.delete_room_key(body.sender_user(), &body.version, &body.room_id, &body.session_id)
.delete_room_key(sender_user, &body.version, &body.room_id, &body.session_id)
.await;
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await;
let (count, etag) = get_count_etag(&services, sender_user, &body.version).await;
Ok(delete_backup_keys_for_session::v3::Response::new(etag, count))
}
+1 -1
View File
@@ -50,7 +50,7 @@ pub(crate) async fn get_capabilities_route(
if services
.users
.is_admin(body.sender_user.as_ref().unwrap())
.is_admin(body.identity.expect_sender_user()?)
.await
{
// Advertise suspension API
+3 -3
View File
@@ -37,8 +37,8 @@ pub(crate) async fn get_context_route(
State(services): State<crate::State>,
body: Ruma<get_context::v3::Request>,
) -> Result<get_context::v3::Response> {
let sender = body.sender();
let (sender_user, sender_device) = sender;
let sender_user = body.identity.expect_sender_user()?;
let sender_device = body.identity.sender_device();
let room_id = &body.room_id;
let event_id = &body.event_id;
let filter = &body.filter;
@@ -143,7 +143,7 @@ pub(crate) async fn get_context_route(
let lazy_loading_context = lazy_loading::Context {
user_id: sender_user,
device_id: Some(sender_device),
device_id: sender_device,
room_id,
token: Some(base_count.into_unsigned()),
options: Some(&filter.lazy_load_options),
+5 -10
View File
@@ -25,16 +25,11 @@ pub(crate) async fn put_dehydrated_device_route(
ClientIp(client): ClientIp,
body: Ruma<put_dehydrated_device::Request>,
) -> Result<put_dehydrated_device::Response> {
let sender_user = body
.sender_user
.as_deref()
.expect("AccessToken authentication required");
let device_id = body.body.device_id.clone();
let device_id = body.device_id.clone();
services
.users
.set_dehydrated_device(sender_user, body.body)
.set_dehydrated_device(body.identity.expect_sender_user()?, body.body)
.await?;
Ok(put_dehydrated_device::Response::new(device_id))
@@ -49,7 +44,7 @@ pub(crate) async fn delete_dehydrated_device_route(
ClientIp(client): ClientIp,
body: Ruma<delete_dehydrated_device::Request>,
) -> Result<delete_dehydrated_device::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let device_id = services.users.get_dehydrated_device_id(sender_user).await?;
@@ -67,7 +62,7 @@ pub(crate) async fn get_dehydrated_device_route(
ClientIp(client): ClientIp,
body: Ruma<get_dehydrated_device::Request>,
) -> Result<get_dehydrated_device::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let device = services.users.get_dehydrated_device(sender_user).await?;
@@ -83,7 +78,7 @@ pub(crate) async fn get_dehydrated_events_route(
ClientIp(client): ClientIp,
body: Ruma<get_events::Request>,
) -> Result<get_events::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let device_id = &body.body.device_id;
let existing_id = services.users.get_dehydrated_device_id(sender_user).await;
+8 -8
View File
@@ -21,7 +21,7 @@ pub(crate) async fn get_devices_route(
) -> Result<get_devices::v3::Response> {
let devices: Vec<device::Device> = services
.users
.all_devices_metadata(body.sender_user())
.all_devices_metadata(body.identity.expect_sender_user()?)
.collect()
.await;
@@ -37,7 +37,7 @@ pub(crate) async fn get_device_route(
) -> Result<get_device::v3::Response> {
let device = services
.users
.get_device_metadata(body.sender_user(), &body.body.device_id)
.get_device_metadata(body.identity.expect_sender_user()?, &body.body.device_id)
.await
.map_err(|_| err!(Request(NotFound("Device not found."))))?;
@@ -53,8 +53,8 @@ pub(crate) async fn update_device_route(
ClientIp(client): ClientIp,
body: Ruma<update_device::v3::Request>,
) -> Result<update_device::v3::Response> {
let sender_user = body.sender_user();
let appservice = body.appservice_info.as_ref();
let sender_user = body.identity.expect_sender_user()?;
let appservice = body.identity.appservice_info();
match services
.users
@@ -118,8 +118,8 @@ pub(crate) async fn delete_device_route(
State(services): State<crate::State>,
body: Ruma<delete_device::v3::Request>,
) -> Result<delete_device::v3::Response> {
let sender_user = body.sender_user();
let appservice = body.appservice_info.as_ref();
let sender_user = body.identity.expect_sender_user()?;
let appservice = body.identity.appservice_info();
// Appservices get to skip UIAA for this endpoint
if appservice.is_none() {
@@ -154,8 +154,8 @@ pub(crate) async fn delete_devices_route(
State(services): State<crate::State>,
body: Ruma<delete_devices::v3::Request>,
) -> Result<delete_devices::v3::Response> {
let sender_user = body.sender_user();
let appservice = body.appservice_info.as_ref();
let sender_user = body.identity.expect_sender_user()?;
let appservice = body.identity.appservice_info();
// Appservices get to skip UIAA for this endpoint
if appservice.is_none() {
+2 -2
View File
@@ -112,7 +112,7 @@ pub(crate) async fn set_room_visibility_route(
ClientIp(client): ClientIp,
body: Ruma<set_room_visibility::v3::Request>,
) -> Result<set_room_visibility::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if !services.rooms.metadata.exists(&body.room_id).await {
// Return 404 if the room doesn't exist
@@ -130,7 +130,7 @@ pub(crate) async fn set_room_visibility_route(
| room::Visibility::Public => {
if services.server.config.lockdown_public_room_directory
&& !services.users.is_admin(sender_user).await
&& body.appservice_info.is_none()
&& !body.identity.is_appservice()
{
info!(
"Non-admin user {sender_user} tried to publish {0} to the room directory \
+2 -2
View File
@@ -15,7 +15,7 @@ pub(crate) async fn get_filter_route(
) -> Result<get_filter::v3::Response> {
services
.users
.get_filter(body.sender_user(), &body.filter_id)
.get_filter(body.identity.expect_sender_user()?, &body.filter_id)
.await
.map(get_filter::v3::Response::new)
.map_err(|_| err!(Request(NotFound("Filter not found."))))
@@ -30,7 +30,7 @@ pub(crate) async fn create_filter_route(
) -> Result<create_filter::v3::Response> {
let filter_id = services
.users
.create_filter(body.sender_user(), &body.filter);
.create_filter(body.identity.expect_sender_user()?, &body.filter);
Ok(create_filter::v3::Response::new(filter_id))
}
+6 -5
View File
@@ -41,7 +41,8 @@ pub(crate) async fn upload_keys_route(
State(services): State<crate::State>,
body: Ruma<upload_keys::v3::Request>,
) -> Result<upload_keys::v3::Response> {
let (sender_user, sender_device) = body.sender();
let sender_user = body.identity.expect_sender_user()?;
let sender_device = body.identity.expect_sender_device()?;
for (key_id, one_time_key) in &body.one_time_keys {
if one_time_key
@@ -154,7 +155,7 @@ pub(crate) async fn get_keys_route(
State(services): State<crate::State>,
body: Ruma<get_keys::v3::Request>,
) -> Result<get_keys::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
get_keys_helper(
&services,
@@ -191,7 +192,7 @@ pub(crate) async fn upload_signing_keys_route(
State(services): State<crate::State>,
body: Ruma<upload_signing_keys::v3::Request>,
) -> Result<upload_signing_keys::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if uiaa_needed_to_upload_keys(
services,
@@ -287,7 +288,7 @@ pub(crate) async fn upload_signatures_route(
return Ok(upload_signatures::v3::Response::new());
}
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
for (user_id, keys) in &body.signed_keys {
for (key_id, key) in keys {
@@ -340,7 +341,7 @@ pub(crate) async fn get_key_changes_route(
State(services): State<crate::State>,
body: Ruma<get_key_changes::v3::Request>,
) -> Result<get_key_changes::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let mut device_list_updates = HashSet::new();
+6 -17
View File
@@ -21,7 +21,6 @@
},
media::create_content,
},
assign,
};
use service::media::mxc::Mxc;
@@ -54,7 +53,7 @@ pub(crate) async fn create_content_route(
ClientIp(client): ClientIp,
body: Ruma<create_content::v3::Request>,
) -> Result<create_content::v3::Response> {
let user = body.sender_user();
let user = body.identity.expect_sender_user()?;
if services.users.is_suspended(user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
@@ -76,17 +75,7 @@ pub(crate) async fn create_content_route(
return Err!(Request(Unknown("Failed to save uploaded media")));
}
let blurhash = body.generate_blurhash.then(|| {
services
.media
.create_blurhash(&body.file, content_type, filename)
.ok()
.flatten()
});
Ok(assign!(create_content::v3::Response::new(mxc.to_string().into()), {
blurhash: blurhash.flatten(),
}))
Ok(create_content::v3::Response::new(mxc.to_string().into()))
}
/// # `GET /_matrix/client/v1/media/thumbnail/{serverName}/{mediaId}`
@@ -103,7 +92,7 @@ pub(crate) async fn get_content_thumbnail_route(
ClientIp(client): ClientIp,
body: Ruma<get_content_thumbnail::v1::Request>,
) -> Result<get_content_thumbnail::v1::Response> {
let user = body.sender_user();
let user = body.identity.expect_sender_user()?;
let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?;
let mxc = Mxc {
@@ -153,7 +142,7 @@ pub(crate) async fn get_content_route(
ClientIp(client): ClientIp,
body: Ruma<get_content::v1::Request>,
) -> Result<get_content::v1::Response> {
let user = body.sender_user();
let user = body.identity.expect_sender_user()?;
let mxc = Mxc {
server_name: &body.server_name,
@@ -200,7 +189,7 @@ pub(crate) async fn get_content_as_filename_route(
ClientIp(client): ClientIp,
body: Ruma<get_content_as_filename::v1::Request>,
) -> Result<get_content_as_filename::v1::Response> {
let user = body.sender_user();
let user = body.identity.expect_sender_user()?;
let mxc = Mxc {
server_name: &body.server_name,
@@ -251,7 +240,7 @@ pub(crate) async fn get_media_preview_route(
ClientIp(client): ClientIp,
body: Ruma<get_media_preview::v1::Request>,
) -> Result<get_media_preview::v1::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let url = &body.url;
let url = Url::parse(&body.url).map_err(|e| {
+1 -1
View File
@@ -56,7 +56,7 @@ pub(crate) async fn get_media_preview_legacy_route(
ClientIp(client): ClientIp,
body: Ruma<get_media_preview::v3::Request>,
) -> Result<get_media_preview::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let url = &body.url;
let url = Url::parse(&body.url).map_err(|e| {
+1 -1
View File
@@ -15,7 +15,7 @@ pub(crate) async fn ban_user_route(
State(services): State<crate::State>,
body: Ruma<ban_user::v3::Request>,
) -> Result<ban_user::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if sender_user == body.user_id {
return Err!(Request(Forbidden("You cannot ban yourself.")));
+1 -1
View File
@@ -18,7 +18,7 @@ pub(crate) async fn forget_room_route(
State(services): State<crate::State>,
body: Ruma<forget_room::v3::Request>,
) -> Result<forget_room::v3::Response> {
let user_id = body.sender_user();
let user_id = body.identity.expect_sender_user()?;
let room_id = &body.room_id;
let joined = services.rooms.state_cache.is_joined(user_id, room_id);
+1 -2
View File
@@ -29,7 +29,7 @@ pub(crate) async fn invite_user_route(
ClientIp(client): ClientIp,
body: Ruma<invite_user::v3::Request>,
) -> Result<invite_user::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
@@ -247,7 +247,6 @@ pub(crate) async fn invite_helper(
let mut content = RoomMemberEventContent::new(MembershipState::Invite);
content.displayname = services.users.displayname(recipient_user).await.ok();
content.avatar_url = services.users.avatar_url(recipient_user).await.ok();
content.blurhash = services.users.blurhash(recipient_user).await.ok();
content.is_direct = Some(is_direct);
content.reason = reason;
+2 -2
View File
@@ -28,7 +28,7 @@ pub(crate) async fn join_room_by_id_route(
ClientIp(client): ClientIp,
body: Ruma<join_room_by_id::v3::Request>,
) -> Result<join_room_by_id::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
@@ -97,7 +97,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
ClientIp(client): ClientIp,
body: Ruma<join_room_by_id_or_alias::v3::Request>,
) -> Result<join_room_by_id_or_alias::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let body = &body.body;
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
+1 -1
View File
@@ -15,7 +15,7 @@ pub(crate) async fn kick_user_route(
State(services): State<crate::State>,
body: Ruma<kick_user::v3::Request>,
) -> Result<kick_user::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
+1 -3
View File
@@ -51,7 +51,7 @@ pub(crate) async fn knock_room_route(
ClientIp(client): ClientIp,
body: Ruma<knock_room::v3::Request>,
) -> Result<knock_room::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let body = &body.body;
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
@@ -343,7 +343,6 @@ async fn knock_room_helper_local(
let mut content = RoomMemberEventContent::new(MembershipState::Knock);
content.displayname = services.users.displayname(sender_user).await.ok();
content.avatar_url = services.users.avatar_url(sender_user).await.ok();
content.blurhash = services.users.blurhash(sender_user).await.ok();
content.reason.clone_from(&reason.clone());
// Try normal knock first
@@ -527,7 +526,6 @@ async fn knock_room_helper_remote(
let mut knock_content = RoomMemberEventContent::new(MembershipState::Knock);
knock_content.displayname = services.users.displayname(sender_user).await.ok();
knock_content.avatar_url = services.users.avatar_url(sender_user).await.ok();
knock_content.blurhash = services.users.blurhash(sender_user).await.ok();
knock_content.reason = reason;
knock_event_stub.insert(
+9 -4
View File
@@ -32,10 +32,15 @@ pub(crate) async fn leave_room_route(
State(services): State<crate::State>,
body: Ruma<leave_room::v3::Request>,
) -> Result<leave_room::v3::Response> {
leave_room(&services, body.sender_user(), &body.room_id, body.reason.clone())
.boxed()
.await
.map(|()| leave_room::v3::Response::new())
leave_room(
&services,
body.identity.expect_sender_user()?,
&body.room_id,
body.reason.clone(),
)
.boxed()
.await
.map(|()| leave_room::v3::Response::new())
}
// Make a user leave all their joined rooms, rescinds knocks, forgets all rooms,
+2 -2
View File
@@ -30,7 +30,7 @@ pub(crate) async fn get_member_events_route(
State(services): State<crate::State>,
body: Ruma<get_member_events::v3::Request>,
) -> Result<get_member_events::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let membership = body.membership.as_ref();
let not_membership = body.not_membership.as_ref();
@@ -72,7 +72,7 @@ pub(crate) async fn joined_members_route(
if !services
.rooms
.state_accessor
.user_can_see_state_events(body.sender_user(), &body.room_id)
.user_can_see_state_events(body.identity.expect_sender_user()?, &body.room_id)
.await
{
return Err!(Request(Forbidden("You don't have permission to view this room.")));
+2 -6
View File
@@ -40,7 +40,7 @@ pub(crate) async fn joined_rooms_route(
let joined_rooms = services
.rooms
.state_cache
.rooms_joined(body.sender_user())
.rooms_joined(body.identity.expect_sender_user()?)
.collect()
.await;
@@ -105,11 +105,7 @@ pub(crate) async fn banned_room_check(
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
}
} else if let Some(server_name) = server_name {
if services
.config
.forbidden_remote_server_names
.is_match(server_name.host())
{
if services.moderation.is_remote_server_forbidden(server_name) {
warn!(
"User {user_id} who is not an admin tried joining a room which has the server \
name {server_name} that is globally forbidden. Rejecting.",
+1 -1
View File
@@ -14,7 +14,7 @@ pub(crate) async fn unban_user_route(
State(services): State<crate::State>,
body: Ruma<unban_user::v3::Request>,
) -> Result<unban_user::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
+5 -16
View File
@@ -23,7 +23,7 @@
};
use futures::{FutureExt, StreamExt, TryFutureExt, future::OptionFuture, pin_mut};
use ruma::{
DeviceId, RoomId, UserId,
RoomId, UserId,
api::{
Direction,
client::{filter::RoomEventFilter, message::get_message_events},
@@ -37,7 +37,6 @@
serde::Raw,
};
use ruminuwuity::invite_permission_config::FilterLevel;
use tracing::warn;
use crate::Ruma;
@@ -76,8 +75,8 @@ pub(crate) async fn get_message_events_route(
ClientIp(client_ip): ClientIp,
body: Ruma<get_message_events::v3::Request>,
) -> Result<get_message_events::v3::Response> {
let sender_user = body.sender_user();
let sender_device = body.sender_device.as_deref();
let sender_user = body.identity.expect_sender_user()?;
let sender_device = body.identity.sender_device();
let room_id = &body.room_id;
let filter = &body.filter;
@@ -158,17 +157,7 @@ pub(crate) async fn get_message_events_route(
let lazy_loading_context = lazy_loading::Context {
user_id: sender_user,
device_id: sender_device.or_else(|| {
if let Some(registration) = body.appservice_info.as_ref() {
Some(<&DeviceId>::from(registration.registration.id.as_str()))
} else {
warn!(
"No device_id provided and no appservice registration found, this should be \
unreachable"
);
None
}
}),
device_id: sender_device,
room_id,
token: Some(from.into_unsigned()),
options: Some(&filter.lazy_load_options),
@@ -307,7 +296,7 @@ pub(crate) async fn is_ignored_pdu<Pdu>(
}
let sender_user = event.sender();
let type_ignored = IGNORED_MESSAGE_TYPES.binary_search(event.kind()).is_ok();
let type_ignored = IGNORED_MESSAGE_TYPES.contains(event.kind());
let server_ignored = services
.moderation
.is_remote_server_ignored(sender_user.server_name());
+1 -1
View File
@@ -15,7 +15,7 @@ pub(crate) async fn get_mutual_rooms_route(
State(services): State<crate::State>,
body: Ruma<mutual_rooms::unstable::Request>,
) -> Result<mutual_rooms::unstable::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if sender_user == body.user_id {
return Err!(Request(Unknown("You cannot request rooms in common with yourself.")));
+1 -1
View File
@@ -16,7 +16,7 @@ pub(crate) async fn create_openid_token_route(
State(services): State<crate::State>,
body: Ruma<account::request_openid_token::v3::Request>,
) -> Result<account::request_openid_token::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if sender_user != body.user_id {
return Err!(Request(InvalidParam(
+5 -3
View File
@@ -16,17 +16,19 @@ pub(crate) async fn set_presence_route(
State(services): State<crate::State>,
body: Ruma<set_presence::v3::Request>,
) -> Result<set_presence::v3::Response> {
let sender_user = body.identity.expect_sender_user()?;
if !services.config.allow_local_presence {
return Err!(Request(Forbidden("Presence is disabled on this server")));
}
if body.sender_user() != body.user_id && body.appservice_info.is_none() {
if sender_user != body.user_id && !body.identity.is_appservice() {
return Err!(Request(InvalidParam("Not allowed to set presence of other users")));
}
services
.presence
.set_presence(body.sender_user(), &body.presence, None, None, body.status_msg.clone())
.set_presence(sender_user, &body.presence, None, None, body.status_msg.clone())
.await?;
Ok(set_presence::v3::Response::new())
@@ -49,7 +51,7 @@ pub(crate) async fn get_presence_route(
let has_shared_rooms = services
.rooms
.state_cache
.user_sees_user(body.sender_user(), &body.user_id)
.user_sees_user(body.identity.expect_sender_user()?, &body.user_id)
.await;
if has_shared_rooms {
+139 -68
View File
@@ -8,12 +8,12 @@
UserId,
api::{
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,
},
assign,
events::room::member::{MembershipState, RoomMemberEventContent},
events::room::member::MembershipState,
presence::PresenceState,
profile::{ProfileFieldName, ProfileFieldValue},
};
@@ -23,8 +23,7 @@
/// # `GET /_matrix/client/v3/profile/{userId}`
///
/// Returns the displayname, avatar_url, blurhash, and custom profile fields of
/// the user.
/// Returns the user's profile information.
///
/// - If user is on another server and we do not have a local copy already,
/// fetch profile over federation.
@@ -52,9 +51,12 @@ pub(crate) async fn set_profile_field_route(
State(services): State<crate::State>,
body: Ruma<set_profile_field::v3::Request>,
) -> Result<set_profile_field::v3::Response> {
if body.user_id != body.sender_user()
&& !(body.appservice_info.is_some()
|| services.admin.user_is_admin(body.sender_user()).await)
if body.user_id != body.identity.expect_sender_user()?
&& !(body.identity.is_appservice()
|| services
.admin
.user_is_admin(body.identity.expect_sender_user()?)
.await)
{
return Err!(Request(Forbidden("You may not change other users' profile data.")));
}
@@ -63,8 +65,13 @@ pub(crate) async fn set_profile_field_route(
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()))
.await?;
set_profile_field(
&services,
&body.user_id,
ProfileFieldChange::Set(body.value.clone()),
body.propagate_to.clone(),
)
.await?;
Ok(set_profile_field::v3::Response::new())
}
@@ -73,9 +80,12 @@ pub(crate) async fn delete_profile_field_route(
State(services): State<crate::State>,
body: Ruma<delete_profile_field::v3::Request>,
) -> Result<delete_profile_field::v3::Response> {
if body.user_id != body.sender_user()
&& !(body.appservice_info.is_some()
|| services.admin.user_is_admin(body.sender_user()).await)
if body.user_id != body.identity.expect_sender_user()?
&& !(body.identity.is_appservice()
|| services
.admin
.user_is_admin(body.identity.expect_sender_user()?)
.await)
{
return Err!(Request(Forbidden("You may not change other users' profile data.")));
}
@@ -84,8 +94,13 @@ pub(crate) async fn delete_profile_field_route(
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()))
.await?;
set_profile_field(
&services,
&body.user_id,
ProfileFieldChange::Delete(body.field.clone()),
body.propagate_to.clone(),
)
.await?;
Ok(delete_profile_field::v3::Response::new())
}
@@ -120,7 +135,13 @@ async fn fetch_full_profile(
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))
@@ -154,8 +175,13 @@ async fn fetch_profile_field(
if let Some(value) = response.get(field.as_str()).map(ToOwned::to_owned) {
if let Ok(value) = ProfileFieldValue::new(field.as_str(), value) {
let _ = set_profile_field(services, user_id, ProfileFieldChange::Set(value.clone()))
.await;
let _ = set_profile_field(
services,
user_id,
ProfileFieldChange::Set(value.clone()),
PropagateTo::None,
)
.await;
Ok(Some(value))
} else {
@@ -164,7 +190,13 @@ async fn fetch_profile_field(
)))
}
} 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)
}
@@ -257,6 +289,7 @@ async fn set_profile_field(
services: &Services,
user_id: &UserId,
change: ProfileFieldChange,
propagate_to: PropagateTo,
) -> Result<()> {
const MAX_KEY_LENGTH_BYTES: usize = 255;
const MAX_PROFILE_LENGTH_BYTES: usize = 65536;
@@ -304,6 +337,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 {
| ProfileFieldChange::Set(ProfileFieldValue::DisplayName(displayname)) => {
services
@@ -322,56 +440,9 @@ async fn set_profile_field(
services.users.set_avatar_url(user_id, None);
},
| other =>
if other.field_name().as_str() == "blurhash" {
if let Some(Value::String(blurhash)) = other.value() {
services.users.set_blurhash(user_id, Some(blurhash));
} else {
services.users.set_blurhash(user_id, None);
}
} else {
services.users.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;
}
services
.users
.set_profile_key(user_id, other.field_name().as_str(), other.value()),
}
Ok(())
+13 -12
View File
@@ -30,7 +30,7 @@ pub(crate) async fn get_pushrules_all_route(
State(services): State<crate::State>,
body: Ruma<get_pushrules_all::v3::Request>,
) -> Result<get_pushrules_all::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let Some(content_value) = services
.account_data
@@ -101,7 +101,7 @@ pub(crate) async fn get_pushrules_global_route(
State(services): State<crate::State>,
body: Ruma<get_pushrules_global_scope::v3::Request>,
) -> Result<get_pushrules_global_scope::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let Some(content_value) = services
.account_data
@@ -189,7 +189,7 @@ pub(crate) async fn get_pushrule_route(
State(services): State<crate::State>,
body: Ruma<get_pushrule::v3::Request>,
) -> Result<get_pushrule::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_user = body.identity.expect_sender_user()?;
// remove old deprecated mentions push rules as per MSC4210
#[allow(deprecated)]
@@ -226,7 +226,7 @@ pub(crate) async fn set_pushrule_route(
State(services): State<crate::State>,
body: Ruma<set_pushrule::v3::Request>,
) -> Result<set_pushrule::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let body = &body.body;
let mut account_data: PushRulesEvent = services
.account_data
@@ -282,7 +282,7 @@ pub(crate) async fn get_pushrule_actions_route(
State(services): State<crate::State>,
body: Ruma<get_pushrule_actions::v3::Request>,
) -> Result<get_pushrule_actions::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
// remove old deprecated mentions push rules as per MSC4210
#[allow(deprecated)]
@@ -316,7 +316,7 @@ pub(crate) async fn set_pushrule_actions_route(
State(services): State<crate::State>,
body: Ruma<set_pushrule_actions::v3::Request>,
) -> Result<set_pushrule_actions::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let mut account_data: PushRulesEvent = services
.account_data
@@ -349,7 +349,7 @@ pub(crate) async fn get_pushrule_enabled_route(
State(services): State<crate::State>,
body: Ruma<get_pushrule_enabled::v3::Request>,
) -> Result<get_pushrule_enabled::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
// remove old deprecated mentions push rules as per MSC4210
#[allow(deprecated)]
@@ -383,7 +383,7 @@ pub(crate) async fn set_pushrule_enabled_route(
State(services): State<crate::State>,
body: Ruma<set_pushrule_enabled::v3::Request>,
) -> Result<set_pushrule_enabled::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let mut account_data: PushRulesEvent = services
.account_data
@@ -416,7 +416,7 @@ pub(crate) async fn delete_pushrule_route(
State(services): State<crate::State>,
body: Ruma<delete_pushrule::v3::Request>,
) -> Result<delete_pushrule::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let mut account_data: PushRulesEvent = services
.account_data
@@ -458,7 +458,7 @@ pub(crate) async fn get_pushers_route(
State(services): State<crate::State>,
body: Ruma<get_pushers::v3::Request>,
) -> Result<get_pushers::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
Ok(get_pushers::v3::Response::new(services.pusher.get_pushers(sender_user).await))
}
@@ -472,11 +472,12 @@ pub(crate) async fn set_pushers_route(
State(services): State<crate::State>,
body: Ruma<set_pusher::v3::Request>,
) -> Result<set_pusher::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let sender_device = body.identity.expect_sender_device()?;
services
.pusher
.set_pusher(sender_user, body.sender_device(), &body.action)
.set_pusher(sender_user, sender_device, &body.action)
.await?;
Ok(set_pusher::v3::Response::new())
+4 -3
View File
@@ -26,7 +26,7 @@ pub(crate) async fn set_read_marker_route(
State(services): State<crate::State>,
body: Ruma<set_read_marker::v3::Request>,
) -> Result<set_read_marker::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if let Some(event) = &body.fully_read {
let fully_read_event = FullyReadEvent::new(FullyReadEventContent::new(event.to_owned()));
@@ -118,10 +118,11 @@ pub(crate) async fn create_receipt_route(
ClientIp(client_ip): ClientIp,
body: Ruma<create_receipt::v3::Request>,
) -> Result<create_receipt::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
services
.users
.update_device_last_seen(sender_user, body.sender_device.as_deref(), client_ip)
.update_device_last_seen(sender_user, body.identity.sender_device(), client_ip)
.await;
if matches!(
+2 -2
View File
@@ -17,10 +17,10 @@ pub(crate) async fn redact_event_route(
ClientIp(client_ip): ClientIp,
body: Ruma<redact_event::v3::Request>,
) -> Result<redact_event::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
services
.users
.update_device_last_seen(sender_user, body.sender_device.as_deref(), client_ip)
.update_device_last_seen(sender_user, body.identity.sender_device(), client_ip)
.await;
let body = &body.body;
if services.users.is_suspended(sender_user).await? {
+3 -3
View File
@@ -28,7 +28,7 @@ pub(crate) async fn get_relating_events_with_rel_type_and_event_type_route(
) -> Result<get_relating_events_with_rel_type_and_event_type::v1::Response> {
paginate_relations_with_filter(
&services,
body.sender_user(),
body.identity.expect_sender_user()?,
&body.room_id,
&body.event_id,
body.event_type.clone().into(),
@@ -56,7 +56,7 @@ pub(crate) async fn get_relating_events_with_rel_type_route(
) -> Result<get_relating_events_with_rel_type::v1::Response> {
paginate_relations_with_filter(
&services,
body.sender_user(),
body.identity.expect_sender_user()?,
&body.room_id,
&body.event_id,
None,
@@ -84,7 +84,7 @@ pub(crate) async fn get_relating_events_route(
) -> Result<get_relating_events::v1::Response> {
paginate_relations_with_filter(
&services,
body.sender_user(),
body.identity.expect_sender_user()?,
&body.room_id,
&body.event_id,
None,
+10 -10
View File
@@ -36,7 +36,7 @@ pub(crate) async fn report_room_route(
ClientIp(client): ClientIp,
body: Ruma<report_room::v3::Request>,
) -> Result<report_room::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
@@ -92,7 +92,7 @@ pub(crate) async fn report_event_route(
body: Ruma<report_content::v3::Request>,
) -> Result<report_content::v3::Response> {
// user authentication
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
@@ -135,8 +135,8 @@ pub(crate) async fn report_user_route(
ClientIp(client): ClientIp,
body: Ruma<report_user::v3::Request>,
) -> Result<report_user::v3::Response> {
// user authentication
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_user = body.identity.expect_sender_user()?;
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
@@ -219,14 +219,14 @@ async fn is_event_report_valid(
fn build_report(report: Report) -> RoomMessageEventContent {
let mut text =
format!("@room New {} report received from {}:\n\n", report.report_type, report.sender);
if report.user_id.is_some() {
let _ = writeln!(text, "- Reported User ID: `{}`", report.user_id.unwrap());
if let Some(user_id) = report.user_id {
let _ = writeln!(text, "- Reported User ID: `{user_id}`");
}
if report.room_id.is_some() {
let _ = writeln!(text, "- Reported Room ID: `{}`", report.room_id.unwrap());
if let Some(room_id) = report.room_id {
let _ = writeln!(text, "- Reported Room ID: `{room_id}`");
}
if report.event_id.is_some() {
let _ = writeln!(text, "- Reported Event ID: `{}`", report.event_id.unwrap());
if let Some(event_id) = report.event_id {
let _ = writeln!(text, "- Reported Event ID: `{event_id}`");
}
let _ = writeln!(text, "- Report Reason: {}", report.reason);
+1 -1
View File
@@ -15,7 +15,7 @@ pub(crate) async fn get_room_aliases_route(
State(services): State<crate::State>,
body: Ruma<aliases::v3::Request>,
) -> Result<aliases::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if !services
.rooms
+83 -37
View File
@@ -10,7 +10,7 @@
use futures::FutureExt;
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, Int, MilliSecondsSinceUnixEpoch, OwnedRoomAliasId,
OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, RoomVersionId, UserId,
OwnedUserId, RoomAliasId, RoomId, RoomVersionId, UserId,
api::client::room::{self, create_room},
assign,
events::{
@@ -24,6 +24,7 @@
member::{MembershipState, RoomMemberEventContent},
name::RoomNameEventContent,
power_levels::RoomPowerLevelsEventContent,
server_acl::RoomServerAclEventContent,
topic::RoomTopicEventContent,
},
},
@@ -60,10 +61,10 @@ pub(crate) async fn create_room_route(
) -> Result<create_room::v3::Response> {
use create_room::v3::RoomPreset;
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if !services.globals.allow_room_creation()
&& body.appservice_info.is_none()
&& !body.identity.is_appservice()
&& !services.users.is_admin(sender_user).await
{
return Err!(Request(Forbidden("Room creation has been disabled.",)));
@@ -86,25 +87,41 @@ pub(crate) async fn create_room_route(
};
let room_version_rules = room_version.rules().unwrap();
let room_id: Option<OwnedRoomId> = match room_version_rules.room_id_format {
| RoomIdFormatVersion::V1 => {
// Check for custom room ID field
if let Some(CanonicalJsonValue::String(room_id)) =
body.json_body.as_ref().unwrap().get("room_id")
{
Some(
RoomId::parse(room_id)
.map_err(|_| err!(Request(BadJson("Malformed custom room ID"))))?,
)
} else {
Some(RoomId::new_v1(services.globals.server_name()))
}
},
// For custom room IDs, if the user is creating a room with a v1 room ID format,
// we can just use that ID directly. However, if it's a custom *v2* room ID, we
// need to make sure that we don't generate one, which would in turn trick us
// into generating invalid v2 room events.
//
// expect_room_id is the custom room ID that the user is expecting - for v2
// formatted rooms, we check that the m.room.create event's generated room ID
// exactly matches this, and abort if it doesn't. Otherwise, we use it as the
// room ID itself.
let expect_room_id = {
let body_ref = body.json_body.as_ref().unwrap();
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,
};
// 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() {
return Err!(Request(RoomInUse("Room with that custom room ID already exists",)));
}
@@ -113,7 +130,7 @@ pub(crate) async fn create_room_route(
if body.visibility == room::Visibility::Public
&& services.server.config.lockdown_public_room_directory
&& !services.users.is_admin(sender_user).await
&& body.appservice_info.is_none()
&& !body.identity.is_appservice()
{
warn!(
"Non-admin user {sender_user} tried to publish {room_id:?} to the room directory \
@@ -169,7 +186,7 @@ pub(crate) async fn create_room_route(
let alias: Option<OwnedRoomAliasId> = match body.room_alias_name.as_ref() {
| Some(alias) =>
Some(room_alias_check(&services, alias, body.appservice_info.as_ref()).await?),
Some(room_alias_check(&services, alias, body.identity.appservice_info()).await?),
| _ => None,
};
@@ -243,15 +260,16 @@ pub(crate) async fn create_room_route(
// Allow requesters to override the `origin_server_ts` to customize room ids
// from v12 onwards
let custom_origin_server_ts = body
.json_body
.as_ref()
.unwrap()
.get("origin_server_ts")
.and_then(CanonicalJsonValue::as_integer)
.map(Into::into)
.and_then(|value: i64| value.try_into().ok())
.map(MilliSecondsSinceUnixEpoch);
let custom_origin_server_ts = {
let body_ref = body.json_body.as_ref().unwrap();
body_ref
.get("origin_server_ts")
.or_else(|| body_ref.get("fi.mau.origin_server_ts"))
.and_then(CanonicalJsonValue::as_integer)
.map(Into::into)
.and_then(|value: i64| value.try_into().ok())
.map(MilliSecondsSinceUnixEpoch)
};
let create_event_id = services
.rooms
@@ -281,6 +299,13 @@ pub(crate) async fn create_room_route(
};
drop(state_lock);
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;
// 2. Let the room creator join
@@ -288,7 +313,6 @@ pub(crate) async fn create_room_route(
let mut join_event = RoomMemberEventContent::new(MembershipState::Join);
join_event.displayname = services.users.displayname(sender_user).await.ok();
join_event.avatar_url = services.users.avatar_url(sender_user).await.ok();
join_event.blurhash = services.users.blurhash(sender_user).await.ok();
join_event.is_direct = Some(body.is_direct);
debug_info!("Joining {sender_user} to room {room_id}");
@@ -454,7 +478,32 @@ pub(crate) async fn create_room_route(
.boxed()
.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 {
let mut partial_pdu = event
.deserialize_as_unchecked::<PartialPdu>()
@@ -482,7 +531,7 @@ pub(crate) async fn create_room_route(
.await?;
}
// 7. Events implied by name and topic
// 8. Events implied by name and topic
if let Some(name) = &body.name {
services
.rooms
@@ -511,7 +560,7 @@ pub(crate) async fn create_room_route(
.await?;
}
// 8. Events implied by invite (and TODO: invite_3pid)
// 9. Events implied by invite (and TODO: invite_3pid)
drop(state_lock);
for recipient_user in &invitees {
if let Err(e) =
@@ -537,10 +586,7 @@ pub(crate) async fn create_room_route(
if services.server.config.admin_room_notices {
services
.admin
.send_text(&format!(
"{sender_user} made {} public to the room directory",
&room_id
))
.send_text(&format!("{sender_user} made {room_id} public to the room directory"))
.await;
}
info!("{sender_user} made {0} public to the room directory", &room_id);
+5 -4
View File
@@ -12,6 +12,7 @@ pub(crate) async fn get_room_event_route(
State(ref services): State<crate::State>,
ref body: Ruma<get_room_event::v3::Request>,
) -> Result<get_room_event::v3::Response> {
let sender_user = body.identity.expect_sender_user()?;
let event_id = &body.event_id;
let room_id = &body.room_id;
@@ -24,25 +25,25 @@ pub(crate) async fn get_room_event_route(
let visible = services
.rooms
.state_accessor
.user_can_see_event(body.sender_user(), room_id, event_id)
.user_can_see_event(sender_user, room_id, event_id)
.map(Ok);
let (mut event, visible) = try_join(event, visible).await?;
if !visible || is_ignored_pdu(services, &event, body.sender_user()).await? {
if !visible || is_ignored_pdu(services, &event, sender_user).await? {
return Err!(Request(Forbidden("You don't have permission to view this event.")));
}
if let Err(e) = services
.rooms
.pdu_metadata
.add_bundled_aggregations_to_pdu(body.sender_user(), &mut event)
.add_bundled_aggregations_to_pdu(sender_user, &mut event)
.await
{
debug_warn!("Failed to add bundled aggregations to event: {e}");
}
event.set_unsigned(body.sender_user.as_deref());
event.set_unsigned(Some(body.identity.expect_sender_user()?));
Ok(get_room_event::v3::Response::new(event.into_format()))
}
+11 -12
View File
@@ -17,12 +17,13 @@ pub(crate) async fn room_initial_sync_route(
State(services): State<crate::State>,
body: Ruma<Request>,
) -> Result<Response> {
let sender_user = body.identity.expect_sender_user()?;
let room_id = &body.room_id;
if !services
.rooms
.state_accessor
.user_can_see_state_events(body.sender_user(), room_id)
.user_can_see_state_events(sender_user, room_id)
.await
{
return Err!(Request(Forbidden("No room preview available.")));
@@ -31,7 +32,7 @@ pub(crate) async fn room_initial_sync_route(
let membership = services
.rooms
.state_cache
.user_membership(body.sender_user(), room_id)
.user_membership(sender_user, room_id)
.map(Ok);
let visibility = services.rooms.directory.visibility(room_id).map(Ok);
@@ -52,16 +53,14 @@ pub(crate) async fn room_initial_sync_route(
.pdus_rev(room_id, None)
.try_take(limit)
.and_then(async |mut pdu| {
pdu.1.set_unsigned(body.sender_user.as_deref());
if let Some(sender_user) = body.sender_user.as_deref() {
if let Err(e) = services
.rooms
.pdu_metadata
.add_bundled_aggregations_to_pdu(sender_user, &mut pdu.1)
.await
{
debug_warn!("Failed to add bundled aggregations: {e}");
}
pdu.1.set_unsigned(Some(sender_user));
if let Err(e) = services
.rooms
.pdu_metadata
.add_bundled_aggregations_to_pdu(sender_user, &mut pdu.1)
.await
{
debug_warn!("Failed to add bundled aggregations: {e}");
}
Ok(pdu)
})
+9 -2
View File
@@ -4,7 +4,7 @@
use ruma::api::client::room::get_summary;
use service::rooms::summary::Accessibility;
use crate::Ruma;
use crate::{Ruma, router::ClientIdentity};
/// # `GET /_matrix/client/v1/room_summary/{roomIdOrAlias}`
///
@@ -28,7 +28,14 @@ pub(crate) async fn get_room_summary(
let summary = services
.rooms
.summary
.get_room_summary_for_user(body.sender_user.as_deref(), &room_id, &servers)
.get_room_summary_for_user(
body.identity
.as_ref()
.map(ClientIdentity::expect_sender_user)
.transpose()?,
&room_id,
&servers,
)
.await?;
match summary {
+419 -272
View File
@@ -2,47 +2,267 @@
use axum::extract::State;
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},
};
use futures::{FutureExt, StreamExt};
use ruma::{
CanonicalJsonObject, RoomId, RoomVersionId,
OwnedEventId, OwnedRoomId, RoomId, UserId,
api::{client::room::upgrade_room, error::ErrorKind},
assign,
events::{
StateEventType, TimelineEventType,
StateEventType,
room::{
create::PreviousRoom,
create::{PreviousRoom, RoomCreateEventContent},
member::{MembershipState, RoomMemberEventContent},
power_levels::RoomPowerLevelsEventContent,
tombstone::RoomTombstoneEventContent,
},
space::child::{RedactedSpaceChildEventContent, SpaceChildEventContent},
space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
},
int,
room_version_rules::RoomIdFormatVersion,
};
use serde_json::{json, value::to_raw_value};
use serde_json::value::to_raw_value;
use crate::router::Ruma;
/// Recommended transferable state events list from the spec
const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 11] = &[
StateEventType::RoomAvatar,
StateEventType::RoomServerAcl,
StateEventType::RoomEncryption,
StateEventType::RoomName,
StateEventType::RoomAvatar,
StateEventType::RoomTopic,
StateEventType::RoomGuestAccess,
StateEventType::RoomHistoryVisibility,
StateEventType::RoomJoinRules,
StateEventType::RoomName,
StateEventType::RoomPowerLevels,
StateEventType::RoomServerAcl,
StateEventType::RoomTopic,
// Not explicitly recommended in spec, but very useful.
StateEventType::SpaceParent,
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`
///
/// Upgrades the room.
@@ -57,10 +277,14 @@ pub(crate) async fn upgrade_room_route(
State(services): State<crate::State>,
body: Ruma<upgrade_room::v3::Request>,
) -> Result<upgrade_room::v3::Response> {
// TODO[v12]: Handle additional creators
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_user = body.identity.expect_sender_user()?;
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(
ErrorKind::UnsupportedRoomVersion,
"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.")));
}
// First, check if the user has permission to upgrade the room (send tombstone
// event)
// 1. Check that the user has permission to send m.room.tombstone events in the
// room.
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
// Note that this does internally call the policy server with a fake room ID,
// which may not be good?
let tombstone_test_result = services
// Check tombstone permission by attempting to create (but not send) the event.
services
.rooms
.timeline
.create_hash_and_sign_event(
.create_event(
PartialPdu::state(
StateKey::new(),
&RoomTombstoneEventContent::new(
@@ -99,157 +321,104 @@ pub(crate) async fn upgrade_room_route(
Some(&body.room_id),
&old_room_state_lock,
)
.await;
if let Err(_e) = tombstone_test_result {
return Err!(Request(Forbidden("User does not have permission to upgrade this room.")));
}
drop(old_room_state_lock);
.await
.map_err(|_| {
err!(Request(Forbidden("You do not have permission to upgrade this room.")))
})?;
// Create a replacement room
let room_version_rules = body
let new_version_rules = body
.new_version
.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()))
} else {
let last_event = if new_version_rules
.authorization
.room_create_event_id_as_room_id
{
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 {
None
Some(
services
.rooms
.state
.get_forward_extremities(&body.room_id)
.collect::<Vec<OwnedEventId>>()
.await[0]
.clone(),
)
};
let state_lock = services
.rooms
.state
.mutex
.lock(replacement_room_tmp.as_str())
.await;
// Get the old room creation event
let mut create_event_content: CanonicalJsonObject = services
let old_create_event: RoomCreateEventContent = services
.rooms
.state_accessor
.room_state_get_content(&body.room_id, &StateEventType::RoomCreate, "")
.await
.map_err(|_| err!(Database("Found room without m.room.create event.")))?;
// Use the m.room.tombstone event as the predecessor
let predecessor = {
#[allow(deprecated, reason = "Clients still use event_id even though it's deprecated")]
Some(assign!(PreviousRoom::new(body.room_id.clone()), {
event_id: tombstone_event_id,
}))
let create_event_content = if new_version_rules.authorization.use_room_create_sender {
RoomCreateEventContent::new_v1(sender_user.to_owned())
} else {
RoomCreateEventContent::new_v11()
};
#[allow(deprecated)]
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
// room_version
{
use RoomVersionId::*;
match body.new_version {
| 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 replacement_room_id: Option<OwnedRoomId> =
if new_version_rules.room_id_format == RoomIdFormatVersion::V2 {
None
} else {
Some(RoomId::new_v1(services.globals.server_name()))
};
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
.rooms
.timeline
.build_and_append_pdu(
PartialPdu {
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,
},
PartialPdu::state(StateKey::new(), &create_event_content),
sender_user,
replacement_room,
&state_lock,
replacement_room_id.as_deref(),
&new_room_state_lock,
)
.boxed()
.await?;
let create_id = create_event_id.as_str().replace('$', "!");
let (replacement_room, state_lock) =
if room_version_rules.room_id_format == RoomIdFormatVersion::V2 {
let parsed_room_id = RoomId::parse(&create_id)?;
drop(new_room_state_lock);
// re-acquire a new lock with the new room ID.
// We don't actually need a state lock for sending the m.room.create event, but
// 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
.rooms
.state
@@ -258,9 +427,13 @@ pub(crate) async fn upgrade_room_route(
.await;
(Some(parsed_room_id), lock)
} 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
services
.rooms
@@ -271,17 +444,16 @@ pub(crate) async fn upgrade_room_route(
&assign!(RoomMemberEventContent::new(MembershipState::Join), {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
}),
),
sender_user,
replacement_room.as_deref(),
&state_lock,
replacement_room_id.as_deref(),
&new_room_state_lock,
)
.boxed()
.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 {
let state_keys = services
.rooms
@@ -298,26 +470,45 @@ pub(crate) async fn upgrade_room_route(
| Ok(v) => v.content().to_owned(),
| 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,
// we need to make sure they dont appear in the users block of power levels.
if *event_type == StateEventType::RoomPowerLevels {
// TODO(v12): additional creators
let creators = vec![sender_user];
let creators = body
.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 =
serde_json::from_str(event_content.get()).map_err(|_| {
err!(Request(BadJson("Power levels event content is not valid")))
})?;
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)
.expect("event is valid, we just deserialized and modified it");
}
debug!(%event_type, ?state_key, "Transferring state event to new room");
services
.rooms
.timeline
@@ -329,15 +520,15 @@ pub(crate) async fn upgrade_room_route(
..Default::default()
},
sender_user,
replacement_room.as_deref(),
&state_lock,
replacement_room_id.as_deref(),
&new_room_state_lock,
)
.boxed()
.await?;
}
}
// Moves any local aliases to the new room
// 4. Move any local aliases to the new room
let mut local_aliases = services
.rooms
.alias
@@ -345,6 +536,7 @@ pub(crate) async fn upgrade_room_route(
.boxed();
while let Some(alias) = local_aliases.next().await {
debug!(?alias, "Migrating alias");
services
.rooms
.alias
@@ -353,11 +545,31 @@ pub(crate) async fn upgrade_room_route(
services.rooms.alias.set_alias(
&alias,
replacement_room.as_ref().unwrap(),
replacement_room_id.as_deref().unwrap(),
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
let mut power_levels = services
.rooms
@@ -379,8 +591,10 @@ pub(crate) async fn upgrade_room_route(
power_levels.events_default = 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
// 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
.rooms
.timeline
@@ -391,117 +605,50 @@ pub(crate) async fn upgrade_room_route(
),
sender_user,
Some(&body.room_id),
&state_lock,
&old_room_state_lock,
)
.boxed()
.await?;
.await
.ok();
drop(state_lock);
// For v12 rooms, send tombstone AFTER creating replacement room
if room_version_rules.room_id_format == RoomIdFormatVersion::V2 {
let old_room_state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await;
// For v12 rooms, no event reference in predecessor due to cyclic dependency -
// could best effort one maybe?
services
.rooms
.timeline
.build_and_append_pdu(
PartialPdu::state(
StateKey::new(),
&RoomTombstoneEventContent::new(
"This room has been replaced".to_owned(),
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()
// MSC4168: Update spaces that reference this room to point at the new room.
debug!("Updating parent spaces");
update_parents(
&services,
sender_user,
&body.room_id,
replacement_room_id.as_deref().unwrap(),
)
.await
.inspect_err(|e| {
error!(
old_room_id=?body.room_id,
new_room_id=?replacement_room_id.as_deref().unwrap(),
%e,
"failed to update parent spaces during room upgrade"
);
// First, drop the space's child event
let state_lock = services.rooms.state.mutex.lock(space_id.as_str()).await;
debug!("Removing space child event for room {} in space {space_id}", &body.room_id);
services
.rooms
.timeline
.build_and_append_pdu(
PartialPdu {
event_type: StateEventType::SpaceChild.into(),
content: to_raw_value(&RedactedSpaceChildEventContent::new())
.expect("event is valid, we just created it"),
state_key: Some(body.room_id.clone().as_str().into()),
..Default::default()
},
sender_user,
Some(&space_id),
&state_lock,
)
.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()
})
.ok();
// MSC4168: Update child rooms to point at the new space, where possible
debug!("Updating space children");
update_children(
&services,
sender_user,
&body.room_id,
replacement_room_id.as_deref().unwrap(),
)
.await
.inspect_err(|e| {
error!(
old_room_id=?body.room_id,
new_room_id=?replacement_room_id.as_deref().unwrap(),
%e,
"failed to update space children during room upgrade"
);
services
.rooms
.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);
}
})
.ok();
// 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()))
}
+1 -1
View File
@@ -43,7 +43,7 @@ pub(crate) async fn search_events_route(
State(services): State<crate::State>,
body: Ruma<Request>,
) -> Result<Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let next_batch = body.next_batch.as_deref();
let mut result_categories = ResultCategories::new();
+9 -5
View File
@@ -22,16 +22,16 @@ pub(crate) async fn send_message_event_route(
ClientIp(client_ip): ClientIp,
body: Ruma<send_message_event::v3::Request>,
) -> Result<send_message_event::v3::Response> {
let sender_user = body.sender_user();
let sender_device = body.sender_device.as_deref();
let appservice_info = body.appservice_info.as_ref();
let sender_user = body.identity.expect_sender_user()?;
let sender_device = body.identity.sender_device();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
services
.users
.update_device_last_seen(sender_user, body.sender_device.as_deref(), client_ip)
.update_device_last_seen(sender_user, sender_device, client_ip)
.await;
// Forbid m.room.encrypted if encryption is disabled
@@ -83,7 +83,11 @@ pub(crate) async fn send_message_event_route(
event_type: body.event_type.clone().into(),
content,
unsigned: Some(unsigned),
timestamp: appservice_info.and(body.timestamp),
timestamp: if body.identity.is_appservice() {
body.timestamp
} else {
None
},
..Default::default()
},
sender_user,
+6 -4
View File
@@ -146,7 +146,7 @@ pub(crate) async fn login_route(
}) => {
debug!("Got appservice login type");
let Some(ref info) = body.appservice_info else {
let Some(ref info) = body.identity else {
return Err!(Request(MissingToken("Missing appservice token.")));
};
@@ -254,7 +254,7 @@ pub(crate) async fn login_token_route(
return Err!(Request(Forbidden("Login via an existing session is not enabled")));
}
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
// Prompt the user to confirm with their password using UIAA
let _ = services
@@ -286,7 +286,9 @@ pub(crate) async fn logout_route(
ClientIp(client): ClientIp,
body: Ruma<logout::v3::Request>,
) -> Result<logout::v3::Response> {
let (sender_user, sender_device) = body.sender();
let sender_user = body.identity.expect_sender_user()?;
let sender_device = body.identity.expect_sender_device()?;
services
.users
.remove_device(sender_user, sender_device)
@@ -332,7 +334,7 @@ pub(crate) async fn logout_all_route(
ClientIp(client): ClientIp,
body: Ruma<logout_all::v3::Request>,
) -> Result<logout_all::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
services
.users
.all_device_ids(sender_user)
+1 -1
View File
@@ -27,7 +27,7 @@ pub(crate) async fn get_hierarchy_route(
.rooms
.summary
.get_room_hierarchy_for_user(
body.sender_user(),
body.identity.expect_sender_user()?,
body.room_id.clone(),
max_depth,
body.suggested_only,
+5 -5
View File
@@ -38,10 +38,10 @@ pub(crate) async fn send_state_event_for_key_route(
ClientIp(ip): ClientIp,
body: Ruma<send_state_event::v3::Request>,
) -> Result<send_state_event::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
services
.users
.update_device_last_seen(sender_user, body.sender_device.as_deref(), ip)
.update_device_last_seen(sender_user, body.identity.sender_device(), ip)
.await;
if services.users.is_suspended(sender_user).await? {
@@ -55,7 +55,7 @@ pub(crate) async fn send_state_event_for_key_route(
&body.event_type,
&body.body.body,
&body.state_key,
if body.appservice_info.is_some() {
if body.identity.is_appservice() {
body.timestamp
} else {
None
@@ -91,7 +91,7 @@ pub(crate) async fn get_state_events_route(
State(services): State<crate::State>,
body: Ruma<get_state_events::v3::Request>,
) -> Result<get_state_events::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if !services
.rooms
@@ -125,7 +125,7 @@ pub(crate) async fn get_state_event_for_key_route(
State(services): State<crate::State>,
body: Ruma<get_state_event_for_key::v3::Request>,
) -> Result<get_state_event_for_key::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
if !services
.rooms
+6 -3
View File
@@ -34,6 +34,7 @@
},
assign,
events::presence::{PresenceEvent, PresenceEventContent},
presence::PresenceState,
serde::Raw,
};
use service::{
@@ -181,10 +182,11 @@ pub(crate) async fn sync_events_route(
ClientIp(client_ip): ClientIp,
body: Ruma<sync_events::v3::Request>,
) -> Result<sync_events::v3::Response> {
let (sender_user, sender_device) = body.sender();
let sender_user = body.identity.expect_sender_user()?;
let sender_device = body.identity.expect_sender_device()?;
// Presence update
if services.config.allow_local_presence {
if services.config.allow_local_presence && body.set_presence != PresenceState::Offline {
services
.presence
.ping_presence(sender_user, &body.body.set_presence)
@@ -225,7 +227,8 @@ pub(crate) async fn build_sync_events(
services: &Services,
body: &Ruma<sync_events::v3::Request>,
) -> Result<sync_events::v3::Response> {
let (syncing_user, syncing_device) = body.sender();
let syncing_user = body.identity.sender_user().expect("should have a user");
let syncing_device = body.identity.sender_device().expect("should have a device");
let current_count = services.globals.current_count()?;
+3 -3
View File
@@ -70,8 +70,8 @@ pub(crate) async fn sync_events_v5_route(
body: Ruma<sync_events::v5::Request>,
) -> Result<sync_events::v5::Response> {
debug_assert!(DEFAULT_BUMP_TYPES.is_sorted(), "DEFAULT_BUMP_TYPES is not sorted");
let ref sender_user = body.sender_user().to_owned();
let ref sender_device = body.sender_device().to_owned();
let sender_user = body.identity.expect_sender_user()?;
let sender_device = body.identity.expect_sender_device()?;
services
.users
@@ -93,7 +93,7 @@ pub(crate) async fn sync_events_v5_route(
.and_then(|string| string.parse().ok())
.unwrap_or(0);
let snake_key = into_snake_key(sender_user.as_ref(), sender_device.as_str(), conn_id);
let snake_key = into_snake_key(sender_user, sender_device.as_str(), conn_id);
if globalsince != 0 && !services.sync.snake_connection_cached(&snake_key) {
return Err!(Request(UnknownPos(
+3 -3
View File
@@ -21,7 +21,7 @@ pub(crate) async fn update_tag_route(
State(services): State<crate::State>,
body: Ruma<create_tag::v3::Request>,
) -> Result<create_tag::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let mut tags_event = services
.account_data
@@ -56,7 +56,7 @@ pub(crate) async fn delete_tag_route(
State(services): State<crate::State>,
body: Ruma<delete_tag::v3::Request>,
) -> Result<delete_tag::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let mut tags_event = services
.account_data
@@ -88,7 +88,7 @@ pub(crate) async fn get_tags_route(
State(services): State<crate::State>,
body: Ruma<get_tags::v3::Request>,
) -> Result<get_tags::v3::Response> {
let sender_user = body.sender_user();
let sender_user = body.identity.expect_sender_user()?;
let tags_event = services
.account_data
+5 -3
View File
@@ -16,6 +16,8 @@ pub(crate) async fn get_threads_route(
State(services): State<crate::State>,
ref body: Ruma<get_threads::v1::Request>,
) -> Result<get_threads::v1::Response> {
let sender_user = body.identity.expect_sender_user()?;
// Use limit or else 10, with maximum 100
let limit = body
.limit
@@ -34,14 +36,14 @@ pub(crate) async fn get_threads_route(
let threads: Vec<(PduCount, PduEvent)> = services
.rooms
.threads
.threads_until(body.sender_user(), &body.room_id, from, &body.include)
.threads_until(sender_user, &body.room_id, from, &body.include)
.await?
.take(limit)
.filter_map(|(count, pdu)| async move {
services
.rooms
.state_accessor
.user_can_see_event(body.sender_user(), &body.room_id, &pdu.event_id)
.user_can_see_event(sender_user, &body.room_id, &pdu.event_id)
.await
.then_some((count, pdu))
})
@@ -49,7 +51,7 @@ pub(crate) async fn get_threads_route(
if let Err(e) = services
.rooms
.pdu_metadata
.add_bundled_aggregations_to_pdu(body.sender_user(), &mut pdu)
.add_bundled_aggregations_to_pdu(sender_user, &mut pdu)
.await
{
debug_warn!("Failed to add bundled aggregations to thread: {e}");

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