mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-04-01 21:35:46 +00:00
Compare commits
100 Commits
ginger/dat
...
tom/max-pe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f11bf4d74 | ||
|
|
1e8748d1a0 | ||
|
|
70ef6e4211 | ||
|
|
212c1bc14d | ||
|
|
ce46b6869f | ||
|
|
a18b8254d0 | ||
|
|
279f7cbfe4 | ||
|
|
006c57face | ||
|
|
d52e0dc014 | ||
|
|
4b873a1b95 | ||
|
|
76865e6f91 | ||
|
|
99f16c2dfc | ||
|
|
5ac82f36f3 | ||
|
|
c249dd992e | ||
|
|
0956779802 | ||
|
|
a83c1f1513 | ||
|
|
8b5e4d8fe1 | ||
|
|
7502a944d7 | ||
|
|
aed15f246a | ||
|
|
27d6604d14 | ||
|
|
1c7bd2f6fa | ||
|
|
56d7099011 | ||
|
|
bc426e1bfc | ||
|
|
6c61b3ec5b | ||
|
|
9d9d1170b6 | ||
|
|
7be20abcad | ||
|
|
078275964c | ||
|
|
bf200ad12d | ||
|
|
41e628892d | ||
|
|
44851ee6a2 | ||
|
|
a7e6e6e83f | ||
|
|
8a561fcd3a | ||
|
|
25c305f473 | ||
|
|
c900350164 | ||
|
|
c565e6ffbc | ||
|
|
442f887c98 | ||
|
|
03220845e5 | ||
|
|
f8c1e9bcde | ||
|
|
21324b748f | ||
|
|
b7bf36443b | ||
|
|
d72192aa32 | ||
|
|
38ecc41780 | ||
|
|
7ae958bb03 | ||
|
|
f676fa53f1 | ||
|
|
978bdc6466 | ||
|
|
7c741e62cf | ||
|
|
12aecf8091 | ||
|
|
19372f0b15 | ||
|
|
a66b90cb3d | ||
|
|
7234ce6cbe | ||
|
|
beb0c2ad9a | ||
|
|
39aaf95d09 | ||
|
|
5e0edd5a1c | ||
|
|
d180f5a759 | ||
|
|
f163264a82 | ||
|
|
5e7bc590d2 | ||
|
|
08df35946b | ||
|
|
c4ebf289fa | ||
|
|
1fc6010f9a | ||
|
|
1d91331275 | ||
|
|
77e62ad772 | ||
|
|
696a1e6a4d | ||
|
|
f41bbd7361 | ||
|
|
7350266c80 | ||
|
|
322c0900c6 | ||
|
|
1237e60aaf | ||
|
|
9b4845bf8d | ||
|
|
fb5b515f96 | ||
|
|
e6336d694a | ||
|
|
b7841280d9 | ||
|
|
f4ccb81913 | ||
|
|
710cdfeadb | ||
|
|
666849ea87 | ||
|
|
71094803f1 | ||
|
|
bf91ce5c7f | ||
|
|
8fd15f26ce | ||
|
|
705fa6c5c6 | ||
|
|
6f67c27538 | ||
|
|
8586d747d1 | ||
|
|
11012a9ce1 | ||
|
|
07be190507 | ||
|
|
ae4acc9568 | ||
|
|
f83ddecd8c | ||
|
|
dd87232f1f | ||
|
|
8e33f9a7d0 | ||
|
|
8d3e4eba99 | ||
|
|
96bfdb97da | ||
|
|
b61010da47 | ||
|
|
987c5eeb03 | ||
|
|
7fa4fa9862 | ||
|
|
b2bead67ac | ||
|
|
48a6a475ce | ||
|
|
86450da705 | ||
|
|
8538b21860 | ||
|
|
63e4aacd2b | ||
|
|
72f0eb9493 | ||
|
|
867d0ab671 | ||
|
|
64e187e5b4 | ||
|
|
5dc449a87a | ||
|
|
f5fda01013 |
@@ -32,11 +32,13 @@ outputs:
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- run: mkdir -p digests
|
||||
shell: bash
|
||||
- name: Download digests
|
||||
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
|
||||
uses: forgejo/download-artifact@v4
|
||||
with:
|
||||
path: /tmp/digests
|
||||
path: digests
|
||||
pattern: ${{ inputs.digest_pattern }}
|
||||
merge-multiple: true
|
||||
|
||||
@@ -62,6 +64,7 @@ runs:
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
flavor: |
|
||||
latest=auto
|
||||
suffix=${{ inputs.tag_suffix }},onlatest=true
|
||||
tags: |
|
||||
type=semver,pattern={{version}},prefix=v
|
||||
@@ -70,7 +73,6 @@ runs:
|
||||
type=ref,event=branch,prefix=${{ format('refs/heads/{0}', github.event.repository.default_branch) != github.ref && 'branch-' || '' }},
|
||||
type=ref,event=pr
|
||||
type=sha,format=short
|
||||
type=raw,value=latest${{ inputs.tag_suffix }},enable=${{ startsWith(github.ref, 'refs/tags/v') }},priority=1100
|
||||
images: ${{ inputs.images }}
|
||||
# default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509
|
||||
env:
|
||||
@@ -78,7 +80,7 @@ runs:
|
||||
|
||||
- name: Create manifest list and push
|
||||
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
|
||||
working-directory: /tmp/digests
|
||||
working-directory: digests
|
||||
shell: bash
|
||||
env:
|
||||
IMAGES: ${{ inputs.images }}
|
||||
|
||||
@@ -54,7 +54,7 @@ runs:
|
||||
run: mv /tmp/binaries/sbin/conduwuit /tmp/binaries/conduwuit${{ inputs.cpu_suffix }}-${{ inputs.slug }}${{ inputs.artifact_suffix }}
|
||||
|
||||
- name: Upload binary artifact
|
||||
uses: forgejo/upload-artifact@v3
|
||||
uses: forgejo/upload-artifact@v4
|
||||
with:
|
||||
name: conduwuit${{ inputs.cpu_suffix }}-${{ inputs.slug }}${{ inputs.artifact_suffix }}
|
||||
path: /tmp/binaries/conduwuit${{ inputs.cpu_suffix }}-${{ inputs.slug }}${{ inputs.artifact_suffix }}
|
||||
@@ -62,7 +62,7 @@ runs:
|
||||
|
||||
- name: Upload digest
|
||||
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
|
||||
uses: forgejo/upload-artifact@v3
|
||||
uses: forgejo/upload-artifact@v4
|
||||
with:
|
||||
name: digests${{ inputs.digest_suffix }}-${{ inputs.slug }}${{ inputs.cpu_suffix }}
|
||||
path: /tmp/digests/*
|
||||
|
||||
82
.forgejo/pull_request_template.md
Normal file
82
.forgejo/pull_request_template.md
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
name: 'New pull request'
|
||||
about: 'Open a new pull request to contribute to continuwuity'
|
||||
ref: 'main'
|
||||
---
|
||||
|
||||
<!--
|
||||
In order to help reviewers know what your pull request does at a glance, you should ensure that
|
||||
|
||||
1. Your PR title is a short, single sentence describing what you changed
|
||||
2. You have described in more detail what you have changed, why you have changed it, what the
|
||||
intended effect is, and why you think this will be beneficial to the project.
|
||||
|
||||
If you have made any potentially strange/questionable design choices, but didn't feel they'd benefit
|
||||
from code comments, please don't mention them here - after opening your pull request,
|
||||
go to "files changed", and click on the "+" symbol in the line number gutter,
|
||||
and attach comments to the lines that you think would benefit from some clarification.
|
||||
-->
|
||||
|
||||
This pull request...
|
||||
|
||||
<!-- Example:
|
||||
This pull request allows us to warp through time and space ten times faster than before by
|
||||
double-inverting the warp drive with hyperheated jump fluid, both making the drive faster and more
|
||||
efficient. This resolves the common issue where we have to wait more than 10 milliseconds to
|
||||
engage, use, and disengage the warp drive when travelling between galaxies.
|
||||
-->
|
||||
|
||||
<!-- Closes: #... -->
|
||||
<!-- Fixes: #... -->
|
||||
<!-- Uncomment the above line(s) if your pull request fixes an issue or closes another pull request
|
||||
by superseding it. Replace `#...` with the issue/pr number, such as `#123`. -->
|
||||
|
||||
**Pull request checklist:**
|
||||
|
||||
<!-- You need to complete these before your PR can be considered.
|
||||
If you aren't sure about some, feel free to ask for clarification in #dev:continuwuity.org. -->
|
||||
- [ ] This pull request targets the `main` branch, and the branch is named something other than
|
||||
`main`.
|
||||
- [ ] I have written an appropriate pull request title and my description is clear.
|
||||
- [ ] I understand I am responsible for the contents of this pull request.
|
||||
- I have followed the [contributing guidelines][c1]:
|
||||
- [ ] My contribution follows the [code style][c2], if applicable.
|
||||
- [ ] I ran [pre-commit checks][c1pc] before opening/drafting this pull request.
|
||||
- [ ] I have [tested my contribution][c1t] (or proof-read it for documentation-only changes)
|
||||
myself, if applicable. This includes ensuring code compiles.
|
||||
- [ ] My commit messages follow the [commit message format][c1cm] and are descriptive.
|
||||
- [ ] I have written a [news fragment][n1] for this PR, if applicable<!--(can be done after hitting open!)-->.
|
||||
|
||||
<!--
|
||||
Notes on these requirements:
|
||||
|
||||
- While not required, we encourage you to sign your commits with GPG or SSH to attest the
|
||||
authenticity of your changes.
|
||||
- While we allow LLM-assisted contributions, we do not appreciate contributions that are
|
||||
low quality, which is typical of machine-generated contributions that have not had a lot of love
|
||||
and care from a human. Please do not open a PR if all you have done is asked ChatGPT to tidy up
|
||||
the codebase with a +-100,000 diff.
|
||||
- In the case of code style violations, reviewers may leave review comments/change requests
|
||||
indicating what the ideal change would look like. For example, a reviewer may suggest you lower
|
||||
a log level, or use `match` instead of `if/else` etc.
|
||||
- In the case of code style violations, pre-commit check failures, minor things like typos/spelling
|
||||
errors, and in some cases commit format violations, reviewers may modify your branch directly,
|
||||
typically by making changes and adding a commit. Particularly in the latter case, a reviewer may
|
||||
rebase your commits to squash "spammy" ones (like "fix", "fix", "actually fix"), and reword
|
||||
commit messages that don't satisfy the format.
|
||||
- Pull requests MUST pass the `Checks` CI workflows to be capable of being merged. This can only be
|
||||
bypassed in exceptional circumstances.
|
||||
If your CI flakes, let us know in matrix:r/dev:continuwuity.org.
|
||||
- Pull requests have to be based on the latest `main` commit before being merged. If the main branch
|
||||
changes while you're making your changes, you should make sure you rebase on main before
|
||||
opening a PR. Your branch will be rebased on main before it is merged if it has fallen behind.
|
||||
- We typically only do fast-forward merges, so your entire commit log will be included. Once in
|
||||
main, it's difficult to get out cleanly, so put on your best dress, smile for the cameras!
|
||||
-->
|
||||
|
||||
[c1]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/CONTRIBUTING.md
|
||||
[c2]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/docs/development/code_style.mdx
|
||||
[c1pc]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/CONTRIBUTING.md#pre-commit-checks
|
||||
[c1t]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/CONTRIBUTING.md#running-tests-locally
|
||||
[c1cm]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/CONTRIBUTING.md#commit-messages
|
||||
[n1]: https://towncrier.readthedocs.io/en/stable/tutorial.html#creating-news-fragments
|
||||
@@ -59,10 +59,9 @@ jobs:
|
||||
# Aggressive GC since cache restores don't increment counter
|
||||
echo "CARGO_INCREMENTAL_GC_TRIGGER=5" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup Rust nightly
|
||||
- name: Setup Rust
|
||||
uses: ./.forgejo/actions/setup-rust
|
||||
with:
|
||||
rust-version: nightly
|
||||
github-token: ${{ secrets.GH_PUBLIC_RO }}
|
||||
|
||||
- name: Get package version and component
|
||||
@@ -127,7 +126,7 @@ jobs:
|
||||
[ -f /etc/conduwuit/conduwuit.toml ] && echo "✅ Config file installed"
|
||||
|
||||
- name: Upload deb artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: forgejo/upload-artifact@v4
|
||||
with:
|
||||
name: continuwuity-${{ steps.debian-version.outputs.distribution }}
|
||||
path: ${{ steps.cargo-deb.outputs.path }}
|
||||
|
||||
@@ -239,13 +239,13 @@ jobs:
|
||||
cp $BIN_RPM upload-bin/
|
||||
|
||||
- name: Upload binary RPM
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: forgejo/upload-artifact@v4
|
||||
with:
|
||||
name: continuwuity
|
||||
path: upload-bin/
|
||||
|
||||
- name: Upload debug RPM artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: forgejo/upload-artifact@v4
|
||||
with:
|
||||
name: continuwuity-debug
|
||||
path: artifacts/*debuginfo*.rpm
|
||||
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
cat ./element-web/webapp/config.json
|
||||
|
||||
- name: 📤 Upload Artifact
|
||||
uses: forgejo/upload-artifact@v3
|
||||
uses: forgejo/upload-artifact@v4
|
||||
with:
|
||||
name: element-web
|
||||
path: ./element-web/webapp/
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
name: Renovate
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/renovatebot/renovate:42.11.0@sha256:656c1e5b808279eac16c37b89562fb4c699e02fc7e219244f4a1fc2f0a7ce367
|
||||
image: ghcr.io/renovatebot/renovate:42.70.2@sha256:3c2ac1b94fa92ef2fa4d1a0493f2c3ba564454720a32fdbcac2db2846ff1ee47
|
||||
options: --tmpfs /tmp:exec
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
persist-credentials: true
|
||||
token: ${{ secrets.FORGEJO_TOKEN }}
|
||||
|
||||
- uses: https://github.com/cachix/install-nix-action@7ab6e7fd29da88e74b1e314a4ae9ac6b5cda3801 # v31.8.0
|
||||
- uses: https://github.com/cachix/install-nix-action@4e002c8ec80594ecd40e759629461e26c8abed15 # v31.9.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
|
||||
4
.mailmap
4
.mailmap
@@ -2,6 +2,7 @@ AlexPewMaster <git@alex.unbox.at> <68469103+AlexPewMaster@users.noreply.github.c
|
||||
Daniel Wiesenberg <weasy@hotmail.de> <weasy666@gmail.com>
|
||||
Devin Ragotzy <devin.ragotzy@gmail.com> <d6ragotzy@wmich.edu>
|
||||
Devin Ragotzy <devin.ragotzy@gmail.com> <dragotzy7460@mail.kvcc.edu>
|
||||
Ginger <ginger@gingershaped.computer> <75683114+gingershaped@users.noreply.github.com>
|
||||
Jonas Platte <jplatte+git@posteo.de> <jplatte+gitlab@posteo.de>
|
||||
Jonas Zohren <git-pbkyr@jzohren.de> <gitlab-jfowl-0ux98@sh14.de>
|
||||
Jonathan de Jong <jonathan@automatia.nl> <jonathandejong02@gmail.com>
|
||||
@@ -12,5 +13,6 @@ Olivia Lee <olivia@computer.surgery> <benjamin@computer.surgery>
|
||||
Rudi Floren <rudi.floren@gmail.com> <rudi.floren@googlemail.com>
|
||||
Tamara Schmitz <tamara.zoe.schmitz@posteo.de> <15906939+tamara-schmitz@users.noreply.github.com>
|
||||
Timo Kösters <timo@koesters.xyz>
|
||||
nexy7574 <git@nexy7574.co.uk> <nex@noreply.forgejo.ellis.link>
|
||||
nexy7574 <git@nexy7574.co.uk> <nex@noreply.localhost>
|
||||
x4u <xi.zhu@protonmail.ch> <14617923-x4u@users.noreply.gitlab.com>
|
||||
Ginger <ginger@gingershaped.computer> <75683114+gingershaped@users.noreply.github.com>
|
||||
|
||||
@@ -23,7 +23,7 @@ repos:
|
||||
- id: check-added-large-files
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.40.0
|
||||
rev: v1.41.0
|
||||
hooks:
|
||||
- id: typos
|
||||
- id: typos
|
||||
@@ -31,7 +31,7 @@ repos:
|
||||
stages: [commit-msg]
|
||||
|
||||
- repo: https://github.com/crate-ci/committed
|
||||
rev: v1.1.8
|
||||
rev: v1.1.9
|
||||
hooks:
|
||||
- id: committed
|
||||
|
||||
|
||||
@@ -24,3 +24,4 @@ extend-ignore-re = [
|
||||
"continuwuity" = "continuwuity"
|
||||
"continuwity" = "continuwuity"
|
||||
"execuse" = "execuse"
|
||||
"oltp" = "OTLP"
|
||||
|
||||
12
CHANGELOG.md
Normal file
12
CHANGELOG.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Continuwuity 0.5.0 (2025-12-30)
|
||||
|
||||
**This release contains a CRITICAL vulnerability patch, and you must update as soon as possible**
|
||||
|
||||
## Features
|
||||
|
||||
- Enabled the OTLP exporter in default builds, and allow configuring the exporter protocol. (@Jade). (#1251)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- Don't allow admin room upgrades, as this can break the admin room (@timedout) (#1245)
|
||||
- Fix invalid creators in power levels during upgrade to v12 (@timedout) (#1245)
|
||||
84
Cargo.lock
generated
84
Cargo.lock
generated
@@ -940,7 +940,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "conduwuit"
|
||||
version = "0.5.0-rc.8.1"
|
||||
version = "0.5.1"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"conduwuit_admin",
|
||||
@@ -972,7 +972,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "conduwuit_admin"
|
||||
version = "0.5.0-rc.8.1"
|
||||
version = "0.5.1"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"conduwuit_api",
|
||||
@@ -994,7 +994,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "conduwuit_api"
|
||||
version = "0.5.0-rc.8.1"
|
||||
version = "0.5.1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum 0.7.9",
|
||||
@@ -1027,14 +1027,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "conduwuit_build_metadata"
|
||||
version = "0.5.0-rc.8.1"
|
||||
version = "0.5.1"
|
||||
dependencies = [
|
||||
"built",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "conduwuit_core"
|
||||
version = "0.5.0-rc.8.1"
|
||||
version = "0.5.1"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"arrayvec",
|
||||
@@ -1095,7 +1095,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "conduwuit_database"
|
||||
version = "0.5.0-rc.8.1"
|
||||
version = "0.5.1"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"conduwuit_core",
|
||||
@@ -1114,7 +1114,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "conduwuit_macros"
|
||||
version = "0.5.0-rc.8.1"
|
||||
version = "0.5.1"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"proc-macro2",
|
||||
@@ -1124,7 +1124,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "conduwuit_router"
|
||||
version = "0.5.0-rc.8.1"
|
||||
version = "0.5.1"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"axum-client-ip",
|
||||
@@ -1159,7 +1159,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "conduwuit_service"
|
||||
version = "0.5.0-rc.8.1"
|
||||
version = "0.5.1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
@@ -1200,7 +1200,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "conduwuit_web"
|
||||
version = "0.5.0-rc.8.1"
|
||||
version = "0.5.1"
|
||||
dependencies = [
|
||||
"askama",
|
||||
"axum 0.7.9",
|
||||
@@ -1632,6 +1632,16 @@ dependencies = [
|
||||
"litrs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "draupnir-antispam"
|
||||
version = "0.1.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"ruma-common",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dtor"
|
||||
version = "0.1.0"
|
||||
@@ -1750,7 +1760,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2405,7 +2415,7 @@ dependencies = [
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.1",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -2982,6 +2992,16 @@ version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "meowlnir-antispam"
|
||||
version = "0.1.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"ruma-common",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
@@ -3121,7 +3141,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3305,6 +3325,8 @@ dependencies = [
|
||||
"prost",
|
||||
"reqwest",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tonic",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -3747,7 +3769,7 @@ dependencies = [
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.1",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -3784,9 +3806,9 @@ dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.1",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4063,11 +4085,13 @@ checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3"
|
||||
[[package]]
|
||||
name = "ruma"
|
||||
version = "0.10.1"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"assign",
|
||||
"draupnir-antispam",
|
||||
"js_int",
|
||||
"js_option",
|
||||
"meowlnir-antispam",
|
||||
"ruma-appservice-api",
|
||||
"ruma-client-api",
|
||||
"ruma-common",
|
||||
@@ -4083,7 +4107,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-appservice-api"
|
||||
version = "0.10.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
@@ -4095,7 +4119,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-client-api"
|
||||
version = "0.18.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"assign",
|
||||
@@ -4118,7 +4142,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-common"
|
||||
version = "0.13.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"base64 0.22.1",
|
||||
@@ -4150,7 +4174,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-events"
|
||||
version = "0.28.1"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"indexmap",
|
||||
@@ -4175,7 +4199,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-federation-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"headers",
|
||||
@@ -4197,7 +4221,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-identifiers-validation"
|
||||
version = "0.9.5"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"thiserror 2.0.17",
|
||||
@@ -4206,7 +4230,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-identity-service-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
@@ -4216,7 +4240,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-macros"
|
||||
version = "0.13.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro-crate",
|
||||
@@ -4231,7 +4255,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-push-gateway-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
@@ -4243,7 +4267,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-signatures"
|
||||
version = "0.15.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=79abd5d331bca596b7f37e367a9f2cebccd9f64d#79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"ed25519-dalek",
|
||||
@@ -4323,7 +4347,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6204,7 +6228,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.5.0-rc.8.1"
|
||||
version = "0.5.1"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"serde",
|
||||
@@ -6213,7 +6237,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "xtask-generate-commands"
|
||||
version = "0.5.0-rc.8.1"
|
||||
version = "0.5.1"
|
||||
dependencies = [
|
||||
"clap-markdown",
|
||||
"clap_builder",
|
||||
|
||||
128
Cargo.toml
128
Cargo.toml
@@ -21,7 +21,7 @@ license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
|
||||
rust-version = "1.86.0"
|
||||
version = "0.5.0-rc.8.1"
|
||||
version = "0.5.1"
|
||||
|
||||
[workspace.metadata.crane]
|
||||
name = "conduwuit"
|
||||
@@ -33,11 +33,11 @@ features = ["serde"]
|
||||
[workspace.dependencies.smallvec]
|
||||
version = "1.14.0"
|
||||
features = [
|
||||
"const_generics",
|
||||
"const_new",
|
||||
"serde",
|
||||
"union",
|
||||
"write",
|
||||
"const_generics",
|
||||
"const_new",
|
||||
"serde",
|
||||
"union",
|
||||
"write",
|
||||
]
|
||||
|
||||
[workspace.dependencies.smallstr]
|
||||
@@ -96,13 +96,13 @@ version = "1.11.1"
|
||||
version = "0.7.9"
|
||||
default-features = false
|
||||
features = [
|
||||
"form",
|
||||
"http1",
|
||||
"http2",
|
||||
"json",
|
||||
"matched-path",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"form",
|
||||
"http1",
|
||||
"http2",
|
||||
"json",
|
||||
"matched-path",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[workspace.dependencies.axum-extra]
|
||||
@@ -149,10 +149,10 @@ features = ["aws_lc_rs"]
|
||||
version = "0.12.15"
|
||||
default-features = false
|
||||
features = [
|
||||
"rustls-tls-native-roots",
|
||||
"socks",
|
||||
"hickory-dns",
|
||||
"http2",
|
||||
"rustls-tls-native-roots",
|
||||
"socks",
|
||||
"hickory-dns",
|
||||
"http2",
|
||||
]
|
||||
|
||||
[workspace.dependencies.serde]
|
||||
@@ -188,18 +188,18 @@ default-features = false
|
||||
version = "0.25.5"
|
||||
default-features = false
|
||||
features = [
|
||||
"jpeg",
|
||||
"png",
|
||||
"gif",
|
||||
"webp",
|
||||
"jpeg",
|
||||
"png",
|
||||
"gif",
|
||||
"webp",
|
||||
]
|
||||
|
||||
[workspace.dependencies.blurhash]
|
||||
version = "0.2.3"
|
||||
default-features = false
|
||||
features = [
|
||||
"fast-linear-to-srgb",
|
||||
"image",
|
||||
"fast-linear-to-srgb",
|
||||
"image",
|
||||
]
|
||||
|
||||
# logging
|
||||
@@ -229,13 +229,13 @@ default-features = false
|
||||
version = "4.5.35"
|
||||
default-features = false
|
||||
features = [
|
||||
"derive",
|
||||
"env",
|
||||
"error-context",
|
||||
"help",
|
||||
"std",
|
||||
"string",
|
||||
"usage",
|
||||
"derive",
|
||||
"env",
|
||||
"error-context",
|
||||
"help",
|
||||
"std",
|
||||
"string",
|
||||
"usage",
|
||||
]
|
||||
|
||||
[workspace.dependencies.futures]
|
||||
@@ -247,15 +247,15 @@ features = ["std", "async-await"]
|
||||
version = "1.44.2"
|
||||
default-features = false
|
||||
features = [
|
||||
"fs",
|
||||
"net",
|
||||
"macros",
|
||||
"sync",
|
||||
"signal",
|
||||
"time",
|
||||
"rt-multi-thread",
|
||||
"io-util",
|
||||
"tracing",
|
||||
"fs",
|
||||
"net",
|
||||
"macros",
|
||||
"sync",
|
||||
"signal",
|
||||
"time",
|
||||
"rt-multi-thread",
|
||||
"io-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[workspace.dependencies.tokio-metrics]
|
||||
@@ -280,18 +280,18 @@ default-features = false
|
||||
version = "1.6.0"
|
||||
default-features = false
|
||||
features = [
|
||||
"server",
|
||||
"http1",
|
||||
"http2",
|
||||
"server",
|
||||
"http1",
|
||||
"http2",
|
||||
]
|
||||
|
||||
[workspace.dependencies.hyper-util]
|
||||
version = "=0.1.17"
|
||||
default-features = false
|
||||
features = [
|
||||
"server-auto",
|
||||
"server-graceful",
|
||||
"tokio",
|
||||
"server-auto",
|
||||
"server-graceful",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
# to support multiple variations of setting a config option
|
||||
@@ -310,9 +310,9 @@ features = ["env", "toml"]
|
||||
version = "0.25.1"
|
||||
default-features = false
|
||||
features = [
|
||||
"serde",
|
||||
"system-config",
|
||||
"tokio",
|
||||
"serde",
|
||||
"system-config",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
# Used for conduwuit::Error type
|
||||
@@ -351,7 +351,7 @@ version = "0.1.2"
|
||||
# Used for matrix spec type definitions and helpers
|
||||
[workspace.dependencies.ruma]
|
||||
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
|
||||
rev = "50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
rev = "79abd5d331bca596b7f37e367a9f2cebccd9f64d"
|
||||
features = [
|
||||
"compat",
|
||||
"rand",
|
||||
@@ -381,13 +381,13 @@ features = [
|
||||
"unstable-msc4095",
|
||||
"unstable-msc4121",
|
||||
"unstable-msc4125",
|
||||
"unstable-msc4155",
|
||||
"unstable-msc4155",
|
||||
"unstable-msc4186",
|
||||
"unstable-msc4203", # sending to-device events to appservices
|
||||
"unstable-msc4210", # remove legacy mentions
|
||||
"unstable-extensible-events",
|
||||
"unstable-pdu",
|
||||
"unstable-msc4155"
|
||||
"unstable-msc4155"
|
||||
]
|
||||
|
||||
[workspace.dependencies.rust-rocksdb]
|
||||
@@ -395,11 +395,11 @@ git = "https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1"
|
||||
rev = "61d9d23872197e9ace4a477f2617d5c9f50ecb23"
|
||||
default-features = false
|
||||
features = [
|
||||
"multi-threaded-cf",
|
||||
"mt_static",
|
||||
"lz4",
|
||||
"zstd",
|
||||
"bzip2",
|
||||
"multi-threaded-cf",
|
||||
"mt_static",
|
||||
"lz4",
|
||||
"zstd",
|
||||
"bzip2",
|
||||
]
|
||||
|
||||
[workspace.dependencies.sha2]
|
||||
@@ -426,7 +426,7 @@ features = ["rt-tokio"]
|
||||
|
||||
[workspace.dependencies.opentelemetry-otlp]
|
||||
version = "0.31.0"
|
||||
features = ["http", "trace", "logs", "metrics"]
|
||||
features = ["http", "grpc-tonic", "trace", "logs", "metrics"]
|
||||
|
||||
|
||||
|
||||
@@ -458,16 +458,16 @@ git = "https://forgejo.ellis.link/continuwuation/jemallocator"
|
||||
rev = "82af58d6a13ddd5dcdc7d4e91eae3b63292995b8"
|
||||
default-features = false
|
||||
features = [
|
||||
"background_threads_runtime_support",
|
||||
"unprefixed_malloc_on_supported_platforms",
|
||||
"background_threads_runtime_support",
|
||||
"unprefixed_malloc_on_supported_platforms",
|
||||
]
|
||||
[workspace.dependencies.tikv-jemallocator]
|
||||
git = "https://forgejo.ellis.link/continuwuation/jemallocator"
|
||||
rev = "82af58d6a13ddd5dcdc7d4e91eae3b63292995b8"
|
||||
default-features = false
|
||||
features = [
|
||||
"background_threads_runtime_support",
|
||||
"unprefixed_malloc_on_supported_platforms",
|
||||
"background_threads_runtime_support",
|
||||
"unprefixed_malloc_on_supported_platforms",
|
||||
]
|
||||
[workspace.dependencies.tikv-jemalloc-ctl]
|
||||
git = "https://forgejo.ellis.link/continuwuation/jemallocator"
|
||||
@@ -491,9 +491,9 @@ default-features = false
|
||||
version = "0.1.2"
|
||||
default-features = false
|
||||
features = [
|
||||
"static",
|
||||
"gcc",
|
||||
"light",
|
||||
"static",
|
||||
"gcc",
|
||||
"light",
|
||||
]
|
||||
|
||||
[workspace.dependencies.rustyline-async]
|
||||
|
||||
1
changelog.d/+6de5f7b2.misc.md
Normal file
1
changelog.d/+6de5f7b2.misc.md
Normal file
@@ -0,0 +1 @@
|
||||
The `console` feature is now enabled by default, allowing the server console to be used for running admin commands directly.
|
||||
1
changelog.d/+f4a756d9.feature.md
Normal file
1
changelog.d/+f4a756d9.feature.md
Normal file
@@ -0,0 +1 @@
|
||||
Certain potentially dangerous admin commands are now restricted to only be usable in the admin room and server console.
|
||||
1
changelog.d/1253.feature
Normal file
1
changelog.d/1253.feature
Normal file
@@ -0,0 +1 @@
|
||||
Implemented a configuration defined admin list independent of the admin room. (@Terryiscool160).
|
||||
1
changelog.d/1257.bugfix
Normal file
1
changelog.d/1257.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Fixed unreliable room summary fetching and improved error messages. Contributed by @nex.
|
||||
2
changelog.d/1261.bugfix
Normal file
2
changelog.d/1261.bugfix
Normal file
@@ -0,0 +1,2 @@
|
||||
Client requested timeout parameter is now applied to e2ee key lookups and claims. Related federation requests are now
|
||||
also concurrent. Contributed by @nex.
|
||||
2
changelog.d/1263.feature
Normal file
2
changelog.d/1263.feature
Normal file
@@ -0,0 +1,2 @@
|
||||
Added support for invite and join anti-spam via Draupnir and Meowlnir, similar to that of synapse-http-antispam.
|
||||
Contributed by @nex.
|
||||
1
changelog.d/1266.feature
Normal file
1
changelog.d/1266.feature
Normal file
@@ -0,0 +1 @@
|
||||
Implemented account locking functionality, to complement user suspension. Contributed by @nex.
|
||||
@@ -26,8 +26,8 @@
|
||||
# Also see the `[global.well_known]` config section at the very bottom.
|
||||
#
|
||||
# Examples of delegation:
|
||||
# - https://puppygock.gay/.well-known/matrix/server
|
||||
# - https://puppygock.gay/.well-known/matrix/client
|
||||
# - https://continuwuity.org/.well-known/matrix/server
|
||||
# - https://continuwuity.org/.well-known/matrix/client
|
||||
#
|
||||
# YOU NEED TO EDIT THIS. THIS CANNOT BE CHANGED AFTER WITHOUT A DATABASE
|
||||
# WIPE.
|
||||
@@ -586,10 +586,13 @@
|
||||
#allow_unstable_room_versions = true
|
||||
|
||||
# Default room version continuwuity will create rooms with.
|
||||
# Note that this has to be a string since the room version is a string
|
||||
# rather than an integer. Forgetting the quotes will make the server fail
|
||||
# to start!
|
||||
#
|
||||
# Per spec, room version 11 is the default.
|
||||
# Per spec, room version "11" is the default.
|
||||
#
|
||||
#default_room_version = 11
|
||||
#default_room_version = "11"
|
||||
|
||||
# Enable OpenTelemetry OTLP tracing export. This replaces the deprecated
|
||||
# Jaeger exporter. Traces will be sent via OTLP to a collector (such as
|
||||
@@ -605,6 +608,11 @@
|
||||
#
|
||||
#otlp_filter = "info"
|
||||
|
||||
# Protocol to use for OTLP tracing export. Options are "http" or "grpc".
|
||||
# The HTTP protocol uses port 4318 by default, while gRPC uses port 4317.
|
||||
#
|
||||
#otlp_protocol = "http"
|
||||
|
||||
# If the 'perf_measurements' compile-time feature is enabled, enables
|
||||
# collecting folded stack trace profile of tracing spans using
|
||||
# tracing_flame. The resulting profile can be visualized with inferno[1],
|
||||
@@ -1530,7 +1538,7 @@
|
||||
# a normal continuwuity admin command. The reply will be publicly visible
|
||||
# to the room, originating from the sender.
|
||||
#
|
||||
# example: \\!admin debug ping puppygock.gay
|
||||
# example: \\!admin debug ping continuwuity.org
|
||||
#
|
||||
#admin_escape_commands = true
|
||||
|
||||
@@ -1548,7 +1556,8 @@
|
||||
# For example: `./continuwuity --execute "server admin-notice continuwuity
|
||||
# has started up at $(date)"`
|
||||
#
|
||||
# example: admin_execute = ["debug ping puppygock.gay", "debug echo hi"]`
|
||||
# example: admin_execute = ["debug ping continuwuity.org", "debug echo
|
||||
# hi"]`
|
||||
#
|
||||
#admin_execute = []
|
||||
|
||||
@@ -1581,6 +1590,18 @@
|
||||
#
|
||||
#admin_room_tag = "m.server_notice"
|
||||
|
||||
# A list of Matrix IDs that are qualified as server admins.
|
||||
#
|
||||
# Any Matrix IDs within this list are regarded as an admin
|
||||
# regardless of whether they are in the admin room or not
|
||||
#
|
||||
#admins_list = []
|
||||
|
||||
# Defines whether those within the admin room are added to the
|
||||
# admins_list.
|
||||
#
|
||||
#admins_from_room = true
|
||||
|
||||
# Sentry.io crash/panic reporting, performance monitoring/metrics, etc.
|
||||
# This is NOT enabled by default.
|
||||
#
|
||||
@@ -1626,7 +1647,7 @@
|
||||
|
||||
# Enable the tokio-console. This option is only relevant to developers.
|
||||
#
|
||||
# For more information, see:
|
||||
# For more information, see:
|
||||
# https://continuwuity.org/development.html#debugging-with-tokio-console
|
||||
#
|
||||
#tokio_console = false
|
||||
@@ -1902,3 +1923,41 @@
|
||||
# example: "(objectClass=conduwuitAdmin)" or "(uid={username})"
|
||||
#
|
||||
#admin_filter = ""
|
||||
|
||||
[global.antispam.meowlnir]
|
||||
|
||||
# The base URL on which to contact Meowlnir (before /_meowlnir/antispam).
|
||||
#
|
||||
# Example: "http://127.0.0.1:29339"
|
||||
#
|
||||
#base_url =
|
||||
|
||||
# The authentication secret defined in antispam->secret. Required for
|
||||
# continuwuity to talk to Meowlnir.
|
||||
#
|
||||
#secret =
|
||||
|
||||
# The management room for which to send requests
|
||||
#
|
||||
#management_room =
|
||||
|
||||
# If enabled run all federated join attempts (both federated and local)
|
||||
# through the Meowlnir anti-spam checks.
|
||||
#
|
||||
# By default, only join attempts for rooms with the `fi.mau.spam_checker`
|
||||
# restricted join rule are checked.
|
||||
#
|
||||
#check_all_joins = false
|
||||
|
||||
[global.antispam.draupnir]
|
||||
|
||||
# The base URL on which to contact Draupnir (before /api/).
|
||||
#
|
||||
# Example: "http://127.0.0.1:29339"
|
||||
#
|
||||
#base_url =
|
||||
|
||||
# The authentication secret defined in
|
||||
# web->synapseHTTPAntispam->authorization
|
||||
#
|
||||
#secret =
|
||||
|
||||
@@ -48,7 +48,7 @@ EOF
|
||||
|
||||
# Developer tool versions
|
||||
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
|
||||
ENV BINSTALL_VERSION=1.16.2
|
||||
ENV BINSTALL_VERSION=1.16.6
|
||||
# renovate: datasource=github-releases depName=psastras/sbom-rs
|
||||
ENV CARGO_SBOM_VERSION=0.9.1
|
||||
# renovate: datasource=crate depName=lddtree
|
||||
|
||||
@@ -18,7 +18,7 @@ RUN --mount=type=cache,target=/etc/apk/cache apk add \
|
||||
|
||||
# Developer tool versions
|
||||
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
|
||||
ENV BINSTALL_VERSION=1.16.2
|
||||
ENV BINSTALL_VERSION=1.16.6
|
||||
# renovate: datasource=github-releases depName=psastras/sbom-rs
|
||||
ENV CARGO_SBOM_VERSION=0.9.1
|
||||
# renovate: datasource=crate depName=lddtree
|
||||
|
||||
@@ -114,6 +114,10 @@ services:
|
||||
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_HTTPCHALLENGE_ENTRYPOINT: web
|
||||
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_STORAGE: "/etc/traefik/acme/acme.json"
|
||||
|
||||
# Since Traefik 3.6.3, paths with certain "encoded characters" are now blocked by default; we need a couple, or else things *will* break
|
||||
TRAEFIK_ENTRYPOINTS_WEBSECURE_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDSLASH: true
|
||||
TRAEFIK_ENTRYPOINTS_WEBSECURE_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDHASH: true
|
||||
|
||||
TRAEFIK_PROVIDERS_DOCKER: true
|
||||
TRAEFIK_PROVIDERS_DOCKER_ENDPOINT: "unix:///var/run/docker.sock"
|
||||
TRAEFIK_PROVIDERS_DOCKER_EXPOSEDBYDEFAULT: false
|
||||
|
||||
@@ -11,10 +11,10 @@ ### Use a registry
|
||||
|
||||
| Registry | Image | Notes |
|
||||
| --------------- | --------------------------------------------------------------- | -----------------------|
|
||||
| Forgejo Registry| [forgejo.ellis.link/continuwuation/continuwuity:latest][fj] | Latest tagged image. |
|
||||
| Forgejo Registry| [forgejo.ellis.link/continuwuation/continuwuity:main][fj] | Main branch image. |
|
||||
|
||||
[fj]: https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity
|
||||
| Forgejo Registry| [forgejo.ellis.link/continuwuation/continuwuity:latest](https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity/latest) | Latest tagged image. |
|
||||
| Forgejo Registry| [forgejo.ellis.link/continuwuation/continuwuity:main](https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity/main) | Main branch image. |
|
||||
| Forgejo Registry| [forgejo.ellis.link/continuwuation/continuwuity:latest-maxperf](https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity/latest-maxperf) | Performance optimised version. |
|
||||
| Forgejo Registry| [forgejo.ellis.link/continuwuation/continuwuity:main-maxperf](https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity/main-maxperf) | Performance optimised version. |
|
||||
|
||||
Use
|
||||
|
||||
@@ -24,6 +24,15 @@ ### Use a registry
|
||||
|
||||
to pull it to your machine.
|
||||
|
||||
#### Mirrors
|
||||
|
||||
Images are mirrored to multiple locations automatically, on a schedule:
|
||||
|
||||
- `ghcr.io/continuwuity/continuwuity`
|
||||
- `docker.io/jadedblueeyes/continuwuity`
|
||||
- `registry.gitlab.com/continuwuity/continuwuity`
|
||||
- `git.nexy7574.co.uk/mirrored/continuwuity` (releases only, no `main`)
|
||||
|
||||
### Run
|
||||
|
||||
When you have the image, you can simply run it with
|
||||
@@ -49,7 +58,7 @@ ### Run
|
||||
flag, which cleans up everything related to your container after you stop
|
||||
it.
|
||||
|
||||
### Docker-compose
|
||||
### Docker Compose
|
||||
|
||||
If the `docker run` command is not suitable for you or your setup, you can also use one
|
||||
of the provided `docker-compose` files.
|
||||
@@ -158,8 +167,19 @@ # Build for the current platform and load into the local Docker daemon
|
||||
# Example: Build for specific platforms and push to a registry.
|
||||
# docker buildx build --platform linux/amd64,linux/arm64 --tag registry.io/org/continuwuity:latest -f docker/Dockerfile . --push
|
||||
|
||||
# Example: Build binary optimized for the current CPU
|
||||
# docker buildx build --load --tag continuwuity:latest --build-arg TARGET_CPU=native -f docker/Dockerfile .
|
||||
# Example: Build binary optimised for the current CPU (standard release profile)
|
||||
# docker buildx build --load \
|
||||
# --tag continuwuity:latest \
|
||||
# --build-arg TARGET_CPU=native \
|
||||
# -f docker/Dockerfile .
|
||||
|
||||
# Example: Build maxperf variant (release-max-perf profile with LTO)
|
||||
# Optimised for runtime performance and smaller binary size, but requires longer build time
|
||||
# docker buildx build --load \
|
||||
# --tag continuwuity:latest-maxperf \
|
||||
# --build-arg TARGET_CPU=native \
|
||||
# --build-arg RUST_PROFILE=release-max-perf \
|
||||
# -f docker/Dockerfile .
|
||||
```
|
||||
|
||||
Refer to the Docker Buildx documentation for more advanced build options.
|
||||
@@ -198,5 +218,3 @@ ### Use Traefik as Proxy
|
||||
## Voice communication
|
||||
|
||||
See the [TURN](../turn.md) page.
|
||||
|
||||
[nix-buildlayeredimage]: https://ryantm.github.io/nixpkgs/builders/images/dockertools/#ssec-pkgs-dockerTools-buildLayeredImage
|
||||
|
||||
@@ -8,29 +8,39 @@ # Generic deployment documentation
|
||||
|
||||
## Installing Continuwuity
|
||||
|
||||
### Static prebuilt binary
|
||||
### Prebuilt binary
|
||||
|
||||
You may simply download the binary that fits your machine architecture (x86_64
|
||||
or aarch64). Run `uname -m` to see what you need.
|
||||
Download the binary for your architecture (x86_64 or aarch64) -
|
||||
run the `uname -m` to check which you need.
|
||||
|
||||
You can download prebuilt fully static musl binaries from the latest tagged
|
||||
release [here](https://forgejo.ellis.link/continuwuation/continuwuity/releases/latest) or
|
||||
from the `main` CI branch workflow artifact output. These also include Debian/Ubuntu
|
||||
packages.
|
||||
Prebuilt binaries are available from:
|
||||
- **Tagged releases**: [Latest release page](https://forgejo.ellis.link/continuwuation/continuwuity/releases/latest)
|
||||
- **Development builds**: CI artifacts from the `main` branch
|
||||
(includes Debian/Ubuntu packages)
|
||||
|
||||
You can download these directly using curl. The `ci-bins` are CI workflow binaries organized by commit
|
||||
hash/revision, and `releases` are tagged releases. Sort by descending last
|
||||
modified date to find the latest.
|
||||
When browsing CI artifacts, `ci-bins` contains binaries organised
|
||||
by commit hash, while `releases` contains tagged versions. Sort
|
||||
by last modified date to find the most recent builds.
|
||||
|
||||
These binaries have jemalloc and io_uring statically linked and included with
|
||||
them, so no additional dynamic dependencies need to be installed.
|
||||
The binaries require jemalloc and io_uring on the host system. Currently
|
||||
we can't cross-build static binaries - contributions are welcome here.
|
||||
|
||||
For the **best** performance: if you are using an `x86_64` CPU made in the last ~15 years,
|
||||
we recommend using the `-haswell-` optimized binaries. These set
|
||||
`-march=haswell`, which provides the most compatible and highest performance with
|
||||
optimized binaries. The database backend, RocksDB, benefits most from this as it
|
||||
uses hardware-accelerated CRC32 hashing/checksumming, which is critical
|
||||
for performance.
|
||||
#### Performance-optimised builds
|
||||
|
||||
For x86_64 systems with CPUs from the last ~15 years, use the
|
||||
`-haswell-` optimised binaries for best performance. These
|
||||
binaries enable hardware-accelerated CRC32 checksumming in
|
||||
RocksDB, which significantly improves database performance.
|
||||
The haswell instruction set provides an excellent balance of
|
||||
compatibility and speed.
|
||||
|
||||
If you're using Docker instead, equivalent performance-optimised
|
||||
images are available with the `-maxperf` suffix (e.g.
|
||||
`forgejo.ellis.link/continuwuation/continuwuity:latest-maxperf`).
|
||||
These images use the `release-max-perf`
|
||||
build profile with
|
||||
[link-time optimisation (LTO)](https://doc.rust-lang.org/cargo/reference/profiles.html#lto)
|
||||
and, for amd64, target the haswell CPU architecture.
|
||||
|
||||
### Compiling
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ ### Log Levels
|
||||
```rs
|
||||
// Good
|
||||
error!(
|
||||
error = %err,
|
||||
error = ?err,
|
||||
room_id = %room_id,
|
||||
"Failed to send event to room"
|
||||
);
|
||||
@@ -264,7 +264,7 @@ ### Code Comments
|
||||
warn!(
|
||||
destination = %destination,
|
||||
attempt = attempt,
|
||||
error = %err,
|
||||
error = ?err,
|
||||
retry_delay_ms = retry_delay.as_millis(),
|
||||
"Federation request failed, retrying"
|
||||
);
|
||||
|
||||
@@ -149,11 +149,12 @@ ### Creating pull requests
|
||||
*looks* done.
|
||||
|
||||
Before submitting a pull request, please ensure:
|
||||
1. Your code passes all CI checks (formatting, linting, typo detection, etc.)
|
||||
1. Your code passes all CI checks (formatting, linting, typo detection, etc.). Run pre-commit for this.
|
||||
2. Your code follows the [code style guide](./code_style)
|
||||
3. Your commit messages follow the conventional commits format
|
||||
4. Tests are added for new functionality
|
||||
5. Documentation is updated if needed
|
||||
6. You have written a [news fragment](#writing-news-fragments) for your changes
|
||||
|
||||
Direct all PRs/MRs to the `main` branch.
|
||||
|
||||
@@ -171,3 +172,32 @@ ### Creating pull requests
|
||||
[sytest]: https://github.com/matrix-org/sytest/
|
||||
[mdbook]: https://rust-lang.github.io/mdBook/
|
||||
[documentation.yml]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/.forgejo/workflows/documentation.yml
|
||||
|
||||
#### Writing news fragments
|
||||
|
||||
In order to make writing our changelogs easier, we make use of [Towncrier]. Towncrier builds changelogs based on
|
||||
"news fragments", which are little markdown files in the `changelog.d/` directory that describe individual changes.
|
||||
|
||||
When you make a pull request that changes functionality, fixes a bug, or adds documentation, please add a news fragment
|
||||
describing your change. The file name *MUST* be in the format of `{pull_request_number}.{type}`, where `{type}` is one
|
||||
of the following:
|
||||
|
||||
- `feature` - for new features
|
||||
- `bugfix` - for bug fixes
|
||||
- `doc` - for documentation changes
|
||||
- `misc` - for other changes that don't fit the above categories
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
$ echo "Fixed the quantum flux stabiliser. Contributed by @alice." > changelog.d/42.bugfix
|
||||
```
|
||||
|
||||
(Note: If you want to credit yourself, you should reference your forgejo handle, however links to other platforms are also acceptable.)
|
||||
|
||||
When the next release is made, Towncrier will automatically include your news fragment in the changelog.
|
||||
|
||||
You can read more about writing news fragments in the [Towncrier tutorial][tt].
|
||||
|
||||
[Towncrier]: https://towncrier.readthedocs.io/
|
||||
[tt]: https://towncrier.readthedocs.io/en/stable/tutorial.html#creating-news-fragments
|
||||
|
||||
@@ -6,12 +6,10 @@
|
||||
"message": "Welcome to Continuwuity! Important announcements about the project will appear here."
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"message": "_taps microphone_ The Continuwuity 0.5.0-rc.7 release is now available, and it's better than ever! **177 commits**, **35 pull requests**, **11 contributors,** and a lot of new stuff!\n\nFor highlights, we've got:\n\n* 🕵️ Full Policy Server support to fight spam!\n* 🚀 Smarter room & space upgrades.\n* 🚫 User suspension tools for better moderation.\n* 🤖 reCaptcha support for safer open registration.\n* 🔍 Ability to disable read receipts & typing indicators.\n* ⚡ Sweeping performance improvements!\n\nGet the [full changelog and downloads on our Forgejo](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.0-rc.7) - and make sure you're in the [Announcements room](https://matrix.to/#/!releases:continuwuity.org/$hN9z6L2_dTAlPxFLAoXVfo_g8DyYXu4cpvWsSrWhmB0) to get stuff like this sooner."
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"message": "It's a bird! It's a plane! No, it's 0.5.0-rc.8.1!\n\nThis is a minor bugfix update to the rc8 which backports some important fixes from the latest main branch. If you still haven't updated to rc8, you should skip to main. Otherwise, you should upgrade to this bugfix release as soon as possible.\n\nBugfixes backported to this version:\n\n- Resolved several issues with state resolution v2.1 (room version 12)\n- Fixed issues with the `restricted` and `knock_restricted` join rules that would sometimes incorrectly disallow a valid join\n- Fixed the automatic support contact listing being a no-op\n- Fixed upgrading pre-v12 rooms to v12 rooms\n- Fixed policy servers sending the incorrect JSON objects (resulted in false positives)\n- Fixed debug build panic during MSC4133 migration\n\nIt is recommended, if you can and are comfortable with doing so, following updates to the main branch - we're in the run up to the full 0.5.0 release, and more and more bugfixes and new features are being pushed constantly. Please don't forget to join [#announcements:continuwuity.org](https://matrix.to/#/#announcements:continuwuity.org) to receive this news faster and be alerted to other important updates!"
|
||||
"id": 7,
|
||||
"mention_room": true,
|
||||
"date": "2025-12-30",
|
||||
"message": "Continuwuity v0.5.1 has been released. **The release contains a fix for the critical vulnerability [GHSA-m5p2-vccg-8c9v](https://github.com/continuwuity/continuwuity/security/advisories/GHSA-m5p2-vccg-8c9v) (embargoed) affecting all Conduit-derived servers. Update as soon as possible.**\n\nThis has been *actively exploited* to attempt account takeover and forge events bricking the Continuwuity rooms. The new space is accessible at [Continuwuity (room list)](https://matrix.to/#/!8cR4g-i9ucof69E4JHNg9LbPVkGprHb3SzcrGBDDJgk?via=continuwuity.org&via=starstruck.systems&via=gingershaped.computer)\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
54
flake.lock
generated
54
flake.lock
generated
@@ -3,11 +3,11 @@
|
||||
"advisory-db": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1761112158,
|
||||
"narHash": "sha256-RIXu/7eyKpQHjsPuAUODO81I4ni8f+WYSb7K4mTG6+0=",
|
||||
"lastModified": 1766324728,
|
||||
"narHash": "sha256-9C+WyE5U3y5w4WQXxmb0ylRyMMsPyzxielWXSHrcDpE=",
|
||||
"owner": "rustsec",
|
||||
"repo": "advisory-db",
|
||||
"rev": "58f3aaec0e1776f4a900737be8cd7cb00972210d",
|
||||
"rev": "c88b88c62bda077be8aa621d4e89d8701e39cb5d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -18,11 +18,11 @@
|
||||
},
|
||||
"crane": {
|
||||
"locked": {
|
||||
"lastModified": 1760924934,
|
||||
"narHash": "sha256-tuuqY5aU7cUkR71sO2TraVKK2boYrdW3gCSXUkF4i44=",
|
||||
"lastModified": 1766194365,
|
||||
"narHash": "sha256-4AFsUZ0kl6MXSm4BaQgItD0VGlEKR3iq7gIaL7TjBvc=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "c6b4d5308293d0d04fcfeee92705017537cad02f",
|
||||
"rev": "7d8ec2c71771937ab99790b45e6d9b93d15d9379",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -39,11 +39,11 @@
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1761115517,
|
||||
"narHash": "sha256-Fev/ag/c3Fp3JBwHfup3lpA5FlNXfkoshnQ7dssBgJ0=",
|
||||
"lastModified": 1766299592,
|
||||
"narHash": "sha256-7u+q5hexu2eAxL2VjhskHvaUKg+GexmelIR2ve9Nbb4=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "320433651636186ea32b387cff05d6bbfa30cea7",
|
||||
"rev": "381579dee168d5ced412e2990e9637ecc7cf1c5d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -55,11 +55,11 @@
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1747046372,
|
||||
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
|
||||
"lastModified": 1765121682,
|
||||
"narHash": "sha256-4VBOP18BFeiPkyhy9o4ssBNQEvfvv1kXkasAYd0+rrA=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
|
||||
"rev": "65f23138d8d09a92e30f1e5c87611b23ef451bf3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -74,11 +74,11 @@
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1760948891,
|
||||
"narHash": "sha256-TmWcdiUUaWk8J4lpjzu4gCGxWY6/Ok7mOK4fIFfBuU4=",
|
||||
"lastModified": 1765835352,
|
||||
"narHash": "sha256-XswHlK/Qtjasvhd1nOa1e8MgZ8GS//jBoTqWtrS1Giw=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "864599284fc7c0ba6357ed89ed5e2cd5040f0c04",
|
||||
"rev": "a34fae9c08a15ad73f295041fec82323541400a9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -89,11 +89,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1760878510,
|
||||
"narHash": "sha256-K5Osef2qexezUfs0alLvZ7nQFTGS9DL2oTVsIXsqLgs=",
|
||||
"lastModified": 1766070988,
|
||||
"narHash": "sha256-G/WVghka6c4bAzMhTwT2vjLccg/awmHkdKSd2JrycLc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "5e2a59a5b1a82f89f2c7e598302a9cacebb72a67",
|
||||
"rev": "c6245e83d836d0433170a16eb185cefe0572f8b8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -105,11 +105,11 @@
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1754788789,
|
||||
"narHash": "sha256-x2rJ+Ovzq0sCMpgfgGaaqgBSwY+LST+WbZ6TytnT9Rk=",
|
||||
"lastModified": 1765674936,
|
||||
"narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "a73b9c743612e4244d865a2fdee11865283c04e6",
|
||||
"rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -132,11 +132,11 @@
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1761077270,
|
||||
"narHash": "sha256-O1uTuvI/rUlubJ8AXKyzh1WSWV3qCZX0huTFUvWLN4E=",
|
||||
"lastModified": 1766253897,
|
||||
"narHash": "sha256-ChK07B1aOlJ4QzWXpJo+y8IGAxp1V9yQ2YloJ+RgHRw=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "39990a923c8bca38f5bd29dc4c96e20ee7808d5d",
|
||||
"rev": "765b7bdb432b3740f2d564afccfae831d5a972e4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -153,11 +153,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1760945191,
|
||||
"narHash": "sha256-ZRVs8UqikBa4Ki3X4KCnMBtBW0ux1DaT35tgsnB1jM4=",
|
||||
"lastModified": 1766000401,
|
||||
"narHash": "sha256-+cqN4PJz9y0JQXfAK5J1drd0U05D5fcAGhzhfVrDlsI=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "f56b1934f5f8fcab8deb5d38d42fd692632b47c2",
|
||||
"rev": "42d96e75aa56a3f70cab7e7dc4a32868db28e8fd",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -6,6 +6,69 @@
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
baseTestScript =
|
||||
pkgs.writers.writePython3Bin "do_test" { libraries = [ pkgs.python3Packages.matrix-nio ]; }
|
||||
''
|
||||
import asyncio
|
||||
import nio
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# Connect to continuwuity
|
||||
client = nio.AsyncClient("http://continuwuity:6167", "alice")
|
||||
|
||||
# Register as user alice
|
||||
response = await client.register("alice", "my-secret-password")
|
||||
|
||||
# Log in as user alice
|
||||
response = await client.login("my-secret-password")
|
||||
|
||||
# Create a new room
|
||||
response = await client.room_create(federate=False)
|
||||
print("Matrix room create response:", response)
|
||||
assert isinstance(response, nio.RoomCreateResponse)
|
||||
room_id = response.room_id
|
||||
|
||||
# Join the room
|
||||
response = await client.join(room_id)
|
||||
print("Matrix join response:", response)
|
||||
assert isinstance(response, nio.JoinResponse)
|
||||
|
||||
# Send a message to the room
|
||||
response = await client.room_send(
|
||||
room_id=room_id,
|
||||
message_type="m.room.message",
|
||||
content={
|
||||
"msgtype": "m.text",
|
||||
"body": "Hello continuwuity!"
|
||||
}
|
||||
)
|
||||
print("Matrix room send response:", response)
|
||||
assert isinstance(response, nio.RoomSendResponse)
|
||||
|
||||
# Sync responses
|
||||
response = await client.sync(timeout=30000)
|
||||
print("Matrix sync response:", response)
|
||||
assert isinstance(response, nio.SyncResponse)
|
||||
|
||||
# Check the message was received by continuwuity
|
||||
last_message = response.rooms.join[room_id].timeline.events[-1].body
|
||||
assert last_message == "Hello continuwuity!"
|
||||
|
||||
# Leave the room
|
||||
response = await client.room_leave(room_id)
|
||||
print("Matrix room leave response:", response)
|
||||
assert isinstance(response, nio.RoomLeaveResponse)
|
||||
|
||||
# Close the client
|
||||
await client.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
'';
|
||||
in
|
||||
{
|
||||
# run some nixos tests as checks
|
||||
checks = lib.pipe self'.packages [
|
||||
@@ -18,106 +81,69 @@
|
||||
# this test was initially yoinked from
|
||||
#
|
||||
# https://github.com/NixOS/nixpkgs/blob/960ce26339661b1b69c6f12b9063ca51b688615f/nixos/tests/matrix/continuwuity.nix
|
||||
(builtins.map (name: {
|
||||
name = "test-${name}";
|
||||
value = pkgs.testers.runNixOSTest {
|
||||
inherit name;
|
||||
(builtins.concatMap (
|
||||
name:
|
||||
builtins.map
|
||||
(
|
||||
{ config, suffix }:
|
||||
{
|
||||
name = "test-${name}-${suffix}";
|
||||
value = pkgs.testers.runNixOSTest {
|
||||
inherit name;
|
||||
|
||||
nodes = {
|
||||
continuwuity = {
|
||||
services.matrix-continuwuity = {
|
||||
enable = true;
|
||||
package = self'.packages.${name};
|
||||
settings.global = {
|
||||
nodes = {
|
||||
continuwuity = {
|
||||
services.matrix-continuwuity = {
|
||||
enable = true;
|
||||
package = self'.packages.${name};
|
||||
settings = config;
|
||||
extraEnvironment.RUST_BACKTRACE = "yes";
|
||||
};
|
||||
networking.firewall.allowedTCPPorts = [ 6167 ];
|
||||
};
|
||||
client.environment.systemPackages = [ baseTestScript ];
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
with subtest("start continuwuity"):
|
||||
continuwuity.wait_for_unit("continuwuity.service")
|
||||
continuwuity.wait_for_open_port(6167)
|
||||
|
||||
with subtest("ensure messages can be exchanged"):
|
||||
client.succeed("${lib.getExe baseTestScript} >&2")
|
||||
'';
|
||||
|
||||
};
|
||||
}
|
||||
)
|
||||
[
|
||||
{
|
||||
suffix = "base";
|
||||
config = {
|
||||
global = {
|
||||
server_name = name;
|
||||
address = [ "0.0.0.0" ];
|
||||
allow_registration = true;
|
||||
yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true;
|
||||
};
|
||||
extraEnvironment.RUST_BACKTRACE = "yes";
|
||||
};
|
||||
networking.firewall.allowedTCPPorts = [ 6167 ];
|
||||
};
|
||||
client =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
environment.systemPackages = [
|
||||
(pkgs.writers.writePython3Bin "do_test" { libraries = [ pkgs.python3Packages.matrix-nio ]; } ''
|
||||
import asyncio
|
||||
import nio
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# Connect to continuwuity
|
||||
client = nio.AsyncClient("http://continuwuity:6167", "alice")
|
||||
|
||||
# Register as user alice
|
||||
response = await client.register("alice", "my-secret-password")
|
||||
|
||||
# Log in as user alice
|
||||
response = await client.login("my-secret-password")
|
||||
|
||||
# Create a new room
|
||||
response = await client.room_create(federate=False)
|
||||
print("Matrix room create response:", response)
|
||||
assert isinstance(response, nio.RoomCreateResponse)
|
||||
room_id = response.room_id
|
||||
|
||||
# Join the room
|
||||
response = await client.join(room_id)
|
||||
print("Matrix join response:", response)
|
||||
assert isinstance(response, nio.JoinResponse)
|
||||
|
||||
# Send a message to the room
|
||||
response = await client.room_send(
|
||||
room_id=room_id,
|
||||
message_type="m.room.message",
|
||||
content={
|
||||
"msgtype": "m.text",
|
||||
"body": "Hello continuwuity!"
|
||||
}
|
||||
)
|
||||
print("Matrix room send response:", response)
|
||||
assert isinstance(response, nio.RoomSendResponse)
|
||||
|
||||
# Sync responses
|
||||
response = await client.sync(timeout=30000)
|
||||
print("Matrix sync response:", response)
|
||||
assert isinstance(response, nio.SyncResponse)
|
||||
|
||||
# Check the message was received by continuwuity
|
||||
last_message = response.rooms.join[room_id].timeline.events[-1].body
|
||||
assert last_message == "Hello continuwuity!"
|
||||
|
||||
# Leave the room
|
||||
response = await client.room_leave(room_id)
|
||||
print("Matrix room leave response:", response)
|
||||
assert isinstance(response, nio.RoomLeaveResponse)
|
||||
|
||||
# Close the client
|
||||
await client.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
'')
|
||||
];
|
||||
}
|
||||
{
|
||||
suffix = "with-room-version";
|
||||
config = {
|
||||
global = {
|
||||
server_name = name;
|
||||
address = [ "0.0.0.0" ];
|
||||
allow_registration = true;
|
||||
yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true;
|
||||
default_room_version = "12";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
|
||||
with subtest("start continuwuity"):
|
||||
continuwuity.wait_for_unit("continuwuity.service")
|
||||
continuwuity.wait_for_open_port(6167)
|
||||
|
||||
with subtest("ensure messages can be exchanged"):
|
||||
client.succeed("do_test >&2")
|
||||
'';
|
||||
|
||||
};
|
||||
}))
|
||||
}
|
||||
]
|
||||
))
|
||||
builtins.listToAttrs
|
||||
];
|
||||
};
|
||||
|
||||
1
release.toml
Normal file
1
release.toml
Normal file
@@ -0,0 +1 @@
|
||||
tag-message = "chore: Release v{{version}}"
|
||||
@@ -48,19 +48,31 @@ pub enum AdminCommand {
|
||||
Query(QueryCommand),
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, name = "command")]
|
||||
#[tracing::instrument(skip_all, name = "command", level = "info")]
|
||||
pub(super) async fn process(command: AdminCommand, context: &Context<'_>) -> Result {
|
||||
use AdminCommand::*;
|
||||
|
||||
match command {
|
||||
| Appservices(command) => appservice::process(command, context).await,
|
||||
| Appservices(command) => {
|
||||
// appservice commands are all restricted
|
||||
context.bail_restricted()?;
|
||||
appservice::process(command, context).await
|
||||
},
|
||||
| Media(command) => media::process(command, context).await,
|
||||
| Users(command) => user::process(command, context).await,
|
||||
| Users(command) => {
|
||||
// user commands are all restricted
|
||||
context.bail_restricted()?;
|
||||
user::process(command, context).await
|
||||
},
|
||||
| Rooms(command) => room::process(command, context).await,
|
||||
| Federation(command) => federation::process(command, context).await,
|
||||
| Server(command) => server::process(command, context).await,
|
||||
| Debug(command) => debug::process(command, context).await,
|
||||
| Query(command) => query::process(command, context).await,
|
||||
| Query(command) => {
|
||||
// query commands are all restricted
|
||||
context.bail_restricted()?;
|
||||
query::process(command, context).await
|
||||
},
|
||||
| Check(command) => check::process(command, context).await,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{fmt, time::SystemTime};
|
||||
|
||||
use conduwuit::Result;
|
||||
use conduwuit::{Err, Result};
|
||||
use conduwuit_service::Services;
|
||||
use futures::{
|
||||
Future, FutureExt, TryFutureExt,
|
||||
@@ -8,6 +8,7 @@
|
||||
lock::Mutex,
|
||||
};
|
||||
use ruma::{EventId, UserId};
|
||||
use service::admin::InvocationSource;
|
||||
|
||||
pub(crate) struct Context<'a> {
|
||||
pub(crate) services: &'a Services,
|
||||
@@ -16,6 +17,7 @@ pub(crate) struct Context<'a> {
|
||||
pub(crate) reply_id: Option<&'a EventId>,
|
||||
pub(crate) sender: Option<&'a UserId>,
|
||||
pub(crate) output: Mutex<BufWriter<Vec<u8>>>,
|
||||
pub(crate) source: InvocationSource,
|
||||
}
|
||||
|
||||
impl Context<'_> {
|
||||
@@ -43,4 +45,22 @@ pub(crate) fn sender_or_service_user(&self) -> &UserId {
|
||||
self.sender
|
||||
.unwrap_or_else(|| self.services.globals.server_user.as_ref())
|
||||
}
|
||||
|
||||
/// Returns an Err if the [`Self::source`] of this context does not allow
|
||||
/// restricted commands to be executed.
|
||||
///
|
||||
/// This is intended to be placed at the start of restricted commands'
|
||||
/// implementations, like so:
|
||||
///
|
||||
/// ```ignore
|
||||
/// self.bail_restricted()?;
|
||||
/// // actual command impl
|
||||
/// ```
|
||||
pub(crate) fn bail_restricted(&self) -> Result {
|
||||
if self.source.allows_restricted() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err!("This command can only be used in the admin room.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,6 +291,8 @@ pub(super) async fn get_remote_pdu(
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
let room_id = self.services.rooms.alias.resolve(&room).await?;
|
||||
let room_state: Vec<Raw<AnyStateEvent>> = self
|
||||
.services
|
||||
@@ -417,27 +419,6 @@ pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool)
|
||||
Err!("No log level was specified.")
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn sign_json(&self) -> Result {
|
||||
if self.body.len() < 2
|
||||
|| !self.body[0].trim().starts_with("```")
|
||||
|| self.body.last().unwrap_or(&"").trim() != "```"
|
||||
{
|
||||
return Err!("Expected code block in command body. Add --help for details.");
|
||||
}
|
||||
|
||||
let string = self.body[1..self.body.len().checked_sub(1).unwrap()].join("\n");
|
||||
match serde_json::from_str(&string) {
|
||||
| Err(e) => return Err!("Invalid json: {e}"),
|
||||
| Ok(mut value) => {
|
||||
self.services.server_keys.sign_json(&mut value)?;
|
||||
let json_text = serde_json::to_string_pretty(&value)?;
|
||||
write!(self, "{json_text}")
|
||||
},
|
||||
}
|
||||
.await
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn verify_json(&self) -> Result {
|
||||
if self.body.len() < 2
|
||||
@@ -475,8 +456,10 @@ pub(super) async fn verify_pdu(&self, event_id: OwnedEventId) -> Result {
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
#[tracing::instrument(skip(self))]
|
||||
#[tracing::instrument(skip(self), level = "info")]
|
||||
pub(super) async fn first_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
@@ -500,8 +483,10 @@ pub(super) async fn first_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
#[tracing::instrument(skip(self))]
|
||||
#[tracing::instrument(skip(self), level = "info")]
|
||||
pub(super) async fn latest_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
@@ -525,13 +510,15 @@ pub(super) async fn latest_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
#[tracing::instrument(skip(self))]
|
||||
#[tracing::instrument(skip(self), level = "info")]
|
||||
pub(super) async fn force_set_room_state_from_server(
|
||||
&self,
|
||||
room_id: OwnedRoomId,
|
||||
server_name: OwnedServerName,
|
||||
at_event: Option<OwnedEventId>,
|
||||
) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
|
||||
@@ -47,9 +47,9 @@ pub enum DebugCommand {
|
||||
shorteventid: ShortEventId,
|
||||
},
|
||||
|
||||
/// - Attempts to retrieve a PDU from a remote server. Inserts it into our
|
||||
/// database/timeline if found and we do not have this PDU already
|
||||
/// (following normal event auth rules, handles it as an incoming PDU).
|
||||
/// - Attempts to retrieve a PDU from a remote server. **Does not** insert
|
||||
/// it into the database
|
||||
/// or persist it anywhere.
|
||||
GetRemotePdu {
|
||||
/// An event ID (a $ followed by the base64 reference hash)
|
||||
event_id: OwnedEventId,
|
||||
@@ -125,12 +125,6 @@ pub enum DebugCommand {
|
||||
reset: bool,
|
||||
},
|
||||
|
||||
/// - Sign JSON blob
|
||||
///
|
||||
/// This command needs a JSON blob provided in a Markdown code block below
|
||||
/// the command.
|
||||
SignJson,
|
||||
|
||||
/// - Verify JSON signatures
|
||||
///
|
||||
/// This command needs a JSON blob provided in a Markdown code block below
|
||||
|
||||
@@ -8,12 +8,14 @@
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn disable_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
self.bail_restricted()?;
|
||||
self.services.rooms.metadata.disable_room(&room_id, true);
|
||||
self.write_str("Room disabled.").await
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn enable_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
self.bail_restricted()?;
|
||||
self.services.rooms.metadata.disable_room(&room_id, false);
|
||||
self.write_str("Room enabled.").await
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ pub(super) async fn delete(
|
||||
mxc: Option<OwnedMxcUri>,
|
||||
event_id: Option<OwnedEventId>,
|
||||
) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
if event_id.is_some() && mxc.is_some() {
|
||||
return Err!("Please specify either an MXC or an event ID, not both.",);
|
||||
}
|
||||
@@ -176,6 +178,8 @@ pub(super) async fn delete(
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn delete_list(&self) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
if self.body.len() < 2
|
||||
|| !self.body[0].trim().starts_with("```")
|
||||
|| self.body.last().unwrap_or(&"").trim() != "```"
|
||||
@@ -231,6 +235,8 @@ pub(super) async fn delete_past_remote_media(
|
||||
after: bool,
|
||||
yes_i_want_to_delete_local_media: bool,
|
||||
) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
if before && after {
|
||||
return Err!("Please only pick one argument, --before or --after.",);
|
||||
}
|
||||
@@ -273,6 +279,8 @@ pub(super) async fn delete_all_from_server(
|
||||
server_name: OwnedServerName,
|
||||
yes_i_want_to_delete_local_media: bool,
|
||||
) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
if server_name == self.services.globals.server_name() && !yes_i_want_to_delete_local_media {
|
||||
return Err!("This command only works for remote media by default.",);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ pub(super) fn dispatch(services: Arc<Services>, command: CommandInput) -> Proces
|
||||
Box::pin(handle_command(services, command))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, name = "admin")]
|
||||
#[tracing::instrument(skip_all, name = "admin", level = "info")]
|
||||
async fn handle_command(services: Arc<Services>, command: CommandInput) -> ProcessorResult {
|
||||
AssertUnwindSafe(Box::pin(process_command(services, &command)))
|
||||
.catch_unwind()
|
||||
@@ -59,6 +59,7 @@ async fn process_command(services: Arc<Services>, input: &CommandInput) -> Proce
|
||||
reply_id: input.reply_id.as_deref(),
|
||||
sender: input.sender.as_deref(),
|
||||
output: BufWriter::new(Vec::new()).into(),
|
||||
source: input.source,
|
||||
};
|
||||
|
||||
let (result, mut logs) = process(&context, command, &args).await;
|
||||
|
||||
@@ -31,7 +31,7 @@ pub(super) async fn last(&self, room_id: OwnedRoomOrAliasId) -> Result {
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.last_timeline_count(None, &room_id)
|
||||
.last_timeline_count(&room_id)
|
||||
.await?;
|
||||
|
||||
self.write_str(&format!("{result:#?}")).await
|
||||
@@ -52,7 +52,7 @@ pub(super) async fn pdus(
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_rev(None, &room_id, from)
|
||||
.pdus_rev(&room_id, from)
|
||||
.try_take(limit.unwrap_or(3))
|
||||
.try_collect()
|
||||
.await?;
|
||||
|
||||
@@ -98,7 +98,7 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
{
|
||||
| Ok((room_id, servers)) => {
|
||||
debug!(
|
||||
?room_id,
|
||||
%room_id,
|
||||
?servers,
|
||||
"Got federation response fetching room ID for room {room}"
|
||||
);
|
||||
@@ -240,7 +240,7 @@ async fn ban_list_of_rooms(&self) -> Result {
|
||||
{
|
||||
| Ok((room_id, servers)) => {
|
||||
debug!(
|
||||
?room_id,
|
||||
%room_id,
|
||||
?servers,
|
||||
"Got federation response fetching room ID for \
|
||||
{room}",
|
||||
@@ -397,7 +397,7 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
{
|
||||
| Ok((room_id, servers)) => {
|
||||
debug!(
|
||||
?room_id,
|
||||
%room_id,
|
||||
?servers,
|
||||
"Got federation response fetching room ID for room {room}"
|
||||
);
|
||||
|
||||
@@ -24,16 +24,39 @@ pub(super) async fn uptime(&self) -> Result {
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn show_config(&self) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
self.write_str(&format!("{}", *self.services.server.config))
|
||||
.await
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn reload_config(&self, path: Option<PathBuf>) -> Result {
|
||||
let path = path.as_deref().into_iter();
|
||||
self.services.config.reload(path)?;
|
||||
// The path argument is only what's optionally passed via the admin command,
|
||||
// so we need to merge it with the existing paths if any were given at startup.
|
||||
let mut paths = Vec::new();
|
||||
|
||||
self.write_str("Successfully reconfigured.").await
|
||||
// Add previously saved paths to the argument list
|
||||
self.services
|
||||
.config
|
||||
.config_paths
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.for_each(|p| paths.push(p.to_owned()));
|
||||
|
||||
// If a path is given, and it's not already in the list,
|
||||
// add it last, so that it overrides earlier files
|
||||
if let Some(p) = path {
|
||||
if !paths.contains(&p) {
|
||||
paths.push(p);
|
||||
}
|
||||
}
|
||||
|
||||
self.services.config.reload(&paths)?;
|
||||
|
||||
self.write_str(&format!("Successfully reconfigured from paths: {paths:?}"))
|
||||
.await
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
@@ -97,6 +120,8 @@ pub(super) async fn list_backups(&self) -> Result {
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn backup_database(&self) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
let db = Arc::clone(&self.services.db);
|
||||
let result = self
|
||||
.services
|
||||
@@ -123,6 +148,8 @@ pub(super) async fn admin_notice(&self, message: Vec<String>) -> Result {
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn reload_mods(&self) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
self.services.server.reload()?;
|
||||
|
||||
self.write_str("Reloading server...").await
|
||||
@@ -147,6 +174,8 @@ pub(super) async fn restart(&self, force: bool) -> Result {
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn shutdown(&self) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
warn!("shutdown command");
|
||||
self.services.server.shutdown()?;
|
||||
|
||||
|
||||
@@ -238,6 +238,7 @@ pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) ->
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn suspend(&self, user_id: String) -> Result {
|
||||
self.bail_restricted()?;
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
|
||||
if user_id == self.services.globals.server_user {
|
||||
@@ -262,6 +263,7 @@ pub(super) async fn suspend(&self, user_id: String) -> Result {
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn unsuspend(&self, user_id: String) -> Result {
|
||||
self.bail_restricted()?;
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
|
||||
if user_id == self.services.globals.server_user {
|
||||
@@ -461,9 +463,11 @@ pub(super) async fn force_join_list_of_local_users(
|
||||
);
|
||||
}
|
||||
|
||||
let Ok(admin_room) = self.services.admin.get_admin_room().await else {
|
||||
return Err!("There is not an admin room to check for server admins.",);
|
||||
};
|
||||
let server_admins = self.services.admin.get_admins().await;
|
||||
|
||||
if server_admins.is_empty() {
|
||||
return Err!("There are no admins set for this server.");
|
||||
}
|
||||
|
||||
let (room_id, servers) = self
|
||||
.services
|
||||
@@ -482,15 +486,6 @@ pub(super) async fn force_join_list_of_local_users(
|
||||
return Err!("We are not joined in this room.");
|
||||
}
|
||||
|
||||
let server_admins: Vec<_> = self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.active_local_users_in_room(&admin_room)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
@@ -583,9 +578,11 @@ pub(super) async fn force_join_all_local_users(
|
||||
);
|
||||
}
|
||||
|
||||
let Ok(admin_room) = self.services.admin.get_admin_room().await else {
|
||||
return Err!("There is not an admin room to check for server admins.",);
|
||||
};
|
||||
let server_admins = self.services.admin.get_admins().await;
|
||||
|
||||
if server_admins.is_empty() {
|
||||
return Err!("There are no admins set for this server.");
|
||||
}
|
||||
|
||||
let (room_id, servers) = self
|
||||
.services
|
||||
@@ -604,15 +601,6 @@ pub(super) async fn force_join_all_local_users(
|
||||
return Err!("We are not joined in this room.");
|
||||
}
|
||||
|
||||
let server_admins: Vec<_> = self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.active_local_users_in_room(&admin_room)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
@@ -988,3 +976,44 @@ pub(super) async fn force_leave_remote_room(
|
||||
self.write_str(&format!("{user_id} successfully left {room_id} via remote server."))
|
||||
.await
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn lock(&self, user_id: String) -> Result {
|
||||
self.bail_restricted()?;
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
assert!(
|
||||
self.services.globals.user_is_local(&user_id),
|
||||
"Parsed user_id must be a local user"
|
||||
);
|
||||
if user_id == self.services.globals.server_user {
|
||||
return Err!("Not allowed to lock the server service account.",);
|
||||
}
|
||||
|
||||
if !self.services.users.exists(&user_id).await {
|
||||
return Err!("User {user_id} does not exist.");
|
||||
}
|
||||
if self.services.users.is_admin(&user_id).await {
|
||||
return Err!("Admin users cannot be locked.");
|
||||
}
|
||||
self.services
|
||||
.users
|
||||
.lock_account(&user_id, self.sender_or_service_user())
|
||||
.await;
|
||||
|
||||
self.write_str(&format!("User {user_id} has been locked."))
|
||||
.await
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn unlock(&self, user_id: String) -> Result {
|
||||
self.bail_restricted()?;
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
assert!(
|
||||
self.services.globals.user_is_local(&user_id),
|
||||
"Parsed user_id must be a local user"
|
||||
);
|
||||
self.services.users.unlock_account(&user_id).await;
|
||||
|
||||
self.write_str(&format!("User {user_id} has been unlocked."))
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -81,6 +81,26 @@ pub enum UserCommand {
|
||||
user_id: String,
|
||||
},
|
||||
|
||||
/// - Lock a user
|
||||
///
|
||||
/// Locked users are unable to use their accounts beyond logging out. This
|
||||
/// is akin to a temporary deactivation that does not change the user's
|
||||
/// password. This can be used to quickly prevent a user from accessing
|
||||
/// their account.
|
||||
Lock {
|
||||
/// Username of the user to lock
|
||||
user_id: String,
|
||||
},
|
||||
|
||||
/// - Unlock a user
|
||||
///
|
||||
/// Reverses the effects of the `lock` command, allowing the user to use
|
||||
/// their account again.
|
||||
Unlock {
|
||||
/// Username of the user to unlock
|
||||
user_id: String,
|
||||
},
|
||||
|
||||
/// - List local users in the database
|
||||
#[clap(alias = "list")]
|
||||
ListUsers,
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
///
|
||||
/// Note: This will not reserve the username, so the username might become
|
||||
/// invalid when trying to register
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "register_available")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "register_available", level = "info")]
|
||||
pub(crate) async fn get_register_available_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -138,7 +138,7 @@ pub(crate) async fn get_register_available_route(
|
||||
/// - If `inhibit_login` is false: Creates a device and returns device id and
|
||||
/// access_token
|
||||
#[allow(clippy::doc_markdown)]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "register")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "register", level = "info")]
|
||||
pub(crate) async fn register_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -603,7 +603,7 @@ pub(crate) async fn register_route(
|
||||
/// last seen ts)
|
||||
/// - Forgets to-device events
|
||||
/// - Triggers device list updates
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "change_password")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "change_password", level = "info")]
|
||||
pub(crate) async fn change_password_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -727,7 +727,7 @@ pub(crate) async fn whoami_route(
|
||||
/// - Forgets all to-device events
|
||||
/// - Triggers device list updates
|
||||
/// - Removes ability to log in again
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "deactivate")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "deactivate", level = "info")]
|
||||
pub(crate) async fn deactivate_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
|
||||
@@ -102,7 +102,7 @@ pub(crate) async fn get_alias_route(
|
||||
};
|
||||
|
||||
let servers = room_available_servers(&services, &room_id, &room_alias, servers).await;
|
||||
debug!(?room_alias, ?room_id, "available servers: {servers:?}");
|
||||
debug!(%room_alias, %room_id, "available servers: {servers:?}");
|
||||
|
||||
Ok(get_alias::v3::Response::new(room_id, servers))
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ pub(crate) async fn get_context_route(
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu(event_id)
|
||||
.map_err(|_| err!(Request(NotFound("Base event not found."))));
|
||||
.map_err(|_| err!(Request(NotFound("Event not found."))));
|
||||
|
||||
let visible = services
|
||||
.rooms
|
||||
@@ -70,11 +70,11 @@ pub(crate) async fn get_context_route(
|
||||
let (base_id, base_pdu, visible) = try_join3(base_id, base_pdu, visible).await?;
|
||||
|
||||
if base_pdu.room_id_or_hash() != *room_id || base_pdu.event_id != *event_id {
|
||||
return Err!(Request(NotFound("Base event not found.")));
|
||||
return Err!(Request(NotFound("Event not found.")));
|
||||
}
|
||||
|
||||
if !visible {
|
||||
debug_warn!(req_evt = ?event_id, ?base_id, ?room_id, "Event requested by {sender_user} but is not allowed to see it, returning 404");
|
||||
debug_warn!(req_evt = %event_id, ?base_id, %room_id, "Event requested by {sender_user} but is not allowed to see it, returning 404");
|
||||
return Err!(Request(NotFound("Event not found.")));
|
||||
}
|
||||
|
||||
@@ -82,11 +82,25 @@ pub(crate) async fn get_context_route(
|
||||
|
||||
let base_event = ignored_filter(&services, (base_count, base_pdu), sender_user);
|
||||
|
||||
// PDUs are used to get seen user IDs and then returned in response.
|
||||
|
||||
let events_before = services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_rev(Some(sender_user), room_id, Some(base_count))
|
||||
.pdus_rev(room_id, Some(base_count))
|
||||
.ignore_err()
|
||||
.then(async |mut pdu| {
|
||||
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}");
|
||||
}
|
||||
pdu
|
||||
})
|
||||
.ready_filter_map(|item| event_filter(item, filter))
|
||||
.wide_filter_map(|item| ignored_filter(&services, item, sender_user))
|
||||
.wide_filter_map(|item| visibility_filter(&services, item, sender_user))
|
||||
@@ -96,8 +110,20 @@ pub(crate) async fn get_context_route(
|
||||
let events_after = services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus(Some(sender_user), room_id, Some(base_count))
|
||||
.pdus(room_id, Some(base_count))
|
||||
.ignore_err()
|
||||
.then(async |mut pdu| {
|
||||
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}");
|
||||
}
|
||||
pdu
|
||||
})
|
||||
.ready_filter_map(|item| event_filter(item, filter))
|
||||
.wide_filter_map(|item| ignored_filter(&services, item, sender_user))
|
||||
.wide_filter_map(|item| visibility_filter(&services, item, sender_user))
|
||||
|
||||
@@ -49,7 +49,7 @@ pub(crate) async fn get_device_route(
|
||||
/// # `PUT /_matrix/client/r0/devices/{deviceId}`
|
||||
///
|
||||
/// Updates the metadata on a given device of the sender user.
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "update_device")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "update_device", level = "debug")]
|
||||
pub(crate) async fn update_device_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
/// Lists the public rooms on this server.
|
||||
///
|
||||
/// - Rooms are ordered by the number of joined members
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms", level = "info")]
|
||||
pub(crate) async fn get_public_rooms_filtered_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -80,7 +80,7 @@ pub(crate) async fn get_public_rooms_filtered_route(
|
||||
/// Lists the public rooms on this server.
|
||||
///
|
||||
/// - Rooms are ordered by the number of joined members
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms", level = "info")]
|
||||
pub(crate) async fn get_public_rooms_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -116,7 +116,7 @@ pub(crate) async fn get_public_rooms_route(
|
||||
/// # `PUT /_matrix/client/r0/directory/list/room/{roomId}`
|
||||
///
|
||||
/// Sets the visibility of a given room in the room directory.
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "room_directory")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "room_directory", level = "info")]
|
||||
pub(crate) async fn set_room_visibility_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, Error, Result, debug, debug_warn, err, result::NotFound, utils};
|
||||
use conduwuit::{
|
||||
Err, Error, Result, debug, debug_warn, err,
|
||||
result::NotFound,
|
||||
utils,
|
||||
utils::{IterStream, stream::WidebandExt},
|
||||
};
|
||||
use conduwuit_service::{Services, users::parse_master_key};
|
||||
use futures::{StreamExt, stream::FuturesUnordered};
|
||||
use ruma::{
|
||||
@@ -44,7 +52,7 @@ pub(crate) async fn upload_keys_route(
|
||||
.deserialize()
|
||||
.inspect_err(|e| {
|
||||
debug_warn!(
|
||||
?key_id,
|
||||
%key_id,
|
||||
?one_time_key,
|
||||
"Invalid one time key JSON submitted by client, skipping: {e}"
|
||||
);
|
||||
@@ -86,8 +94,8 @@ pub(crate) async fn upload_keys_route(
|
||||
{
|
||||
if existing_keys.json().get() == device_keys.json().get() {
|
||||
debug!(
|
||||
?sender_user,
|
||||
?sender_device,
|
||||
%sender_user,
|
||||
%sender_device,
|
||||
?device_keys,
|
||||
"Ignoring user uploaded keys as they are an exact copy already in the \
|
||||
database"
|
||||
@@ -134,6 +142,7 @@ pub(crate) async fn get_keys_route(
|
||||
&body.device_keys,
|
||||
|u| u == sender_user,
|
||||
true, // Always allow local users to see device names of other local users
|
||||
body.timeout.unwrap_or(Duration::from_secs(10)),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -145,7 +154,12 @@ pub(crate) async fn claim_keys_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<claim_keys::v3::Request>,
|
||||
) -> Result<claim_keys::v3::Response> {
|
||||
claim_keys_helper(&services, &body.one_time_keys).await
|
||||
claim_keys_helper(
|
||||
&services,
|
||||
&body.one_time_keys,
|
||||
body.timeout.unwrap_or(Duration::from_secs(10)),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/r0/keys/device_signing/upload`
|
||||
@@ -324,7 +338,7 @@ pub(crate) async fn upload_signatures_route(
|
||||
for (user_id, keys) in &body.signed_keys {
|
||||
for (key_id, key) in keys {
|
||||
let Ok(key) = serde_json::to_value(key)
|
||||
.inspect_err(|e| debug_warn!(?key_id, "Invalid \"key\" JSON: {e}"))
|
||||
.inspect_err(|e| debug_warn!(%key_id, "Invalid \"key\" JSON: {e}"))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
@@ -421,6 +435,7 @@ pub(crate) async fn get_keys_helper<F>(
|
||||
device_keys_input: &BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
|
||||
allowed_signatures: F,
|
||||
include_display_names: bool,
|
||||
timeout: Duration,
|
||||
) -> Result<get_keys::v3::Response>
|
||||
where
|
||||
F: Fn(&UserId) -> bool + Send + Sync,
|
||||
@@ -512,9 +527,10 @@ pub(crate) async fn get_keys_helper<F>(
|
||||
|
||||
let mut failures = BTreeMap::new();
|
||||
|
||||
let mut futures: FuturesUnordered<_> = get_over_federation
|
||||
let futures = get_over_federation
|
||||
.into_iter()
|
||||
.map(|(server, vec)| async move {
|
||||
.stream()
|
||||
.wide_filter_map(|(server, vec)| async move {
|
||||
let mut device_keys_input_fed = BTreeMap::new();
|
||||
for (user_id, keys) in vec {
|
||||
device_keys_input_fed.insert(user_id.to_owned(), keys.clone());
|
||||
@@ -522,17 +538,22 @@ pub(crate) async fn get_keys_helper<F>(
|
||||
|
||||
let request =
|
||||
federation::keys::get_keys::v1::Request { device_keys: device_keys_input_fed };
|
||||
let response = tokio::time::timeout(
|
||||
timeout,
|
||||
services.sending.send_federation_request(server, request),
|
||||
)
|
||||
.await
|
||||
// Need to flatten the Result<Result<V, E>, E> into Result<V, E>
|
||||
.map_err(|_| err!(Request(Unknown("Timeout when getting keys over federation."))))
|
||||
.and_then(|res| res);
|
||||
|
||||
let response = services
|
||||
.sending
|
||||
.send_federation_request(server, request)
|
||||
.await;
|
||||
|
||||
(server, response)
|
||||
Some((server, response))
|
||||
})
|
||||
.collect();
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.await
|
||||
.into_iter();
|
||||
|
||||
while let Some((server, response)) = futures.next().await {
|
||||
for (server, response) in futures {
|
||||
match response {
|
||||
| Ok(response) => {
|
||||
for (user, master_key) in response.master_keys {
|
||||
@@ -564,8 +585,8 @@ pub(crate) async fn get_keys_helper<F>(
|
||||
self_signing_keys.extend(response.self_signing_keys);
|
||||
device_keys.extend(response.device_keys);
|
||||
},
|
||||
| _ => {
|
||||
failures.insert(server.to_string(), json!({}));
|
||||
| Err(e) => {
|
||||
failures.insert(server.to_string(), json!({ "error": e.to_string() }));
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -608,6 +629,7 @@ fn add_unsigned_device_display_name(
|
||||
pub(crate) async fn claim_keys_helper(
|
||||
services: &Services,
|
||||
one_time_keys_input: &BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, OneTimeKeyAlgorithm>>,
|
||||
timeout: Duration,
|
||||
) -> Result<claim_keys::v3::Response> {
|
||||
let mut one_time_keys = BTreeMap::new();
|
||||
|
||||
@@ -638,32 +660,39 @@ pub(crate) async fn claim_keys_helper(
|
||||
|
||||
let mut failures = BTreeMap::new();
|
||||
|
||||
let mut futures: FuturesUnordered<_> = get_over_federation
|
||||
let futures = get_over_federation
|
||||
.into_iter()
|
||||
.map(|(server, vec)| async move {
|
||||
.stream()
|
||||
.wide_filter_map(|(server, vec)| async move {
|
||||
let mut one_time_keys_input_fed = BTreeMap::new();
|
||||
for (user_id, keys) in vec {
|
||||
one_time_keys_input_fed.insert(user_id.clone(), keys.clone());
|
||||
}
|
||||
(
|
||||
server,
|
||||
services
|
||||
.sending
|
||||
.send_federation_request(server, federation::keys::claim_keys::v1::Request {
|
||||
let response = tokio::time::timeout(
|
||||
timeout,
|
||||
services.sending.send_federation_request(
|
||||
server,
|
||||
federation::keys::claim_keys::v1::Request {
|
||||
one_time_keys: one_time_keys_input_fed,
|
||||
})
|
||||
.await,
|
||||
},
|
||||
),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| err!(Request(Unknown("Timeout when claiming keys over federation."))))
|
||||
.and_then(|res| res);
|
||||
Some((server, response))
|
||||
})
|
||||
.collect();
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.await
|
||||
.into_iter();
|
||||
|
||||
while let Some((server, response)) = futures.next().await {
|
||||
for (server, response) in futures {
|
||||
match response {
|
||||
| Ok(keys) => {
|
||||
one_time_keys.extend(keys.one_time_keys);
|
||||
},
|
||||
| Err(_e) => {
|
||||
failures.insert(server.to_string(), json!({}));
|
||||
| Err(e) => {
|
||||
failures.insert(server.to_string(), json!({"error": e.to_string()}));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use conduwuit::{
|
||||
Err, Result, debug_error, err, info,
|
||||
matrix::{event::gen_event_id_canonical_json, pdu::PduBuilder},
|
||||
warn,
|
||||
};
|
||||
use futures::FutureExt;
|
||||
use ruma::{
|
||||
@@ -21,7 +22,7 @@
|
||||
/// # `POST /_matrix/client/r0/rooms/{roomId}/invite`
|
||||
///
|
||||
/// Tries to send an invite event into the room.
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "invite")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "invite", level = "info")]
|
||||
pub(crate) async fn invite_user_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -124,6 +125,18 @@ pub(crate) async fn invite_helper(
|
||||
return Err!(Request(Forbidden("Invites are not allowed on this server.")));
|
||||
}
|
||||
|
||||
if let Err(e) = services
|
||||
.antispam
|
||||
.user_may_invite(sender_user.to_owned(), recipient_user.to_owned(), room_id.to_owned())
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
"Invite from {} to {} in room {} blocked by antispam: {e:?}",
|
||||
sender_user, recipient_user, room_id
|
||||
);
|
||||
return Err!(Request(Forbidden("Invite blocked by antispam service.")));
|
||||
}
|
||||
|
||||
if !services.globals.user_is_local(recipient_user) {
|
||||
let (pdu, pdu_json, invite_room_state) = {
|
||||
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
events::{
|
||||
StateEventType,
|
||||
room::{
|
||||
join_rules::{AllowRule, JoinRule, RoomJoinRulesEventContent},
|
||||
join_rules::{AllowRule, JoinRule},
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
},
|
||||
@@ -48,7 +48,7 @@
|
||||
},
|
||||
};
|
||||
|
||||
use super::banned_room_check;
|
||||
use super::{banned_room_check, validate_remote_member_event_stub};
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `POST /_matrix/client/r0/rooms/{roomId}/join`
|
||||
@@ -59,7 +59,7 @@
|
||||
/// rules locally
|
||||
/// - If the server does not know about the room: asks other servers over
|
||||
/// federation
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "join")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "join", level = "info")]
|
||||
pub(crate) async fn join_room_by_id_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -131,7 +131,7 @@ pub(crate) async fn join_room_by_id_route(
|
||||
/// - If the server does not know about the room: use the server name query
|
||||
/// param if specified. if not specified, asks other servers over federation
|
||||
/// via room alias server name and room ID server name
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "join")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "join", level = "info")]
|
||||
pub(crate) async fn join_room_by_id_or_alias_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -288,6 +288,23 @@ pub async fn join_room_by_id_helper(
|
||||
return Ok(join_room_by_id::v3::Response { room_id: room_id.into() });
|
||||
}
|
||||
|
||||
if let Err(e) = services
|
||||
.antispam
|
||||
.user_may_join_room(
|
||||
sender_user.to_owned(),
|
||||
room_id.to_owned(),
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_invited(sender_user, room_id)
|
||||
.await,
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!("Antispam prevented user {} from joining room {}: {}", sender_user, room_id, e);
|
||||
return Err!(Request(Forbidden("You are not allowed to join this room.")));
|
||||
}
|
||||
|
||||
let server_in_room = services
|
||||
.rooms
|
||||
.state_cache
|
||||
@@ -321,6 +338,17 @@ pub async fn join_room_by_id_helper(
|
||||
)));
|
||||
}
|
||||
|
||||
if services.antispam.check_all_joins() {
|
||||
if let Err(e) = services
|
||||
.antispam
|
||||
.meowlnir_accept_make_join(room_id.to_owned(), sender_user.to_owned())
|
||||
.await
|
||||
{
|
||||
warn!("Antispam prevented user {} from joining room {}: {}", sender_user, room_id, e);
|
||||
return Err!(Request(Forbidden("Antispam rejected join request.")));
|
||||
}
|
||||
}
|
||||
|
||||
if server_in_room {
|
||||
join_room_by_id_helper_local(
|
||||
services,
|
||||
@@ -347,11 +375,10 @@ pub async fn join_room_by_id_helper(
|
||||
.boxed()
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(join_room_by_id::v3::Response::new(room_id.to_owned()))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_remote")]
|
||||
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_remote", level = "info")]
|
||||
async fn join_room_by_id_helper_remote(
|
||||
services: &Services,
|
||||
sender_user: &UserId,
|
||||
@@ -709,7 +736,7 @@ async fn join_room_by_id_helper_remote(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_local")]
|
||||
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_local", level = "info")]
|
||||
async fn join_room_by_id_helper_local(
|
||||
services: &Services,
|
||||
sender_user: &UserId,
|
||||
@@ -720,45 +747,51 @@ async fn join_room_by_id_helper_local(
|
||||
state_lock: RoomMutexGuard,
|
||||
) -> Result {
|
||||
debug_info!("We can join locally");
|
||||
let join_rules = services.rooms.state_accessor.get_join_rules(room_id).await;
|
||||
|
||||
let join_rules_event_content = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get_content::<RoomJoinRulesEventContent>(
|
||||
room_id,
|
||||
&StateEventType::RoomJoinRules,
|
||||
"",
|
||||
)
|
||||
.await;
|
||||
|
||||
let restriction_rooms = match join_rules_event_content {
|
||||
| Ok(RoomJoinRulesEventContent {
|
||||
join_rule: JoinRule::Restricted(restricted) | JoinRule::KnockRestricted(restricted),
|
||||
}) => restricted
|
||||
.allow
|
||||
.into_iter()
|
||||
.filter_map(|a| match a {
|
||||
| AllowRule::RoomMembership(r) => Some(r.room_id),
|
||||
| _ => None,
|
||||
})
|
||||
.collect(),
|
||||
| _ => Vec::new(),
|
||||
};
|
||||
|
||||
let join_authorized_via_users_server: Option<OwnedUserId> = {
|
||||
if restriction_rooms
|
||||
.iter()
|
||||
.stream()
|
||||
.any(|restriction_room_id| {
|
||||
trace!("Checking if {sender_user} is joined to {restriction_room_id}");
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(sender_user, restriction_room_id)
|
||||
})
|
||||
.await
|
||||
{
|
||||
services
|
||||
let mut restricted_join_authorized = None;
|
||||
match join_rules {
|
||||
| JoinRule::Restricted(restricted) | JoinRule::KnockRestricted(restricted) => {
|
||||
for restriction in restricted.allow {
|
||||
match restriction {
|
||||
| AllowRule::RoomMembership(membership) => {
|
||||
if services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(sender_user, &membership.room_id)
|
||||
.await
|
||||
{
|
||||
restricted_join_authorized = Some(true);
|
||||
break;
|
||||
}
|
||||
},
|
||||
| AllowRule::UnstableSpamChecker => {
|
||||
match services
|
||||
.antispam
|
||||
.meowlnir_accept_make_join(room_id.to_owned(), sender_user.to_owned())
|
||||
.await
|
||||
{
|
||||
| Ok(()) => {
|
||||
restricted_join_authorized = Some(true);
|
||||
break;
|
||||
},
|
||||
| Err(_) =>
|
||||
return Err!(Request(Forbidden(
|
||||
"Antispam rejected join request."
|
||||
))),
|
||||
}
|
||||
},
|
||||
| _ => {},
|
||||
}
|
||||
}
|
||||
},
|
||||
| _ => {},
|
||||
}
|
||||
let join_authorized_via_users_server = if restricted_join_authorized.is_none() {
|
||||
None
|
||||
} else {
|
||||
match restricted_join_authorized.unwrap() {
|
||||
| true => services
|
||||
.rooms
|
||||
.state_cache
|
||||
.local_users_in_room(room_id)
|
||||
@@ -774,10 +807,14 @@ async fn join_room_by_id_helper_local(
|
||||
.boxed()
|
||||
.next()
|
||||
.await
|
||||
.map(ToOwned::to_owned)
|
||||
} else {
|
||||
trace!("No restriction rooms are joined by {sender_user}");
|
||||
None
|
||||
.map(ToOwned::to_owned),
|
||||
| false => {
|
||||
warn!(
|
||||
"Join authorization failed for restricted join in room {room_id} for user \
|
||||
{sender_user}"
|
||||
);
|
||||
return Err!(Request(Forbidden("You are not authorized to join this room.")));
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -805,16 +842,14 @@ async fn join_room_by_id_helper_local(
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if restriction_rooms.is_empty()
|
||||
&& (servers.is_empty()
|
||||
|| servers.len() == 1 && services.globals.server_is_ours(&servers[0]))
|
||||
{
|
||||
if servers.is_empty() || servers.len() == 1 && services.globals.server_is_ours(&servers[0]) {
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
warn!(
|
||||
"We couldn't do the join locally, maybe federation can help to satisfy the restricted \
|
||||
join requirements"
|
||||
?error,
|
||||
servers = %servers.len(),
|
||||
"Could not join restricted room locally, attempting remote join",
|
||||
);
|
||||
let Ok((make_join_response, remote_server)) =
|
||||
make_join_request(services, sender_user, room_id, servers).await
|
||||
@@ -837,6 +872,13 @@ async fn join_room_by_id_helper_local(
|
||||
err!(BadServerResponse("Invalid make_join event json received from server: {e:?}"))
|
||||
})?;
|
||||
|
||||
validate_remote_member_event_stub(
|
||||
&MembershipState::Join,
|
||||
sender_user,
|
||||
room_id,
|
||||
&join_event_stub,
|
||||
)?;
|
||||
|
||||
let join_authorized_via_users_server = join_event_stub
|
||||
.get("content")
|
||||
.map(|s| {
|
||||
|
||||
@@ -38,13 +38,13 @@
|
||||
},
|
||||
};
|
||||
|
||||
use super::{banned_room_check, join::join_room_by_id_helper};
|
||||
use super::{banned_room_check, join::join_room_by_id_helper, validate_remote_member_event_stub};
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `POST /_matrix/client/*/knock/{roomIdOrAlias}`
|
||||
///
|
||||
/// Tries to knock the room to ask permission to join for the sender user.
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "knock")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "knock", level = "info")]
|
||||
pub(crate) async fn knock_room_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -408,6 +408,13 @@ async fn knock_room_helper_local(
|
||||
err!(BadServerResponse("Invalid make_knock event json received from server: {e:?}"))
|
||||
})?;
|
||||
|
||||
validate_remote_member_event_stub(
|
||||
&MembershipState::Knock,
|
||||
sender_user,
|
||||
room_id,
|
||||
&knock_event_stub,
|
||||
)?;
|
||||
|
||||
knock_event_stub.insert(
|
||||
"origin".to_owned(),
|
||||
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
};
|
||||
use service::Services;
|
||||
|
||||
use super::validate_remote_member_event_stub;
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `POST /_matrix/client/v3/rooms/{roomId}/leave`
|
||||
@@ -324,6 +325,13 @@ pub async fn remote_leave_room<S: ::std::hash::BuildHasher>(
|
||||
)))
|
||||
})?;
|
||||
|
||||
validate_remote_member_event_stub(
|
||||
&MembershipState::Leave,
|
||||
user_id,
|
||||
room_id,
|
||||
&leave_event_stub,
|
||||
)?;
|
||||
|
||||
// TODO: Is origin needed?
|
||||
leave_event_stub.insert(
|
||||
"origin".to_owned(),
|
||||
|
||||
@@ -13,7 +13,14 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, Result, warn};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ruma::{OwnedRoomId, RoomId, ServerName, UserId, api::client::membership::joined_rooms};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, OwnedRoomId, RoomId, ServerName, UserId,
|
||||
api::client::membership::joined_rooms,
|
||||
events::{
|
||||
StaticEventContent,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
};
|
||||
use service::Services;
|
||||
|
||||
pub(crate) use self::{
|
||||
@@ -56,7 +63,7 @@ pub(crate) async fn joined_rooms_route(
|
||||
///
|
||||
/// Performs automatic deactivation if `auto_deactivate_banned_room_attempts` is
|
||||
/// enabled
|
||||
#[tracing::instrument(skip(services))]
|
||||
#[tracing::instrument(skip(services), level = "info")]
|
||||
pub(crate) async fn banned_room_check(
|
||||
services: &Services,
|
||||
user_id: &UserId,
|
||||
@@ -153,3 +160,80 @@ pub(crate) async fn banned_room_check(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validates that an event returned from a remote server by `/make_*`
|
||||
/// actually is a membership event with the expected fields.
|
||||
///
|
||||
/// Without checking this, the remote server could use the remote membership
|
||||
/// mechanism to trick our server into signing arbitrary malicious events.
|
||||
pub(crate) fn validate_remote_member_event_stub(
|
||||
membership: &MembershipState,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
event_stub: &CanonicalJsonObject,
|
||||
) -> Result<()> {
|
||||
let Some(event_type) = event_stub.get("type") else {
|
||||
return Err!(BadServerResponse(
|
||||
"Remote server returned member event with missing type field"
|
||||
));
|
||||
};
|
||||
if event_type != &RoomMemberEventContent::TYPE {
|
||||
return Err!(BadServerResponse(
|
||||
"Remote server returned member event with invalid event type"
|
||||
));
|
||||
}
|
||||
|
||||
let Some(sender) = event_stub.get("sender") else {
|
||||
return Err!(BadServerResponse(
|
||||
"Remote server returned member event with missing sender field"
|
||||
));
|
||||
};
|
||||
if sender != &user_id.as_str() {
|
||||
return Err!(BadServerResponse(
|
||||
"Remote server returned member event with incorrect sender"
|
||||
));
|
||||
}
|
||||
|
||||
let Some(state_key) = event_stub.get("state_key") else {
|
||||
return Err!(BadServerResponse(
|
||||
"Remote server returned member event with missing state_key field"
|
||||
));
|
||||
};
|
||||
if state_key != &user_id.as_str() {
|
||||
return Err!(BadServerResponse(
|
||||
"Remote server returned member event with incorrect state_key"
|
||||
));
|
||||
}
|
||||
|
||||
let Some(event_room_id) = event_stub.get("room_id") else {
|
||||
return Err!(BadServerResponse(
|
||||
"Remote server returned member event with missing room_id field"
|
||||
));
|
||||
};
|
||||
if event_room_id != &room_id.as_str() {
|
||||
return Err!(BadServerResponse(
|
||||
"Remote server returned member event with incorrect room_id"
|
||||
));
|
||||
}
|
||||
|
||||
let Some(content) = event_stub
|
||||
.get("content")
|
||||
.and_then(|content| content.as_object())
|
||||
else {
|
||||
return Err!(BadServerResponse(
|
||||
"Remote server returned member event with missing content field"
|
||||
));
|
||||
};
|
||||
let Some(event_membership) = content.get("membership") else {
|
||||
return Err!(BadServerResponse(
|
||||
"Remote server returned member event with missing membership field"
|
||||
));
|
||||
};
|
||||
if event_membership != &membership.as_str() {
|
||||
return Err!(BadServerResponse(
|
||||
"Remote server returned member event with incorrect room_id"
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduwuit::{
|
||||
Err, Result, at,
|
||||
Err, Result, at, debug_warn,
|
||||
matrix::{
|
||||
event::{Event, Matches},
|
||||
pdu::PduCount,
|
||||
@@ -122,14 +122,14 @@ pub(crate) async fn get_message_events_route(
|
||||
| Direction::Forward => services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus(Some(sender_user), room_id, Some(from))
|
||||
.pdus(room_id, Some(from))
|
||||
.ignore_err()
|
||||
.boxed(),
|
||||
|
||||
| Direction::Backward => services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_rev(Some(sender_user), room_id, Some(from))
|
||||
.pdus_rev(room_id, Some(from))
|
||||
.ignore_err()
|
||||
.boxed(),
|
||||
};
|
||||
@@ -140,6 +140,18 @@ pub(crate) async fn get_message_events_route(
|
||||
.wide_filter_map(|item| ignored_filter(&services, item, sender_user))
|
||||
.wide_filter_map(|item| visibility_filter(&services, item, sender_user))
|
||||
.take(limit)
|
||||
.then(async |mut pdu| {
|
||||
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}");
|
||||
}
|
||||
pdu
|
||||
})
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{
|
||||
Result, at,
|
||||
Err, Result, at, debug_warn,
|
||||
matrix::{Event, event::RelationTypeEqual, pdu::PduCount},
|
||||
utils::{IterStream, ReadyExt, result::FlatOk, stream::WidebandExt},
|
||||
};
|
||||
@@ -109,6 +109,16 @@ async fn paginate_relations_with_filter(
|
||||
recurse: bool,
|
||||
dir: Direction,
|
||||
) -> Result<get_relating_events::v1::Response> {
|
||||
if !services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_event(sender_user, room_id, target)
|
||||
.await
|
||||
{
|
||||
debug_warn!(req_evt = %target, %room_id, "Event relations requested by {sender_user} but is not allowed to see it, returning 404");
|
||||
return Err!(Request(NotFound("Event not found.")));
|
||||
}
|
||||
|
||||
let start: PduCount = from
|
||||
.map(str::parse)
|
||||
.transpose()?
|
||||
@@ -129,11 +139,6 @@ async fn paginate_relations_with_filter(
|
||||
// Spec (v1.10) recommends depth of at least 3
|
||||
let depth: u8 = if recurse { 3 } else { 1 };
|
||||
|
||||
// Check if this is a thread request
|
||||
let is_thread = filter_rel_type
|
||||
.as_ref()
|
||||
.is_some_and(|rel| *rel == RelationType::Thread);
|
||||
|
||||
let events: Vec<_> = services
|
||||
.rooms
|
||||
.pdu_metadata
|
||||
@@ -152,40 +157,24 @@ async fn paginate_relations_with_filter(
|
||||
})
|
||||
.stream()
|
||||
.ready_take_while(|(count, _)| Some(*count) != to)
|
||||
.wide_filter_map(|item| visibility_filter(services, sender_user, item))
|
||||
.take(limit)
|
||||
.wide_filter_map(|item| visibility_filter(services, sender_user, item))
|
||||
.then(async |mut pdu| {
|
||||
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 to relation: {e}");
|
||||
}
|
||||
pdu
|
||||
})
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
// For threads, check if we should include the root event
|
||||
let mut root_event = None;
|
||||
if is_thread && dir == Direction::Backward {
|
||||
// Check if we've reached the beginning of the thread
|
||||
// (fewer events than requested means we've exhausted the thread)
|
||||
if events.len() < limit {
|
||||
// Try to get the thread root event
|
||||
if let Ok(root_pdu) = services.rooms.timeline.get_pdu(target).await {
|
||||
// Check visibility
|
||||
if services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_event(sender_user, room_id, target)
|
||||
.await
|
||||
{
|
||||
// Store the root event to add to the response
|
||||
root_event = Some(root_pdu);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if there are more events to fetch
|
||||
let has_more = if root_event.is_some() {
|
||||
false // We've included the root, no more events
|
||||
} else {
|
||||
// Check if we got a full page of results (might be more)
|
||||
events.len() >= limit
|
||||
};
|
||||
let has_more = events.len() >= limit;
|
||||
|
||||
let next_batch = if has_more {
|
||||
match dir {
|
||||
@@ -197,11 +186,10 @@ async fn paginate_relations_with_filter(
|
||||
None
|
||||
};
|
||||
|
||||
// Build the response chunk with thread root if needed
|
||||
let chunk: Vec<_> = root_event
|
||||
let chunk: Vec<_> = events
|
||||
.into_iter()
|
||||
.map(at!(1))
|
||||
.map(Event::into_format)
|
||||
.chain(events.into_iter().map(at!(1)).map(Event::into_format))
|
||||
.collect();
|
||||
|
||||
Ok(get_relating_events::v1::Response {
|
||||
|
||||
@@ -29,7 +29,7 @@ struct Report {
|
||||
/// # `POST /_matrix/client/v3/rooms/{roomId}/report`
|
||||
///
|
||||
/// Reports an abusive room to homeserver admins
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "report_room")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "report_room", level = "info")]
|
||||
pub(crate) async fn report_room_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -85,7 +85,7 @@ pub(crate) async fn report_room_route(
|
||||
/// # `POST /_matrix/client/v3/rooms/{roomId}/report/{eventId}`
|
||||
///
|
||||
/// Reports an inappropriate event to homeserver admins
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "report_event")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "report_event", level = "info")]
|
||||
pub(crate) async fn report_event_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -133,7 +133,7 @@ pub(crate) async fn report_event_route(
|
||||
Ok(report_content::v3::Response {})
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "report_user")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "report_user", level = "info")]
|
||||
pub(crate) async fn report_user_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
|
||||
@@ -492,7 +492,7 @@ pub(crate) async fn create_room_route(
|
||||
.boxed()
|
||||
.await
|
||||
{
|
||||
warn!(%e, "Failed to send invite");
|
||||
warn!(?e, "Failed to send invite");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,7 +627,7 @@ async fn room_alias_check(
|
||||
.map_err(|e| {
|
||||
err!(Request(InvalidParam(debug_error!(
|
||||
?e,
|
||||
?room_alias_name,
|
||||
%room_alias_name,
|
||||
"Failed to parse room alias.",
|
||||
))))
|
||||
})?;
|
||||
@@ -711,7 +711,7 @@ fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<Own
|
||||
}
|
||||
})
|
||||
.inspect(|full_room_id| {
|
||||
debug_info!(?full_room_id, "Full custom room ID");
|
||||
debug_info!(%full_room_id, "Full custom room ID");
|
||||
})
|
||||
.inspect_err(|e| warn!(?e, ?custom_room_id, "Failed to create room with custom room ID",))
|
||||
.inspect_err(|e| warn!(?e, %custom_room_id, "Failed to create room with custom room ID",))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, Event, Result, err};
|
||||
use conduwuit::{Err, Event, Result, debug_warn, err};
|
||||
use futures::{FutureExt, TryFutureExt, future::try_join};
|
||||
use ruma::api::client::room::get_room_event;
|
||||
|
||||
@@ -33,7 +33,16 @@ pub(crate) async fn get_room_event_route(
|
||||
return Err!(Request(Forbidden("You don't have permission to view this event.")));
|
||||
}
|
||||
|
||||
event.add_age().ok();
|
||||
if let Err(e) = services
|
||||
.rooms
|
||||
.pdu_metadata
|
||||
.add_bundled_aggregations_to_pdu(body.sender_user(), &mut event)
|
||||
.await
|
||||
{
|
||||
debug_warn!("Failed to add bundled aggregations to event: {e}");
|
||||
}
|
||||
|
||||
event.set_unsigned(body.sender_user.as_deref());
|
||||
|
||||
Ok(get_room_event::v3::Response { event: event.into_format() })
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{
|
||||
Err, Event, Result, at,
|
||||
Err, Event, Result, at, debug_warn,
|
||||
utils::{BoolExt, stream::TryTools},
|
||||
};
|
||||
use futures::{FutureExt, TryStreamExt, future::try_join4};
|
||||
@@ -40,12 +40,28 @@ pub(crate) async fn room_initial_sync_route(
|
||||
.map_ok(Event::into_format)
|
||||
.try_collect::<Vec<_>>();
|
||||
|
||||
// Events are returned in body
|
||||
|
||||
let limit = LIMIT_MAX;
|
||||
let events = services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_rev(None, room_id, None)
|
||||
.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}");
|
||||
}
|
||||
}
|
||||
Ok(pdu)
|
||||
})
|
||||
.try_collect::<Vec<_>>();
|
||||
|
||||
let (membership, visibility, state, events) =
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduwuit::{
|
||||
Err, Result, debug_warn, trace,
|
||||
Err, Result, debug, debug_warn, info, trace,
|
||||
utils::{IterStream, future::TryExtExt},
|
||||
};
|
||||
use futures::{
|
||||
FutureExt, StreamExt,
|
||||
FutureExt, StreamExt, TryFutureExt,
|
||||
future::{OptionFuture, join3},
|
||||
stream::FuturesUnordered,
|
||||
};
|
||||
@@ -46,7 +46,7 @@ pub(crate) async fn get_room_summary_legacy(
|
||||
/// # `GET /_matrix/client/v1/room_summary/{roomIdOrAlias}`
|
||||
///
|
||||
/// Returns a short description of the state of a room.
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "room_summary")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "room_summary", level = "info")]
|
||||
pub(crate) async fn get_room_summary(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -79,9 +79,15 @@ async fn room_summary_response(
|
||||
.server_in_room(services.globals.server_name(), room_id)
|
||||
.await
|
||||
{
|
||||
return local_room_summary_response(services, room_id, sender_user)
|
||||
match local_room_summary_response(services, room_id, sender_user)
|
||||
.boxed()
|
||||
.await;
|
||||
.await
|
||||
{
|
||||
| Ok(response) => return Ok(response),
|
||||
| Err(e) => {
|
||||
debug_warn!("Failed to get local room summary: {e:?}, falling back to remote");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let room =
|
||||
@@ -110,27 +116,31 @@ async fn local_room_summary_response(
|
||||
room_id: &RoomId,
|
||||
sender_user: Option<&UserId>,
|
||||
) -> Result<get_summary::msc3266::Response> {
|
||||
trace!(?sender_user, "Sending local room summary response for {room_id:?}");
|
||||
let join_rule = services.rooms.state_accessor.get_join_rules(room_id);
|
||||
|
||||
let world_readable = services.rooms.state_accessor.is_world_readable(room_id);
|
||||
|
||||
let guest_can_join = services.rooms.state_accessor.guest_can_join(room_id);
|
||||
|
||||
let (join_rule, world_readable, guest_can_join) =
|
||||
join3(join_rule, world_readable, guest_can_join).await;
|
||||
|
||||
trace!("{join_rule:?}, {world_readable:?}, {guest_can_join:?}");
|
||||
user_can_see_summary(
|
||||
services,
|
||||
room_id,
|
||||
&join_rule.clone().into(),
|
||||
guest_can_join,
|
||||
world_readable,
|
||||
join_rule.allowed_rooms(),
|
||||
sender_user,
|
||||
trace!(
|
||||
sender_user = sender_user.map(tracing::field::display),
|
||||
"Sending local room summary response for {room_id:?}"
|
||||
);
|
||||
let (join_rule, world_readable, guest_can_join) = join3(
|
||||
services.rooms.state_accessor.get_join_rules(room_id),
|
||||
services.rooms.state_accessor.is_world_readable(room_id),
|
||||
services.rooms.state_accessor.guest_can_join(room_id),
|
||||
)
|
||||
.await?;
|
||||
.await;
|
||||
|
||||
// Synapse allows server admins to bypass visibility checks.
|
||||
// That seems neat so we'll copy that behaviour.
|
||||
if sender_user.is_none() || !services.users.is_admin(sender_user.unwrap()).await {
|
||||
user_can_see_summary(
|
||||
services,
|
||||
room_id,
|
||||
&join_rule.clone().into(),
|
||||
guest_can_join,
|
||||
world_readable,
|
||||
join_rule.allowed_rooms(),
|
||||
sender_user,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let canonical_alias = services
|
||||
.rooms
|
||||
@@ -221,7 +231,7 @@ async fn remote_room_summary_hierarchy_response(
|
||||
servers: &[OwnedServerName],
|
||||
sender_user: Option<&UserId>,
|
||||
) -> Result<SpaceHierarchyParentSummary> {
|
||||
trace!(?sender_user, ?servers, "Sending remote room summary response for {room_id:?}");
|
||||
trace!(sender_user = ?sender_user.map(tracing::field::display), ?servers, "Sending remote room summary response for {room_id:?}");
|
||||
if !services.config.allow_federation {
|
||||
return Err!(Request(Forbidden("Federation is disabled.")));
|
||||
}
|
||||
@@ -231,15 +241,27 @@ async fn remote_room_summary_hierarchy_response(
|
||||
"Federaton of room {room_id} is currently disabled on this server."
|
||||
)));
|
||||
}
|
||||
if servers.is_empty() {
|
||||
return Err!(Request(MissingParam(
|
||||
"No servers were provided to fetch the room over federation"
|
||||
)));
|
||||
}
|
||||
|
||||
let request = get_hierarchy::v1::Request::new(room_id.to_owned());
|
||||
|
||||
let mut requests: FuturesUnordered<_> = servers
|
||||
.iter()
|
||||
.map(|server| {
|
||||
info!("Fetching room summary for {room_id} from server {server}");
|
||||
services
|
||||
.sending
|
||||
.send_federation_request(server, request.clone())
|
||||
.inspect_ok(move |v| {
|
||||
debug!("Fetched room summary for {room_id} from server {server}: {v:?}");
|
||||
})
|
||||
.inspect_err(move |e| {
|
||||
info!("Failed to fetch room summary for {room_id} from server {server}: {e}");
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -255,23 +277,23 @@ async fn remote_room_summary_hierarchy_response(
|
||||
continue;
|
||||
}
|
||||
|
||||
return user_can_see_summary(
|
||||
services,
|
||||
room_id,
|
||||
&room.join_rule,
|
||||
room.guest_can_join,
|
||||
room.world_readable,
|
||||
room.allowed_room_ids.iter().map(AsRef::as_ref),
|
||||
sender_user,
|
||||
)
|
||||
.await
|
||||
.map(|()| room);
|
||||
if sender_user.is_none() || !services.users.is_admin(sender_user.unwrap()).await {
|
||||
return user_can_see_summary(
|
||||
services,
|
||||
room_id,
|
||||
&room.join_rule,
|
||||
room.guest_can_join,
|
||||
room.world_readable,
|
||||
room.allowed_room_ids.iter().map(AsRef::as_ref),
|
||||
sender_user,
|
||||
)
|
||||
.await
|
||||
.map(|()| room);
|
||||
}
|
||||
return Ok(room);
|
||||
}
|
||||
|
||||
Err!(Request(NotFound(
|
||||
"Room is unknown to this server and was unable to fetch over federation with the \
|
||||
provided servers available"
|
||||
)))
|
||||
Err!(Request(NotFound("Room not found or is not accessible")))
|
||||
}
|
||||
|
||||
async fn user_can_see_summary<'a, I>(
|
||||
@@ -311,21 +333,14 @@ async fn user_can_see_summary<'a, I>(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err!(Request(Forbidden(
|
||||
"Room is not world readable, not publicly accessible/joinable, restricted room \
|
||||
conditions not met, and guest access is forbidden. Not allowed to see details \
|
||||
of this room."
|
||||
)))
|
||||
Err!(Request(Forbidden("Room is not accessible")))
|
||||
},
|
||||
| None => {
|
||||
if is_public_room || world_readable {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err!(Request(Forbidden(
|
||||
"Room is not world readable or publicly accessible/joinable, authentication is \
|
||||
required"
|
||||
)))
|
||||
Err!(Request(Forbidden("Room is not accessible")))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,12 @@ pub(crate) async fn upgrade_room_route(
|
||||
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
|
||||
}
|
||||
|
||||
// Make sure this isn't the admin room
|
||||
// Admin room upgrades are hacky and should be done manually instead.
|
||||
if services.admin.is_admin_room(&body.room_id).await {
|
||||
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)
|
||||
let old_room_state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
|
||||
@@ -266,7 +272,7 @@ pub(crate) async fn upgrade_room_route(
|
||||
.room_state_keys(&body.room_id, event_type)
|
||||
.await?;
|
||||
for state_key in state_keys {
|
||||
let event_content = match services
|
||||
let mut event_content = match services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(&body.room_id, event_type, &state_key)
|
||||
@@ -279,6 +285,21 @@ pub(crate) async fn upgrade_room_route(
|
||||
// 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 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);
|
||||
}
|
||||
event_content = to_raw_value(&power_levels_event_content)
|
||||
.expect("event is valid, we just deserialized and modified it");
|
||||
}
|
||||
|
||||
services
|
||||
.rooms
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{
|
||||
Err, Result, at, is_true,
|
||||
Err, Result, at, debug_warn, is_true,
|
||||
matrix::Event,
|
||||
result::FlatOk,
|
||||
utils::{IterStream, stream::ReadyExt},
|
||||
@@ -50,7 +50,7 @@ pub(crate) async fn search_events_route(
|
||||
|
||||
Ok(Response {
|
||||
search_categories: ResultCategories {
|
||||
room_events: room_events_result
|
||||
room_events: Box::pin(room_events_result)
|
||||
.await
|
||||
.unwrap_or_else(|| Ok(ResultRoomEvents::default()))?,
|
||||
},
|
||||
@@ -110,7 +110,12 @@ async fn category_room_events(
|
||||
limit,
|
||||
};
|
||||
|
||||
let (count, results) = services.rooms.search.search_pdus(&query).await.ok()?;
|
||||
let (count, results) = services
|
||||
.rooms
|
||||
.search
|
||||
.search_pdus(&query, sender_user)
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
results
|
||||
.collect::<Vec<_>>()
|
||||
@@ -144,6 +149,17 @@ async fn category_room_events(
|
||||
.map(at!(2))
|
||||
.flatten()
|
||||
.stream()
|
||||
.then(|mut pdu| async {
|
||||
if let Err(e) = services
|
||||
.rooms
|
||||
.pdu_metadata
|
||||
.add_bundled_aggregations_to_pdu(sender_user, &mut pdu)
|
||||
.await
|
||||
{
|
||||
debug_warn!("Failed to add bundled aggregations to search result: {e}");
|
||||
}
|
||||
pdu
|
||||
})
|
||||
.map(Event::into_format)
|
||||
.map(|result| SearchResult {
|
||||
rank: None,
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
///
|
||||
/// Get the supported login types of this server. One of these should be used as
|
||||
/// the `type` field when logging in.
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "login")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "login", level = "info")]
|
||||
pub(crate) async fn get_login_types_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -53,7 +53,7 @@ pub(crate) async fn get_login_types_route(
|
||||
/// Authenticates the given user by its ID and its password.
|
||||
///
|
||||
/// Returns the user ID if successful, and an error otherwise.
|
||||
#[tracing::instrument(skip_all, fields(%user_id), name = "password")]
|
||||
#[tracing::instrument(skip_all, fields(%user_id), name = "password", level = "debug")]
|
||||
pub(crate) async fn password_login(
|
||||
services: &Services,
|
||||
user_id: &UserId,
|
||||
@@ -96,7 +96,7 @@ pub(crate) async fn password_login(
|
||||
///
|
||||
/// Creates the user if the user is found in the LDAP and do not already have an
|
||||
/// account.
|
||||
#[tracing::instrument(skip_all, fields(%user_id), name = "ldap")]
|
||||
#[tracing::instrument(skip_all, fields(%user_id), name = "ldap", level = "debug")]
|
||||
pub(super) async fn ldap_login(
|
||||
services: &Services,
|
||||
user_id: &UserId,
|
||||
@@ -212,7 +212,7 @@ pub(crate) async fn handle_login(
|
||||
/// Note: You can use [`GET
|
||||
/// /_matrix/client/r0/login`](fn.get_supported_versions_route.html) to see
|
||||
/// supported login types.
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "login")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "login", level = "info")]
|
||||
pub(crate) async fn login_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -345,7 +345,7 @@ pub(crate) async fn login_route(
|
||||
/// to log in with the m.login.token flow.
|
||||
///
|
||||
/// <https://spec.matrix.org/v1.13/client-server-api/#post_matrixclientv1loginget_token>
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "login_token")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "login_token", level = "info")]
|
||||
pub(crate) async fn login_token_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -413,7 +413,7 @@ pub(crate) async fn login_token_route(
|
||||
/// last seen ts)
|
||||
/// - Forgets to-device events
|
||||
/// - Triggers device list updates
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "logout")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "logout", level = "info")]
|
||||
pub(crate) async fn logout_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -440,7 +440,7 @@ pub(crate) async fn logout_route(
|
||||
/// Note: This is equivalent to calling [`GET
|
||||
/// /_matrix/client/r0/logout`](fn.logout_route.html) from each device of this
|
||||
/// user.
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "logout")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "logout", level = "info")]
|
||||
pub(crate) async fn logout_all_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
|
||||
@@ -140,8 +140,8 @@ pub(crate) async fn get_state_events_for_key_route(
|
||||
.await
|
||||
.map_err(|_| {
|
||||
err!(Request(NotFound(debug_warn!(
|
||||
room_id = ?body.room_id,
|
||||
event_type = ?body.event_type,
|
||||
room_id = %body.room_id,
|
||||
event_type = %body.event_type,
|
||||
"State event not found in room.",
|
||||
))))
|
||||
})?;
|
||||
@@ -158,7 +158,7 @@ pub(crate) async fn get_state_events_for_key_route(
|
||||
"content": event.content(),
|
||||
"event_id": event.event_id(),
|
||||
"origin_server_ts": event.origin_server_ts(),
|
||||
"room_id": event.room_id(),
|
||||
"room_id": event.room_id_or_hash(),
|
||||
"sender": event.sender(),
|
||||
"state_key": event.state_key(),
|
||||
"type": event.kind(),
|
||||
@@ -226,7 +226,7 @@ async fn allowed_to_send_state_event(
|
||||
match event_type {
|
||||
| StateEventType::RoomCreate => {
|
||||
return Err!(Request(BadJson(debug_warn!(
|
||||
?room_id,
|
||||
%room_id,
|
||||
"You cannot update m.room.create after a room has been created."
|
||||
))));
|
||||
},
|
||||
@@ -237,7 +237,7 @@ async fn allowed_to_send_state_event(
|
||||
| Ok(acl_content) => {
|
||||
if acl_content.allow_is_empty() {
|
||||
return Err!(Request(BadJson(debug_warn!(
|
||||
?room_id,
|
||||
%room_id,
|
||||
"Sending an ACL event with an empty allow key will permanently \
|
||||
brick the room for non-conduwuit's as this equates to no servers \
|
||||
being allowed to participate in this room."
|
||||
@@ -246,7 +246,7 @@ async fn allowed_to_send_state_event(
|
||||
|
||||
if acl_content.deny_contains("*") && acl_content.allow_contains("*") {
|
||||
return Err!(Request(BadJson(debug_warn!(
|
||||
?room_id,
|
||||
%room_id,
|
||||
"Sending an ACL event with a deny and allow key value of \"*\" will \
|
||||
permanently brick the room for non-conduwuit's as this equates to \
|
||||
no servers being allowed to participate in this room."
|
||||
@@ -258,7 +258,7 @@ async fn allowed_to_send_state_event(
|
||||
&& !acl_content.allow_contains(services.globals.server_name().as_str())
|
||||
{
|
||||
return Err!(Request(BadJson(debug_warn!(
|
||||
?room_id,
|
||||
%room_id,
|
||||
"Sending an ACL event with a deny key value of \"*\" and without \
|
||||
your own server name in the allow key will result in you being \
|
||||
unable to participate in this room."
|
||||
@@ -270,7 +270,7 @@ async fn allowed_to_send_state_event(
|
||||
&& !acl_content.allow_contains(services.globals.server_name().as_str())
|
||||
{
|
||||
return Err!(Request(BadJson(debug_warn!(
|
||||
?room_id,
|
||||
%room_id,
|
||||
"Sending an ACL event for an allow key without \"*\" and without \
|
||||
your own server name in the allow key will result in you being \
|
||||
unable to participate in this room."
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use conduwuit::{
|
||||
Event, PduCount, Result, err,
|
||||
Event, PduCount, Result, debug_warn, err,
|
||||
matrix::pdu::PduEvent,
|
||||
ref_at, trace,
|
||||
utils::stream::{BroadbandExt, ReadyExt, TryIgnore},
|
||||
@@ -53,7 +53,7 @@ async fn load_timeline(
|
||||
let last_timeline_count = services
|
||||
.rooms
|
||||
.timeline
|
||||
.last_timeline_count(Some(sender_user), room_id)
|
||||
.last_timeline_count(room_id)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
err!(Database(warn!("Failed to fetch end of room timeline: {}", err)))
|
||||
@@ -71,13 +71,24 @@ async fn load_timeline(
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_rev(
|
||||
Some(sender_user),
|
||||
room_id,
|
||||
ending_count.map(|count| count.saturating_add(1)),
|
||||
)
|
||||
.pdus_rev(room_id, ending_count.map(|count| count.saturating_add(1)))
|
||||
.ignore_err()
|
||||
.ready_take_while(move |&(pducount, _)| pducount > starting_count)
|
||||
.map(move |mut pdu| {
|
||||
pdu.1.set_unsigned(Some(sender_user));
|
||||
pdu
|
||||
})
|
||||
.then(async move |mut pdu| {
|
||||
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
|
||||
})
|
||||
.boxed()
|
||||
},
|
||||
| None => {
|
||||
@@ -86,12 +97,23 @@ async fn load_timeline(
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_rev(
|
||||
Some(sender_user),
|
||||
room_id,
|
||||
ending_count.map(|count| count.saturating_add(1)),
|
||||
)
|
||||
.pdus_rev(room_id, ending_count.map(|count| count.saturating_add(1)))
|
||||
.ignore_err()
|
||||
.map(move |mut pdu| {
|
||||
pdu.1.set_unsigned(Some(sender_user));
|
||||
pdu
|
||||
})
|
||||
.then(async move |mut pdu| {
|
||||
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
|
||||
})
|
||||
.boxed()
|
||||
},
|
||||
};
|
||||
|
||||
@@ -50,8 +50,8 @@
|
||||
level = "debug",
|
||||
skip_all,
|
||||
fields(
|
||||
room_id = ?room_id,
|
||||
syncing_user = ?sync_context.syncing_user,
|
||||
room_id = %room_id,
|
||||
syncing_user = %sync_context.syncing_user,
|
||||
),
|
||||
)]
|
||||
pub(super) async fn load_joined_room(
|
||||
@@ -578,7 +578,7 @@ async fn build_notification_counts(
|
||||
)
|
||||
.await;
|
||||
|
||||
trace!(?notification_count, ?highlight_count, "syncing new notification counts");
|
||||
trace!(%notification_count, %highlight_count, "syncing new notification counts");
|
||||
|
||||
Ok(Some(UnreadNotificationsCount {
|
||||
notification_count: Some(notification_count),
|
||||
@@ -692,8 +692,8 @@ async fn build_room_summary(
|
||||
};
|
||||
|
||||
trace!(
|
||||
?joined_member_count,
|
||||
?invited_member_count,
|
||||
%joined_member_count,
|
||||
%invited_member_count,
|
||||
heroes_length = heroes.as_ref().map(HashSet::len),
|
||||
"syncing updated summary"
|
||||
);
|
||||
|
||||
@@ -307,8 +307,8 @@ async fn build_left_state_and_timeline(
|
||||
}
|
||||
|
||||
trace!(
|
||||
?timeline_start_count,
|
||||
?timeline_end_count,
|
||||
%timeline_start_count,
|
||||
%timeline_end_count,
|
||||
"syncing {} timeline events (limited = {}) and {} state events",
|
||||
timeline.pdus.len(),
|
||||
timeline.limited,
|
||||
|
||||
@@ -275,7 +275,7 @@ pub(crate) async fn build_sync_events(
|
||||
match joined_room {
|
||||
| Ok((room, updates)) => Some((room_id, room, updates)),
|
||||
| Err(err) => {
|
||||
warn!(?err, ?room_id, "error loading joined room {}", room_id);
|
||||
warn!(?err, %room_id, "error loading joined room");
|
||||
None
|
||||
},
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ pub(super) async fn build_state_incremental<'a>(
|
||||
let last_pdu_of_last_sync = services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_rev(Some(sender_user), room_id, Some(last_sync_end_count.saturating_add(1)))
|
||||
.pdus_rev(room_id, Some(last_sync_end_count.saturating_add(1)))
|
||||
.boxed()
|
||||
.next()
|
||||
.await
|
||||
@@ -217,7 +217,7 @@ pub(super) async fn build_state_incremental<'a>(
|
||||
the performance penalty is acceptable.
|
||||
*/
|
||||
|
||||
trace!(?timeline_is_linear, ?timeline.limited, "computing state for incremental sync");
|
||||
trace!(%timeline_is_linear, %timeline.limited, "computing state for incremental sync");
|
||||
|
||||
// fetch the shorteventids of state events in the timeline
|
||||
let state_events_in_timeline: BTreeSet<ShortEventId> = services
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{
|
||||
Result, at,
|
||||
Result, at, debug_warn,
|
||||
matrix::{
|
||||
Event,
|
||||
pdu::{PduCount, PduEvent},
|
||||
@@ -45,6 +45,17 @@ pub(crate) async fn get_threads_route(
|
||||
.await
|
||||
.then_some((count, pdu))
|
||||
})
|
||||
.then(|(count, mut pdu)| async move {
|
||||
if let Err(e) = services
|
||||
.rooms
|
||||
.pdu_metadata
|
||||
.add_bundled_aggregations_to_pdu(body.sender_user(), &mut pdu)
|
||||
.await
|
||||
{
|
||||
debug_warn!("Failed to add bundled aggregations to thread: {e}");
|
||||
}
|
||||
(count, pdu)
|
||||
})
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
/// TODO: Implement pagination, currently this just returns everything
|
||||
///
|
||||
/// An implementation of [MSC2666](https://github.com/matrix-org/matrix-spec-proposals/pull/2666)
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "mutual_rooms")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "mutual_rooms", level = "info")]
|
||||
pub(crate) async fn get_mutual_rooms_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use axum::{Json, extract::State, response::IntoResponse};
|
||||
use conduwuit::{Error, Result};
|
||||
use futures::StreamExt;
|
||||
use ruma::api::client::{
|
||||
discovery::{
|
||||
discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo},
|
||||
@@ -71,21 +70,18 @@ pub(crate) async fn well_known_support(
|
||||
|
||||
// Try to add admin users as contacts if no contacts are configured
|
||||
if contacts.is_empty() {
|
||||
if let Ok(admin_room) = services.admin.get_admin_room().await {
|
||||
let admin_users = services.rooms.state_cache.room_members(&admin_room);
|
||||
let mut stream = admin_users;
|
||||
let admin_users = services.admin.get_admins().await;
|
||||
|
||||
while let Some(user_id) = stream.next().await {
|
||||
// Skip server user
|
||||
if *user_id == services.globals.server_user {
|
||||
continue;
|
||||
}
|
||||
contacts.push(Contact {
|
||||
role: role_value.clone(),
|
||||
email_address: None,
|
||||
matrix_id: Some(user_id.to_owned()),
|
||||
});
|
||||
for user_id in &admin_users {
|
||||
if *user_id == services.globals.server_user {
|
||||
continue;
|
||||
}
|
||||
|
||||
contacts.push(Contact {
|
||||
role: role_value.clone(),
|
||||
email_address: None,
|
||||
matrix_id: Some(user_id.to_owned()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -137,12 +137,30 @@ pub(super) async fn auth(
|
||||
| (
|
||||
AuthScheme::AccessToken | AuthScheme::AccessTokenOptional | AuthScheme::None,
|
||||
Token::User((user_id, device_id)),
|
||||
) => Ok(Auth {
|
||||
origin: None,
|
||||
sender_user: Some(user_id),
|
||||
sender_device: Some(device_id),
|
||||
appservice_info: None,
|
||||
}),
|
||||
) => {
|
||||
let is_locked = services.users.is_locked(&user_id).await.map_err(|e| {
|
||||
err!(Request(Forbidden(warn!("Failed to check user lock status: {e}"))))
|
||||
})?;
|
||||
if is_locked {
|
||||
// Only /logout and /logout/all are allowed for locked users
|
||||
if !matches!(
|
||||
metadata,
|
||||
&ruma::api::client::session::logout::v3::Request::METADATA
|
||||
| &ruma::api::client::session::logout_all::v3::Request::METADATA
|
||||
) {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::UserLocked,
|
||||
"This account has been locked.",
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(Auth {
|
||||
origin: None,
|
||||
sender_user: Some(user_id),
|
||||
sender_device: Some(device_id),
|
||||
appservice_info: None,
|
||||
})
|
||||
},
|
||||
| (AuthScheme::ServerSignatures, Token::None) =>
|
||||
Ok(auth_server(services, request, json_body).await?),
|
||||
| (
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{
|
||||
Event, PduCount, Result,
|
||||
result::LogErr,
|
||||
utils::{IterStream, ReadyExt, stream::TryTools},
|
||||
};
|
||||
use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||
@@ -62,7 +63,7 @@ pub(crate) async fn get_backfill_route(
|
||||
pdus: services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_rev(None, &body.room_id, Some(from.saturating_add(1)))
|
||||
.pdus_rev(&body.room_id, Some(from.saturating_add(1)))
|
||||
.try_take(limit)
|
||||
.try_filter_map(|(_, pdu)| async move {
|
||||
Ok(services
|
||||
@@ -72,6 +73,15 @@ pub(crate) async fn get_backfill_route(
|
||||
.await
|
||||
.then_some(pdu))
|
||||
})
|
||||
.and_then(async |mut pdu| {
|
||||
// Strip the transaction ID, as that is private
|
||||
pdu.remove_transaction_id().log_err().ok();
|
||||
// Add age, as this is specified
|
||||
pdu.add_age().log_err().ok();
|
||||
// It's not clear if we should strip or add any more data, leave as is.
|
||||
// In particular: Redaction?
|
||||
Ok(pdu)
|
||||
})
|
||||
.try_filter_map(|pdu| async move {
|
||||
Ok(services
|
||||
.rooms
|
||||
|
||||
@@ -40,7 +40,7 @@ pub(crate) async fn get_missing_events_route(
|
||||
while i < queued_events.len() && events.len() < limit {
|
||||
let Ok(pdu) = services.rooms.timeline.get_pdu(&queued_events[i]).await else {
|
||||
debug!(
|
||||
?body.origin,
|
||||
body.origin = body.origin.as_ref().map(tracing::field::display),
|
||||
"Event {} does not exist locally, skipping", &queued_events[i]
|
||||
);
|
||||
i = i.saturating_add(1);
|
||||
@@ -59,7 +59,7 @@ pub(crate) async fn get_missing_events_route(
|
||||
.await
|
||||
{
|
||||
debug!(
|
||||
?body.origin,
|
||||
body.origin = body.origin.as_ref().map(tracing::field::display),
|
||||
"Server cannot see {:?} in {:?}, skipping", pdu.event_id, pdu.room_id
|
||||
);
|
||||
i = i.saturating_add(1);
|
||||
@@ -68,7 +68,7 @@ pub(crate) async fn get_missing_events_route(
|
||||
|
||||
let Ok(event) = to_canonical_object(&pdu) else {
|
||||
debug_error!(
|
||||
?body.origin,
|
||||
body.origin = body.origin.as_ref().map(tracing::field::display),
|
||||
"Failed to convert PDU in database to canonical JSON: {pdu:?}"
|
||||
);
|
||||
i = i.saturating_add(1);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
use ruma::{
|
||||
CanonicalJsonValue, OwnedUserId, UserId,
|
||||
api::{client::error::ErrorKind, federation::membership::create_invite},
|
||||
events::room::member::{MembershipState, RoomMemberEventContent},
|
||||
serde::JsonObject,
|
||||
};
|
||||
|
||||
@@ -18,7 +19,7 @@
|
||||
/// # `PUT /_matrix/federation/v2/invite/{roomId}/{eventId}`
|
||||
///
|
||||
/// Invites a remote user to a room.
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "invite")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "invite", level = "info")]
|
||||
pub(crate) async fn create_invite_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
@@ -60,6 +61,46 @@ pub(crate) async fn create_invite_route(
|
||||
let mut signed_event = utils::to_canonical_object(&body.event)
|
||||
.map_err(|_| err!(Request(InvalidParam("Invite event is invalid."))))?;
|
||||
|
||||
// Ensure this is a membership event
|
||||
if signed_event
|
||||
.get("type")
|
||||
.expect("event must have a type")
|
||||
.as_str()
|
||||
.expect("type must be a string")
|
||||
!= "m.room.member"
|
||||
{
|
||||
return Err!(Request(BadJson(
|
||||
"Not allowed to send non-membership event to invite endpoint."
|
||||
)));
|
||||
}
|
||||
|
||||
let content: RoomMemberEventContent = serde_json::from_value(
|
||||
signed_event
|
||||
.get("content")
|
||||
.ok_or_else(|| err!(Request(BadJson("Event missing content property"))))?
|
||||
.clone()
|
||||
.into(),
|
||||
)
|
||||
.map_err(|e| err!(Request(BadJson(warn!("Event content is empty or invalid: {e}")))))?;
|
||||
|
||||
// Ensure this is an invite membership event
|
||||
if content.membership != MembershipState::Invite {
|
||||
return Err!(Request(BadJson(
|
||||
"Not allowed to send a non-invite membership event to invite endpoint."
|
||||
)));
|
||||
}
|
||||
|
||||
// Ensure the sending user isn't a lying bozo
|
||||
let sender_server = signed_event
|
||||
.get("sender")
|
||||
.try_into()
|
||||
.map(UserId::server_name)
|
||||
.map_err(|e| err!(Request(InvalidParam("Invalid sender property: {e}"))))?;
|
||||
if sender_server != body.origin() {
|
||||
return Err!(Request(Forbidden("Sender's server does not match the origin server.",)));
|
||||
}
|
||||
|
||||
// Ensure the target user belongs to this server
|
||||
let recipient_user: OwnedUserId = signed_event
|
||||
.get("state_key")
|
||||
.try_into()
|
||||
@@ -107,6 +148,15 @@ pub(crate) async fn create_invite_route(
|
||||
return Err!(Request(Forbidden("This server does not allow room invites.")));
|
||||
}
|
||||
|
||||
if let Err(e) = services
|
||||
.antispam
|
||||
.user_may_invite(sender_user.to_owned(), recipient_user.clone(), body.room_id.clone())
|
||||
.await
|
||||
{
|
||||
warn!("Antispam rejected invite: {e:?}");
|
||||
return Err!(Request(Forbidden("Invite rejected by antispam service.")));
|
||||
}
|
||||
|
||||
let mut invite_state = body.invite_room_state.clone();
|
||||
|
||||
let mut event: JsonObject = serde_json::from_str(body.event.get())
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::borrow::ToOwned;
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{
|
||||
Err, Error, Result, debug_info, info, matrix::pdu::PduBuilder, utils::IterStream, warn,
|
||||
};
|
||||
use conduwuit::{Err, Error, Result, debug, debug_info, info, matrix::pdu::PduBuilder, warn};
|
||||
use conduwuit_service::Services;
|
||||
use futures::StreamExt;
|
||||
use ruma::{
|
||||
@@ -22,7 +22,7 @@
|
||||
/// # `GET /_matrix/federation/v1/make_join/{roomId}/{userId}`
|
||||
///
|
||||
/// Creates a join template.
|
||||
#[tracing::instrument(skip_all, fields(room_id = %body.room_id, user_id = %body.user_id, origin = %body.origin()))]
|
||||
#[tracing::instrument(skip_all, fields(room_id = %body.room_id, user_id = %body.user_id, origin = %body.origin()), level = "info")]
|
||||
pub(crate) async fn create_join_event_template_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<prepare_join_event::v1::Request>,
|
||||
@@ -122,6 +122,16 @@ pub(crate) async fn create_join_event_template_route(
|
||||
None
|
||||
}
|
||||
};
|
||||
if services.antispam.check_all_joins() && join_authorized_via_users_server.is_none() {
|
||||
if services
|
||||
.antispam
|
||||
.meowlnir_accept_make_join(body.room_id.clone(), body.user_id.clone())
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return Err!(Request(Forbidden("Antispam rejected join request.")));
|
||||
}
|
||||
}
|
||||
|
||||
let (_pdu, mut pdu_json) = services
|
||||
.rooms
|
||||
@@ -136,7 +146,6 @@ pub(crate) async fn create_join_event_template_route(
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
// room v3 and above removed the "event_id" field from remote PDU format
|
||||
@@ -192,25 +201,44 @@ pub(crate) async fn user_can_perform_restricted_join(
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if r.allow
|
||||
.iter()
|
||||
.filter_map(|rule| {
|
||||
if let AllowRule::RoomMembership(membership) = rule {
|
||||
Some(membership)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.stream()
|
||||
.any(|m| services.rooms.state_cache.is_joined(user_id, &m.room_id))
|
||||
.await
|
||||
{
|
||||
Ok(true)
|
||||
} else {
|
||||
Err!(Request(UnableToAuthorizeJoin(
|
||||
"Joining user is not known to be in any required room."
|
||||
)))
|
||||
for allow_rule in &r.allow {
|
||||
match allow_rule {
|
||||
| AllowRule::RoomMembership(membership) => {
|
||||
if services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(user_id, &membership.room_id)
|
||||
.await
|
||||
{
|
||||
debug!(
|
||||
"User {} is allowed to join room {} via membership in room {}",
|
||||
user_id, room_id, membership.room_id
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
},
|
||||
| AllowRule::UnstableSpamChecker =>
|
||||
return match services
|
||||
.antispam
|
||||
.meowlnir_accept_make_join(room_id.to_owned(), user_id.to_owned())
|
||||
.await
|
||||
{
|
||||
| Ok(()) => Ok(true),
|
||||
| Err(_) => Err!(Request(Forbidden("Antispam rejected join request."))),
|
||||
},
|
||||
| _ => {
|
||||
debug_info!(
|
||||
"Unsupported allow rule in restricted join for room {}: {:?}",
|
||||
room_id,
|
||||
allow_rule
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Err!(Request(UnableToAuthorizeJoin(
|
||||
"Joining user is not known to be in any required room."
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_strip_event_id(
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduwuit::{
|
||||
Err, Error, Result, debug,
|
||||
debug::INFO_SPAN_LEVEL,
|
||||
debug_warn, err, error,
|
||||
Err, Error, Result, debug, debug_warn, err, error,
|
||||
result::LogErr,
|
||||
trace,
|
||||
utils::{
|
||||
@@ -48,7 +46,7 @@
|
||||
/// Push EDUs and PDUs to this server.
|
||||
#[tracing::instrument(
|
||||
name = "txn",
|
||||
level = INFO_SPAN_LEVEL,
|
||||
level = "debug",
|
||||
skip_all,
|
||||
fields(
|
||||
%client,
|
||||
@@ -83,8 +81,8 @@ pub(crate) async fn send_transaction_message_route(
|
||||
pdus = body.pdus.len(),
|
||||
edus = body.edus.len(),
|
||||
elapsed = ?txn_start_time.elapsed(),
|
||||
id = ?body.transaction_id,
|
||||
origin =?body.origin(),
|
||||
id = %body.transaction_id,
|
||||
origin = %body.origin(),
|
||||
"Starting txn",
|
||||
);
|
||||
|
||||
@@ -110,8 +108,8 @@ pub(crate) async fn send_transaction_message_route(
|
||||
pdus = body.pdus.len(),
|
||||
edus = body.edus.len(),
|
||||
elapsed = ?txn_start_time.elapsed(),
|
||||
id = ?body.transaction_id,
|
||||
origin =?body.origin(),
|
||||
id = %body.transaction_id,
|
||||
origin = %body.origin(),
|
||||
"Finished txn",
|
||||
);
|
||||
for (id, result) in &results {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
use crate::Ruma;
|
||||
|
||||
/// helper method for /send_join v1 and v2
|
||||
#[tracing::instrument(skip(services, pdu, omit_members), fields(room_id = room_id.as_str(), origin = origin.as_str()))]
|
||||
#[tracing::instrument(skip(services, pdu, omit_members), fields(room_id = room_id.as_str(), origin = origin.as_str()), level = "info")]
|
||||
async fn create_join_event(
|
||||
services: &Services,
|
||||
origin: &ServerName,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Error, Result};
|
||||
use futures::{FutureExt, StreamExt, TryFutureExt};
|
||||
@@ -96,6 +98,7 @@ pub(crate) async fn get_keys_route(
|
||||
&body.device_keys,
|
||||
|u| Some(u.server_name()) == body.origin.as_deref(),
|
||||
services.globals.allow_device_name_federation(),
|
||||
Duration::from_secs(0),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -124,7 +127,8 @@ pub(crate) async fn claim_keys_route(
|
||||
));
|
||||
}
|
||||
|
||||
let result = claim_keys_helper(&services, &body.one_time_keys).await?;
|
||||
let result =
|
||||
claim_keys_helper(&services, &body.one_time_keys, Duration::from_secs(0)).await?;
|
||||
|
||||
Ok(claim_keys::v1::Response { one_time_keys: result.one_time_keys })
|
||||
}
|
||||
|
||||
@@ -340,7 +340,7 @@ fn set<T>(key: &Key, val: T) -> Result<T>
|
||||
|
||||
#[tracing::instrument(
|
||||
name = "get",
|
||||
level = "trace"
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(?key)
|
||||
)]
|
||||
@@ -357,7 +357,7 @@ fn get<T>(key: &Key) -> Result<T>
|
||||
|
||||
#[tracing::instrument(
|
||||
name = "xchg",
|
||||
level = "trace"
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(?key, ?val)
|
||||
)]
|
||||
|
||||
@@ -59,7 +59,7 @@ fn deref(&self) -> &Self::Target { HANDLE.with_borrow_mut(|handle| self.load(han
|
||||
|
||||
/// Update the active configuration, returning prior configuration.
|
||||
#[implement(Manager)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
#[tracing::instrument(skip_all, level = "info")]
|
||||
pub fn update(&self, config: Config) -> Result<Arc<Config>> {
|
||||
let config = Arc::new(config);
|
||||
let new = Arc::into_raw(config);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||
path::{Path, PathBuf},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use conduwuit_macros::config_example_generator;
|
||||
@@ -18,7 +18,7 @@
|
||||
pub use figment::{Figment, value::Value as FigmentValue};
|
||||
use regex::RegexSet;
|
||||
use ruma::{
|
||||
OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, RoomVersionId,
|
||||
OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, RoomVersionId,
|
||||
api::client::discovery::discover_support::ContactRole,
|
||||
};
|
||||
use serde::{Deserialize, de::IgnoredAny};
|
||||
@@ -53,9 +53,14 @@
|
||||
### For more information, see:
|
||||
### https://continuwuity.org/configuration.html
|
||||
"#,
|
||||
ignore = "catchall well_known tls blurhashing allow_invalid_tls_certificates_yes_i_know_what_the_fuck_i_am_doing_with_this_and_i_know_this_is_insecure"
|
||||
ignore = "config_paths catchall well_known tls blurhashing \
|
||||
allow_invalid_tls_certificates_yes_i_know_what_the_fuck_i_am_doing_with_this_and_i_know_this_is_insecure antispam"
|
||||
)]
|
||||
pub struct Config {
|
||||
// Paths to config file(s). Not supposed to be set manually in the config file,
|
||||
// only updated dynamically from the --config option given at runtime.
|
||||
pub config_paths: Option<Vec<PathBuf>>,
|
||||
|
||||
/// The server_name is the pretty name of this server. It is used as a
|
||||
/// suffix for user and room IDs/aliases.
|
||||
///
|
||||
@@ -65,8 +70,8 @@ pub struct Config {
|
||||
/// Also see the `[global.well_known]` config section at the very bottom.
|
||||
///
|
||||
/// Examples of delegation:
|
||||
/// - https://puppygock.gay/.well-known/matrix/server
|
||||
/// - https://puppygock.gay/.well-known/matrix/client
|
||||
/// - https://continuwuity.org/.well-known/matrix/server
|
||||
/// - https://continuwuity.org/.well-known/matrix/client
|
||||
///
|
||||
/// YOU NEED TO EDIT THIS. THIS CANNOT BE CHANGED AFTER WITHOUT A DATABASE
|
||||
/// WIPE.
|
||||
@@ -703,10 +708,13 @@ pub struct Config {
|
||||
pub allow_unstable_room_versions: bool,
|
||||
|
||||
/// Default room version continuwuity will create rooms with.
|
||||
/// Note that this has to be a string since the room version is a string
|
||||
/// rather than an integer. Forgetting the quotes will make the server fail
|
||||
/// to start!
|
||||
///
|
||||
/// Per spec, room version 11 is the default.
|
||||
/// Per spec, room version "11" is the default.
|
||||
///
|
||||
/// default: 11
|
||||
/// default: "11"
|
||||
#[serde(default = "default_default_room_version")]
|
||||
pub default_room_version: RoomVersionId,
|
||||
|
||||
@@ -730,6 +738,13 @@ pub struct Config {
|
||||
#[serde(default = "default_otlp_filter", alias = "jaeger_filter")]
|
||||
pub otlp_filter: String,
|
||||
|
||||
/// Protocol to use for OTLP tracing export. Options are "http" or "grpc".
|
||||
/// The HTTP protocol uses port 4318 by default, while gRPC uses port 4317.
|
||||
///
|
||||
/// default: "http"
|
||||
#[serde(default = "default_otlp_protocol")]
|
||||
pub otlp_protocol: String,
|
||||
|
||||
/// If the 'perf_measurements' compile-time feature is enabled, enables
|
||||
/// collecting folded stack trace profile of tracing spans using
|
||||
/// tracing_flame. The resulting profile can be visualized with inferno[1],
|
||||
@@ -1745,7 +1760,7 @@ pub struct Config {
|
||||
/// a normal continuwuity admin command. The reply will be publicly visible
|
||||
/// to the room, originating from the sender.
|
||||
///
|
||||
/// example: \\!admin debug ping puppygock.gay
|
||||
/// example: \\!admin debug ping continuwuity.org
|
||||
#[serde(default = "true_fn")]
|
||||
pub admin_escape_commands: bool,
|
||||
|
||||
@@ -1763,7 +1778,8 @@ pub struct Config {
|
||||
/// For example: `./continuwuity --execute "server admin-notice continuwuity
|
||||
/// has started up at $(date)"`
|
||||
///
|
||||
/// example: admin_execute = ["debug ping puppygock.gay", "debug echo hi"]`
|
||||
/// example: admin_execute = ["debug ping continuwuity.org", "debug echo
|
||||
/// hi"]`
|
||||
///
|
||||
/// default: []
|
||||
#[serde(default)]
|
||||
@@ -1804,6 +1820,22 @@ pub struct Config {
|
||||
#[serde(default = "default_admin_room_tag")]
|
||||
pub admin_room_tag: String,
|
||||
|
||||
/// A list of Matrix IDs that are qualified as server admins.
|
||||
///
|
||||
/// Any Matrix IDs within this list are regarded as an admin
|
||||
/// regardless of whether they are in the admin room or not
|
||||
///
|
||||
/// default: []
|
||||
#[serde(default)]
|
||||
pub admins_list: Vec<OwnedUserId>,
|
||||
|
||||
/// Defines whether those within the admin room are added to the
|
||||
/// admins_list.
|
||||
///
|
||||
/// default: true
|
||||
#[serde(default = "true_fn")]
|
||||
pub admins_from_room: bool,
|
||||
|
||||
/// Sentry.io crash/panic reporting, performance monitoring/metrics, etc.
|
||||
/// This is NOT enabled by default.
|
||||
#[serde(default)]
|
||||
@@ -1856,7 +1888,7 @@ pub struct Config {
|
||||
|
||||
/// Enable the tokio-console. This option is only relevant to developers.
|
||||
///
|
||||
/// For more information, see:
|
||||
/// For more information, see:
|
||||
/// https://continuwuity.org/development.html#debugging-with-tokio-console
|
||||
#[serde(default)]
|
||||
pub tokio_console: bool,
|
||||
@@ -1993,6 +2025,10 @@ pub struct Config {
|
||||
#[serde(default)]
|
||||
pub ldap: LdapConfig,
|
||||
|
||||
/// Configuration for antispam support
|
||||
#[serde(default)]
|
||||
pub antispam: Option<Antispam>,
|
||||
|
||||
// external structure; separate section
|
||||
#[serde(default)]
|
||||
pub blurhashing: BlurhashConfig,
|
||||
@@ -2209,6 +2245,57 @@ struct ListeningAddr {
|
||||
addrs: Either<IpAddr, Vec<IpAddr>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Antispam {
|
||||
pub meowlnir: Option<MeowlnirConfig>,
|
||||
pub draupnir: Option<DraupnirConfig>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[config_example_generator(
|
||||
filename = "conduwuit-example.toml",
|
||||
section = "global.antispam.meowlnir"
|
||||
)]
|
||||
pub struct MeowlnirConfig {
|
||||
/// The base URL on which to contact Meowlnir (before /_meowlnir/antispam).
|
||||
///
|
||||
/// Example: "http://127.0.0.1:29339"
|
||||
pub base_url: Url,
|
||||
|
||||
/// The authentication secret defined in antispam->secret. Required for
|
||||
/// continuwuity to talk to Meowlnir.
|
||||
pub secret: String,
|
||||
|
||||
/// The management room for which to send requests
|
||||
pub management_room: OwnedRoomId,
|
||||
|
||||
/// If enabled run all federated join attempts (both federated and local)
|
||||
/// through the Meowlnir anti-spam checks.
|
||||
///
|
||||
/// By default, only join attempts for rooms with the `fi.mau.spam_checker`
|
||||
/// restricted join rule are checked.
|
||||
#[serde(default)]
|
||||
pub check_all_joins: bool,
|
||||
}
|
||||
|
||||
// TODO: the DraupnirConfig and MeowlnirConfig are basically identical.
|
||||
// Maybe management_room could just become an Option<> and these structs merged?
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[config_example_generator(
|
||||
filename = "conduwuit-example.toml",
|
||||
section = "global.antispam.draupnir"
|
||||
)]
|
||||
pub struct DraupnirConfig {
|
||||
/// The base URL on which to contact Draupnir (before /api/).
|
||||
///
|
||||
/// Example: "http://127.0.0.1:29339"
|
||||
pub base_url: Url,
|
||||
|
||||
/// The authentication secret defined in
|
||||
/// web->synapseHTTPAntispam->authorization
|
||||
pub secret: String,
|
||||
}
|
||||
|
||||
const DEPRECATED_KEYS: &[&str; 9] = &[
|
||||
"cache_capacity",
|
||||
"conduit_cache_capacity_modifier",
|
||||
@@ -2223,26 +2310,24 @@ struct ListeningAddr {
|
||||
|
||||
impl Config {
|
||||
/// Pre-initialize config
|
||||
pub fn load<'a, I>(paths: I) -> Result<Figment>
|
||||
where
|
||||
I: Iterator<Item = &'a Path>,
|
||||
{
|
||||
pub fn load(paths: &[PathBuf]) -> Result<Figment> {
|
||||
let envs = [
|
||||
Env::var("CONDUIT_CONFIG"),
|
||||
Env::var("CONDUWUIT_CONFIG"),
|
||||
Env::var("CONTINUWUITY_CONFIG"),
|
||||
];
|
||||
|
||||
let config = envs
|
||||
let mut config = envs
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(Toml::file)
|
||||
.chain(paths.map(Toml::file))
|
||||
.chain(paths.iter().cloned().map(Toml::file))
|
||||
.fold(Figment::new(), |config, file| config.merge(file.nested()))
|
||||
.merge(Env::prefixed("CONDUIT_").global().split("__"))
|
||||
.merge(Env::prefixed("CONDUWUIT_").global().split("__"))
|
||||
.merge(Env::prefixed("CONTINUWUITY_").global().split("__"));
|
||||
|
||||
config = config.join(("config_paths", paths));
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
@@ -2413,6 +2498,8 @@ fn default_otlp_filter() -> String {
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
fn default_otlp_protocol() -> String { "http".to_owned() }
|
||||
|
||||
fn default_tracing_flame_output_path() -> String { "./tracing.folded".to_owned() }
|
||||
|
||||
fn default_trusted_servers() -> Vec<OwnedServerName> {
|
||||
|
||||
@@ -75,10 +75,12 @@ pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode {
|
||||
| ThreepidDenied
|
||||
| InviteBlocked
|
||||
| WrongRoomKeysVersion { .. }
|
||||
| UserSuspended
|
||||
| Forbidden { .. } => StatusCode::FORBIDDEN,
|
||||
|
||||
// 401
|
||||
| UnknownToken { .. } | MissingToken | Unauthorized => StatusCode::UNAUTHORIZED,
|
||||
| UnknownToken { .. } | MissingToken | Unauthorized | UserLocked =>
|
||||
StatusCode::UNAUTHORIZED,
|
||||
|
||||
// 400
|
||||
| _ => StatusCode::BAD_REQUEST,
|
||||
|
||||
@@ -14,11 +14,12 @@
|
||||
RoomVersionId::V9,
|
||||
RoomVersionId::V10,
|
||||
RoomVersionId::V11,
|
||||
RoomVersionId::V12,
|
||||
];
|
||||
|
||||
/// Experimental, partially supported room versions
|
||||
pub const UNSTABLE_ROOM_VERSIONS: &[RoomVersionId] =
|
||||
&[RoomVersionId::V3, RoomVersionId::V4, RoomVersionId::V5, RoomVersionId::V12];
|
||||
&[RoomVersionId::V3, RoomVersionId::V4, RoomVersionId::V5];
|
||||
|
||||
type RoomVersion = (RoomVersionId, RoomVersionStability);
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ fn from(event: Ref<'a, E>) -> Self {
|
||||
"content": content,
|
||||
"event_id": event.event_id(),
|
||||
"origin_server_ts": event.origin_server_ts(),
|
||||
"room_id": event.room_id(),
|
||||
"room_id": event.room_id_or_hash(),
|
||||
"sender": event.sender(),
|
||||
"type": event.kind(),
|
||||
});
|
||||
@@ -117,7 +117,7 @@ fn from(event: Ref<'a, E>) -> Self {
|
||||
"content": event.content(),
|
||||
"event_id": event.event_id(),
|
||||
"origin_server_ts": event.origin_server_ts(),
|
||||
"room_id": event.room_id(),
|
||||
"room_id": event.room_id_or_hash(),
|
||||
"sender": event.sender(),
|
||||
"state_key": event.state_key(),
|
||||
"type": event.kind(),
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::{borrow::Borrow, collections::BTreeMap};
|
||||
|
||||
use ruma::MilliSecondsSinceUnixEpoch;
|
||||
use serde_json::value::{RawValue as RawJsonValue, Value as JsonValue, to_raw_value};
|
||||
|
||||
use super::Pdu;
|
||||
use crate::{Result, err, implement};
|
||||
use crate::{Result, err, implement, result::LogErr};
|
||||
|
||||
/// Set the `unsigned` field of the PDU using only information in the PDU.
|
||||
/// Some unsigned data is already set within the database (eg. prev events,
|
||||
/// threads). Once this is done, other data must be calculated from the database
|
||||
/// (eg. relations) This is for server-to-client events.
|
||||
/// Backfill handles this itself.
|
||||
#[implement(Pdu)]
|
||||
pub fn set_unsigned(&mut self, user_id: Option<&ruma::UserId>) {
|
||||
if Some(self.sender.borrow()) != user_id {
|
||||
self.remove_transaction_id().log_err().ok();
|
||||
}
|
||||
self.add_age().log_err().ok();
|
||||
}
|
||||
|
||||
#[implement(Pdu)]
|
||||
pub fn remove_transaction_id(&mut self) -> Result {
|
||||
|
||||
@@ -532,8 +532,8 @@ pub async fn auth_check<E, F, Fut>(
|
||||
if sender_power_level < invite_level {
|
||||
warn!(
|
||||
%sender,
|
||||
has=?sender_power_level,
|
||||
required=?invite_level,
|
||||
has=%sender_power_level,
|
||||
required=%invite_level,
|
||||
"sender cannot send invites in this room"
|
||||
);
|
||||
return Ok(false);
|
||||
@@ -558,12 +558,19 @@ pub async fn auth_check<E, F, Fut>(
|
||||
// If type is m.room.power_levels
|
||||
if *incoming_event.event_type() == TimelineEventType::RoomPowerLevels {
|
||||
debug!("starting m.room.power_levels check");
|
||||
|
||||
let mut creators = BTreeSet::new();
|
||||
if room_version.explicitly_privilege_room_creators {
|
||||
creators.insert(create_event.sender().to_owned());
|
||||
for creator in room_create_content.additional_creators.iter().flatten() {
|
||||
creators.insert(creator.deserialize()?);
|
||||
}
|
||||
}
|
||||
match check_power_levels(
|
||||
room_version,
|
||||
incoming_event,
|
||||
power_levels_event.as_ref(),
|
||||
sender_power_level,
|
||||
&creators,
|
||||
) {
|
||||
| Some(required_pwr_lvl) =>
|
||||
if !required_pwr_lvl {
|
||||
@@ -598,8 +605,8 @@ pub async fn auth_check<E, F, Fut>(
|
||||
if !check_redaction(room_version, incoming_event, sender_power_level, redact_level)? {
|
||||
warn!(
|
||||
%sender,
|
||||
?sender_power_level,
|
||||
?redact_level,
|
||||
%sender_power_level,
|
||||
%redact_level,
|
||||
"redaction event was not allowed"
|
||||
);
|
||||
return Ok(false);
|
||||
@@ -765,11 +772,12 @@ struct GetThirdPartyInvite {
|
||||
power_levels_event.as_ref().is_some(),
|
||||
) || auth_user_pl >= invite_level;
|
||||
trace!(
|
||||
auth_user_pl=?auth_user_pl,
|
||||
invite_level=?invite_level,
|
||||
user_joined=?user_joined,
|
||||
okay_power=?okay_power,
|
||||
passing=?(user_joined && okay_power),
|
||||
%auth_user_pl,
|
||||
%auth_user_pl,
|
||||
%invite_level,
|
||||
%user_joined,
|
||||
%okay_power,
|
||||
passing=%(user_joined && okay_power),
|
||||
"user for join auth is valid check details"
|
||||
);
|
||||
user_joined && okay_power
|
||||
@@ -1204,7 +1212,7 @@ fn can_send_event(event: &impl Event, ple: Option<&impl Event>, user_level: Int)
|
||||
{
|
||||
warn!(
|
||||
%user_level,
|
||||
required=?event_type_power_level,
|
||||
required=%event_type_power_level,
|
||||
state_key=?event.state_key(),
|
||||
sender=%event.sender(),
|
||||
"state_key starts with @ but does not match sender",
|
||||
@@ -1221,8 +1229,8 @@ fn check_power_levels(
|
||||
power_event: &impl Event,
|
||||
previous_power_event: Option<&impl Event>,
|
||||
user_level: Int,
|
||||
creators: &BTreeSet<OwnedUserId>,
|
||||
) -> Option<bool> {
|
||||
// TODO(hydra): This function does not care about creators!
|
||||
match power_event.state_key() {
|
||||
| Some("") => {},
|
||||
| Some(key) => {
|
||||
@@ -1287,6 +1295,10 @@ fn check_power_levels(
|
||||
for user in user_levels_to_check {
|
||||
let old_level = old_state.users.get(user);
|
||||
let new_level = new_state.users.get(user);
|
||||
if new_level.is_some() && creators.contains(user) {
|
||||
warn!("creators cannot appear in the users list of m.room.power_levels");
|
||||
return Some(false); // cannot alter creator power level
|
||||
}
|
||||
if old_level.is_some() && new_level.is_some() && old_level == new_level {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use crate::util::map_err;
|
||||
|
||||
#[implement(Engine)]
|
||||
#[tracing::instrument(skip(self))]
|
||||
#[tracing::instrument(skip(self), level = "info")]
|
||||
pub fn backup(&self) -> Result {
|
||||
let mut engine = self.backup_engine()?;
|
||||
let config = &self.ctx.server.config;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#[tracing::instrument(
|
||||
parent = None,
|
||||
name = "rocksdb",
|
||||
level = "trace"
|
||||
level = "trace",
|
||||
skip(msg),
|
||||
)]
|
||||
pub(crate) fn handle(level: LogLevel, msg: &str) {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
use crate::{Context, or_else};
|
||||
|
||||
#[implement(Engine)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
#[tracing::instrument(skip_all, level = "info")]
|
||||
pub(crate) async fn open(ctx: Arc<Context>, desc: &[Descriptor]) -> Result<Arc<Self>> {
|
||||
let server = &ctx.server;
|
||||
let config = &server.config;
|
||||
@@ -63,7 +63,7 @@ pub(crate) async fn open(ctx: Arc<Context>, desc: &[Descriptor]) -> Result<Arc<S
|
||||
}
|
||||
|
||||
#[implement(Engine)]
|
||||
#[tracing::instrument(name = "configure", skip_all)]
|
||||
#[tracing::instrument(name = "configure", skip_all, level = "debug")]
|
||||
fn configure_cfds(
|
||||
ctx: &Arc<Context>,
|
||||
db_opts: &Options,
|
||||
@@ -119,7 +119,7 @@ fn configure_cfds(
|
||||
}
|
||||
|
||||
#[implement(Engine)]
|
||||
#[tracing::instrument(name = "discover", skip_all)]
|
||||
#[tracing::instrument(name = "discover", skip_all, level = "debug")]
|
||||
fn discover_cfs(path: &Path, opts: &Options) -> BTreeSet<String> {
|
||||
Db::list_cf(opts, path)
|
||||
.unwrap_or_default()
|
||||
|
||||
@@ -26,7 +26,7 @@ pub struct Options {
|
||||
#[implement(super::Map)]
|
||||
#[tracing::instrument(
|
||||
name = "compact",
|
||||
level = "info"
|
||||
level = "info",
|
||||
skip(self),
|
||||
fields(%self),
|
||||
)]
|
||||
|
||||
@@ -386,6 +386,10 @@ pub(super) fn open_list(db: &Arc<Engine>, maps: &[Descriptor]) -> Result<Maps> {
|
||||
name: "userid_suspension",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "userid_lock",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "userid_presenceid",
|
||||
..descriptor::RANDOM_SMALL
|
||||
|
||||
@@ -113,7 +113,7 @@ fn drop(&mut self) {
|
||||
}
|
||||
|
||||
#[implement(Pool)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
#[tracing::instrument(skip_all, level = "debug")]
|
||||
pub(crate) fn close(&self) {
|
||||
let workers = take(&mut *self.workers.lock());
|
||||
|
||||
@@ -147,8 +147,8 @@ pub(crate) fn close(&self) {
|
||||
.map(|result| result.map_err(Error::from_panic))
|
||||
.enumerate()
|
||||
.for_each(|(id, result)| match result {
|
||||
| Ok(()) => trace!(?id, "worker joined"),
|
||||
| Err(error) => error!(?id, "worker joined with error: {error}"),
|
||||
| Ok(()) => trace!(%id, "worker joined"),
|
||||
| Err(error) => error!(%id, "worker joined with error: {error}"),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -297,7 +297,7 @@ fn worker_init(&self, id: usize) {
|
||||
}
|
||||
|
||||
debug!(
|
||||
?group,
|
||||
%group,
|
||||
affinity = ?affinity.collect::<Vec<_>>(),
|
||||
"worker ready"
|
||||
);
|
||||
|
||||
@@ -105,8 +105,8 @@ pub(super) fn configure(server: &Arc<Server>) -> (usize, Vec<usize>, Vec<usize>)
|
||||
.unwrap_or("None"),
|
||||
?worker_counts,
|
||||
?queue_sizes,
|
||||
?total_workers,
|
||||
stream_width = ?stream::automatic_width(),
|
||||
%total_workers,
|
||||
stream_width = %stream::automatic_width(),
|
||||
"Frontend topology",
|
||||
);
|
||||
|
||||
@@ -139,13 +139,13 @@ fn update_stream_width(server: &Arc<Server>, num_queues: usize, total_workers: u
|
||||
let (old_width, new_width) = stream::set_width(req_width);
|
||||
let (old_amp, new_amp) = stream::set_amplification(req_amp);
|
||||
debug!(
|
||||
scale = ?config.stream_width_scale,
|
||||
?num_queues,
|
||||
?req_width,
|
||||
?old_width,
|
||||
?new_width,
|
||||
?old_amp,
|
||||
?new_amp,
|
||||
scale = %config.stream_width_scale,
|
||||
%num_queues,
|
||||
%req_width,
|
||||
%old_width,
|
||||
%new_width,
|
||||
%old_amp,
|
||||
%new_amp,
|
||||
"Updated global stream width"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,15 +63,16 @@ standard = [
|
||||
"systemd",
|
||||
"url_preview",
|
||||
"zstd_compression",
|
||||
"sentry_telemetry"
|
||||
"sentry_telemetry",
|
||||
"otlp_telemetry",
|
||||
"console",
|
||||
]
|
||||
full = [
|
||||
"standard",
|
||||
# "hardened_malloc", # Conflicts with jemalloc
|
||||
"jemalloc_prof",
|
||||
"perf_measurements",
|
||||
"tokio_console"
|
||||
# sentry_telemetry
|
||||
"tokio_console",
|
||||
]
|
||||
|
||||
blurhashing = [
|
||||
@@ -124,12 +125,15 @@ ldap = [
|
||||
media_thumbnail = [
|
||||
"conduwuit-service/media_thumbnail",
|
||||
]
|
||||
perf_measurements = [
|
||||
otlp_telemetry = [
|
||||
"dep:opentelemetry",
|
||||
"dep:tracing-flame",
|
||||
"dep:tracing-opentelemetry",
|
||||
"dep:opentelemetry_sdk",
|
||||
"dep:opentelemetry-otlp",
|
||||
]
|
||||
perf_measurements = [
|
||||
"dep:tracing-flame",
|
||||
"otlp_telemetry",
|
||||
"conduwuit-core/perf_measurements",
|
||||
"conduwuit-core/sentry_telemetry",
|
||||
]
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
debug_warn, err,
|
||||
log::{ConsoleFormat, ConsoleWriter, LogLevelReloadHandles, capture, fmt_span},
|
||||
result::UnwrapOrErr,
|
||||
warn,
|
||||
};
|
||||
#[cfg(feature = "perf_measurements")]
|
||||
#[cfg(feature = "otlp_telemetry")]
|
||||
use opentelemetry::trace::TracerProvider;
|
||||
#[cfg(feature = "otlp_telemetry")]
|
||||
use opentelemetry_otlp::WithExportConfig;
|
||||
use tracing_subscriber::{EnvFilter, Layer, Registry, fmt, layer::SubscriberExt, reload};
|
||||
|
||||
#[cfg(feature = "perf_measurements")]
|
||||
@@ -70,6 +73,57 @@ pub(crate) fn init(
|
||||
subscriber.with(sentry_layer.with_filter(sentry_reload_filter))
|
||||
};
|
||||
|
||||
#[cfg(feature = "otlp_telemetry")]
|
||||
let subscriber = {
|
||||
let otlp_filter = EnvFilter::try_new(&config.otlp_filter)
|
||||
.map_err(|e| err!(Config("otlp_filter", "{e}.")))?;
|
||||
|
||||
let otlp_layer = config.allow_otlp.then(|| {
|
||||
opentelemetry::global::set_text_map_propagator(
|
||||
opentelemetry_sdk::propagation::TraceContextPropagator::new(),
|
||||
);
|
||||
|
||||
let exporter = match config.otlp_protocol.as_str() {
|
||||
| "grpc" => opentelemetry_otlp::SpanExporter::builder()
|
||||
.with_tonic()
|
||||
.with_protocol(opentelemetry_otlp::Protocol::Grpc) // TODO: build from env when 0.32 is released
|
||||
.build()
|
||||
.expect("Failed to create OTLP gRPC exporter"),
|
||||
| "http" => opentelemetry_otlp::SpanExporter::builder()
|
||||
.with_http()
|
||||
.build()
|
||||
.expect("Failed to create OTLP HTTP exporter"),
|
||||
| protocol => {
|
||||
warn!(
|
||||
"Invalid OTLP protocol '{}', falling back to HTTP. Valid options are \
|
||||
'http' or 'grpc'.",
|
||||
protocol
|
||||
);
|
||||
opentelemetry_otlp::SpanExporter::builder()
|
||||
.with_http()
|
||||
.build()
|
||||
.expect("Failed to create OTLP HTTP exporter")
|
||||
},
|
||||
};
|
||||
|
||||
let provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
|
||||
.with_batch_exporter(exporter)
|
||||
.build();
|
||||
|
||||
let tracer = provider.tracer(conduwuit_core::name());
|
||||
|
||||
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
|
||||
|
||||
let (otlp_reload_filter, otlp_reload_handle) =
|
||||
reload::Layer::new(otlp_filter.clone());
|
||||
reload_handles.add("otlp", Box::new(otlp_reload_handle));
|
||||
|
||||
Some(telemetry.with_filter(otlp_reload_filter))
|
||||
});
|
||||
|
||||
subscriber.with(otlp_layer)
|
||||
};
|
||||
|
||||
#[cfg(feature = "perf_measurements")]
|
||||
let (subscriber, flame_guard) = {
|
||||
let (flame_layer, flame_guard) = if config.tracing_flame {
|
||||
@@ -89,35 +143,7 @@ pub(crate) fn init(
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let otlp_filter = EnvFilter::try_new(&config.otlp_filter)
|
||||
.map_err(|e| err!(Config("otlp_filter", "{e}.")))?;
|
||||
|
||||
let otlp_layer = config.allow_otlp.then(|| {
|
||||
opentelemetry::global::set_text_map_propagator(
|
||||
opentelemetry_sdk::propagation::TraceContextPropagator::new(),
|
||||
);
|
||||
|
||||
let exporter = opentelemetry_otlp::SpanExporter::builder()
|
||||
.with_http()
|
||||
.build()
|
||||
.expect("Failed to create OTLP exporter");
|
||||
|
||||
let provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
|
||||
.with_batch_exporter(exporter)
|
||||
.build();
|
||||
|
||||
let tracer = provider.tracer(conduwuit_core::name());
|
||||
|
||||
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
|
||||
|
||||
let (otlp_reload_filter, otlp_reload_handle) =
|
||||
reload::Layer::new(otlp_filter.clone());
|
||||
reload_handles.add("otlp", Box::new(otlp_reload_handle));
|
||||
|
||||
Some(telemetry.with_filter(otlp_reload_filter))
|
||||
});
|
||||
|
||||
let subscriber = subscriber.with(flame_layer).with(otlp_layer);
|
||||
let subscriber = subscriber.with(flame_layer);
|
||||
(subscriber, flame_guard)
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user