Compare commits

..

16 commits

Author SHA1 Message Date
Jason Volk
1c08c0cdac
Fix clippy::unnecessary-unwrap.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-29 16:16:46 +01:00
Jason Volk
374a5d6fa4
Add revoke_admin to service.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-29 16:16:45 +01:00
Jason Volk
5fff540775
Split state_cache service.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-29 16:16:45 +01:00
Jason Volk
f030ba590d
Outdent state_compressor service.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-29 16:16:45 +01:00
Jason Volk
73c4042331
Split timeline service.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-29 16:16:45 +01:00
Jason Volk
461da03ca5
Fix regression 75aadd5c6a
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-29 16:16:45 +01:00
Jason Volk
671d0619ca
Post-formatting aesthetic and spacing corrections
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-29 16:16:45 +01:00
Jason Volk
e8ddc9bd18
Cleanup/improve other async queries in some client handlers.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-29 16:16:44 +01:00
Jason Volk
4217479455
Toward abstracting Pdu into trait Event.
Co-authored-by: Jade Ellis <jade@ellis.link>
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-29 16:16:44 +01:00
Jason Volk
b01965b1ee
Dedup and parallelize current key backup count and etag fetching.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-29 16:16:44 +01:00
Jason Volk
90deb7ce1a
Macroize various remaining Error constructions.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-29 16:16:44 +01:00
Jason Volk
e02fa39688
Simplify api to send notices to admin room
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-29 16:16:44 +01:00
Jason Volk
84c3264c07
Use integrated error instead of panic on some legacy codepaths
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-29 16:16:43 +01:00
Jason Volk
7a22442295
Mitigate large futures
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-29 16:16:43 +01:00
Jason Volk
1103cf7290
Support optional device_id's in lazy-loading context.
Co-authored-by: Jade Ellis <jade@ellis.link>
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-29 16:16:43 +01:00
Jason Volk
bf086158bd
Modernize various sender_user/sender_device lets.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-29 16:16:43 +01:00
49 changed files with 297 additions and 731 deletions

View file

@ -17,7 +17,6 @@ jobs:
docs:
name: Build and Deploy Documentation
runs-on: ubuntu-latest
if: secrets.CLOUDFLARE_API_TOKEN != ''
steps:
- name: Sync repository

5
.github/FUNDING.yml vendored
View file

@ -1,5 +0,0 @@
github: [JadedBlueEyes]
# Doesn't support an array, so we can only list nex
ko_fi: nexy7574
custom:
- https://ko-fi.com/JadedBlueEyes

View file

@ -26,14 +26,6 @@ repos:
rev: v1.26.0
hooks:
- id: typos
- id: typos
name: commit-msg-typos
stages: [commit-msg]
- repo: https://github.com/crate-ci/committed
rev: v1.1.7
hooks:
- id: committed
- repo: local
hooks:
@ -45,3 +37,14 @@ repos:
pass_filenames: false
stages:
- pre-commit
- repo: local
hooks:
- id: cargo-clippy
name: cargo clippy
language: system
types: [rust]
pass_filenames: false
entry: cargo clippy --workspace --locked --no-deps --profile test -- -D warnings
stages:
- pre-commit

View file

@ -1,6 +1,6 @@
# Contributing guide
This page is about contributing to Continuwuity. The
This page is for about contributing to Continuwuity. The
[development](./development.md) page may be of interest for you as well.
If you would like to work on an [issue][issues] that is not assigned, preferably
@ -10,7 +10,7 @@ and comment on it.
### Linting and Formatting
It is mandatory all your changes satisfy the lints (clippy, rustc, rustdoc, etc)
and your code is formatted via the **nightly** rustfmt (`cargo +nightly fmt`). A lot of the
and your code is formatted via the **nightly** `cargo fmt`. A lot of the
`rustfmt.toml` features depend on nightly toolchain. It would be ideal if they
weren't nightly-exclusive features, but they currently still are. CI's rustfmt
uses nightly.
@ -21,91 +21,67 @@ comment saying why. Do not write inefficient code for the sake of satisfying
lints. If a lint is wrong and provides a more inefficient solution or
suggestion, allow the lint and mention that in a comment.
### Pre-commit Checks
### Running CI tests locally
Continuwuity uses pre-commit hooks to enforce various coding standards and catch common issues before they're committed. These checks include:
continuwuity's CI for tests, linting, formatting, audit, etc use
[`engage`][engage]. engage can be installed from nixpkgs or `cargo install
engage`. continuwuity's Nix flake devshell has the nixpkgs engage with `direnv`.
Use `engage --help` for more usage details.
- Code formatting and linting
- Typo detection (both in code and commit messages)
- Checking for large files
- Ensuring proper line endings and no trailing whitespace
- Validating YAML, JSON, and TOML files
- Checking for merge conflicts
To test, format, lint, etc that CI would do, install engage, allow the `.envrc`
file using `direnv allow`, and run `engage`.
You can run these checks locally by installing [prefligit](https://github.com/j178/prefligit):
All of the tasks are defined at the [engage.toml][engage.toml] file. You can
view all of them neatly by running `engage list`
If you would like to run only a specific engage task group, use `just`:
```bash
# Install prefligit using cargo-binstall
cargo binstall prefligit
- `engage just <group>`
- Example: `engage just lints`
# Install git hooks to run checks automatically
prefligit install
If you would like to run a specific engage task in a specific group, use `just
<GROUP> [TASK]`: `engage just lints cargo-fmt`
# Run all checks
prefligit --all-files
```
The following binaries are used in [`engage.toml`][engage.toml]:
Alternatively, you can use [pre-commit](https://pre-commit.com/):
```bash
# Install pre-commit
pip install pre-commit
# Install the hooks
pre-commit install
# Run all checks manually
pre-commit run --all-files
```
These same checks are run in CI via the prefligit-checks workflow to ensure consistency.
### Running tests locally
Tests, compilation, and linting can be run with standard Cargo commands:
```bash
# Run tests
cargo test
# Check compilation
cargo check --workspace
# Run lints
cargo clippy --workspace
# Auto-fix: cargo clippy --workspace --fix --allow-staged;
# Format code (must use nightly)
cargo +nightly fmt
```
- [`engage`][engage]
- `nix`
- [`direnv`][direnv]
- `rustc`
- `cargo`
- `cargo-fmt`
- `rustdoc`
- `cargo-clippy`
- [`cargo-audit`][cargo-audit]
- [`cargo-deb`][cargo-deb]
- [`lychee`][lychee]
- [`markdownlint-cli`][markdownlint-cli]
- `dpkg`
### Matrix tests
Continuwuity uses [Complement][complement] for Matrix protocol compliance testing. Complement tests are run manually by developers, and documentation on how to run these tests locally is currently being developed.
CI runs [Complement][complement], but currently does not fail if results from
the checked-in results differ with the new results. If your changes are done to
fix Matrix tests, note that in your pull request. If more Complement tests start
failing from your changes, please review the logs (they are uploaded as
artifacts) and determine if they're intended or not.
If your changes are done to fix Matrix tests, please note that in your pull request. If more Complement tests start failing from your changes, please review the logs and determine if they're intended or not.
If you'd like to run Complement locally using Nix, see the
[testing](development/testing.md) page.
[Sytest][sytest] is currently unsupported.
[Sytest][sytest] support will come soon.
### Writing documentation
Continuwuity's website uses [`mdbook`][mdbook] and is deployed via CI using Cloudflare Pages
in the [`documentation.yml`][documentation.yml] workflow file. All documentation is in the `docs/`
directory at the top level.
Continuwuity's website uses [`mdbook`][mdbook] and deployed via CI using GitHub
Pages in the [`documentation.yml`][documentation.yml] workflow file with Nix's
mdbook in the devshell. All documentation is in the `docs/` directory at the top
level. The compiled mdbook website is also uploaded as an artifact.
To build the documentation locally:
To build the documentation using Nix, run: `bin/nix-build-and-cache just .#book`
1. Install mdbook if you don't have it already:
```bash
cargo install mdbook # or cargo binstall, or another method
```
2. Build the documentation:
```bash
mdbook build
```
The output of the mdbook generation is in `public/`. You can open the HTML files directly in your browser without needing a web server.
The output of the mdbook generation is in `result/`. mdbooks can be opened in
your browser from the individual HTML files without any web server needed.
### Inclusivity and Diversity
@ -133,40 +109,6 @@ Rust's default style and standards with regards to [function names, variable
names, comments](https://rust-lang.github.io/api-guidelines/naming.html), etc
applies here.
### Commit Messages
Continuwuity follows the [Conventional Commits](https://www.conventionalcommits.org/) specification for commit messages. This provides a standardized format that makes the commit history more readable and enables automated tools to generate changelogs.
The basic structure is:
```
<type>[(optional scope)]: <description>
[optional body]
[optional footer(s)]
```
The allowed types for commits are:
- `fix`: Bug fixes
- `feat`: New features
- `docs`: Documentation changes
- `style`: Changes that don't affect the meaning of the code (formatting, etc.)
- `refactor`: Code changes that neither fix bugs nor add features
- `perf`: Performance improvements
- `test`: Adding or fixing tests
- `build`: Changes to the build system or dependencies
- `ci`: Changes to CI configuration
- `chore`: Other changes that don't modify source or test files
Examples:
```
feat: add user authentication
fix(database): resolve connection pooling issue
docs: update installation instructions
```
The project uses the `committed` hook to validate commit messages in pre-commit. This ensures all commits follow the conventional format.
### Creating pull requests
Please try to keep contributions to the Forgejo Instance. While the mirrors of continuwuity
@ -176,13 +118,6 @@ This prevents us from having to ping once in a while to double check the status
of it, especially when the CI completed successfully and everything so it
*looks* done.
Before submitting a pull request, please ensure:
1. Your code passes all CI checks (formatting, linting, typo detection, etc.)
2. Your commit messages follow the conventional commits format
3. Tests are added for new functionality
4. Documentation is updated if needed
Direct all PRs/MRs to the `main` branch.
@ -190,13 +125,20 @@ By sending a pull request or patch, you are agreeing that your changes are
allowed to be licenced under the Apache-2.0 licence and all of your conduct is
in line with the Contributor's Covenant, and continuwuity's Code of Conduct.
Contribution by users who violate either of these code of conducts may not have
Contribution by users who violate either of these code of conducts will not have
their contributions accepted. This includes users who have been banned from
continuwuity Matrix rooms for Code of Conduct violations.
continuwuityMatrix rooms for Code of Conduct violations.
[issues]: https://forgejo.ellis.link/continuwuation/continuwuity/issues
[continuwuity-matrix]: https://matrix.to/#/#continuwuity:continuwuity.org
[complement]: https://github.com/matrix-org/complement/
[engage.toml]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/engage.toml
[engage]: https://charles.page.computer.surgery/engage/
[sytest]: https://github.com/matrix-org/sytest/
[cargo-deb]: https://github.com/kornelski/cargo-deb
[lychee]: https://github.com/lycheeverse/lychee
[markdownlint-cli]: https://github.com/igorshubovych/markdownlint-cli
[cargo-audit]: https://github.com/RustSec/rustsec/tree/main/cargo-audit
[direnv]: https://direnv.net/
[mdbook]: https://rust-lang.github.io/mdBook/
[documentation.yml]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/.forgejo/workflows/documentation.yml

22
Cargo.lock generated
View file

@ -3798,7 +3798,7 @@ dependencies = [
[[package]]
name = "ruma"
version = "0.10.1"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983"
dependencies = [
"assign",
"js_int",
@ -3818,7 +3818,7 @@ dependencies = [
[[package]]
name = "ruma-appservice-api"
version = "0.10.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983"
dependencies = [
"js_int",
"ruma-common",
@ -3830,7 +3830,7 @@ dependencies = [
[[package]]
name = "ruma-client-api"
version = "0.18.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983"
dependencies = [
"as_variant",
"assign",
@ -3853,7 +3853,7 @@ dependencies = [
[[package]]
name = "ruma-common"
version = "0.13.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983"
dependencies = [
"as_variant",
"base64 0.22.1",
@ -3885,7 +3885,7 @@ dependencies = [
[[package]]
name = "ruma-events"
version = "0.28.1"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983"
dependencies = [
"as_variant",
"indexmap 2.9.0",
@ -3910,7 +3910,7 @@ dependencies = [
[[package]]
name = "ruma-federation-api"
version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983"
dependencies = [
"bytes",
"headers",
@ -3932,7 +3932,7 @@ dependencies = [
[[package]]
name = "ruma-identifiers-validation"
version = "0.9.5"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983"
dependencies = [
"js_int",
"thiserror 2.0.12",
@ -3941,7 +3941,7 @@ dependencies = [
[[package]]
name = "ruma-identity-service-api"
version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983"
dependencies = [
"js_int",
"ruma-common",
@ -3951,7 +3951,7 @@ dependencies = [
[[package]]
name = "ruma-macros"
version = "0.13.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983"
dependencies = [
"cfg-if",
"proc-macro-crate",
@ -3966,7 +3966,7 @@ dependencies = [
[[package]]
name = "ruma-push-gateway-api"
version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983"
dependencies = [
"js_int",
"ruma-common",
@ -3978,7 +3978,7 @@ dependencies = [
[[package]]
name = "ruma-signatures"
version = "0.15.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=d6870a7fb7f6cccff63f7fd0ff6c581bad80e983#d6870a7fb7f6cccff63f7fd0ff6c581bad80e983"
dependencies = [
"base64 0.22.1",
"ed25519-dalek",

View file

@ -350,7 +350,7 @@ version = "0.1.2"
[workspace.dependencies.ruma]
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
#branch = "conduwuit-changes"
rev = "a4b948b40417a65ab0282ae47cc50035dd455e02"
rev = "d6870a7fb7f6cccff63f7fd0ff6c581bad80e983"
features = [
"compat",
"rand",

View file

@ -4,10 +4,6 @@
## A community-driven [Matrix](https://matrix.org/) homeserver in Rust
[![Chat on Matrix](https://img.shields.io/matrix/continuwuity%3Acontinuwuity.org?server_fqdn=matrix.continuwuity.org&fetchMode=summary&logo=matrix)](https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org) [![Join the space](https://img.shields.io/matrix/space%3Acontinuwuity.org?server_fqdn=matrix.continuwuity.org&fetchMode=summary&logo=matrix&label=space)](https://matrix.to/#/#space:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org)
<!-- ANCHOR_END: catchphrase -->
[continuwuity] is a Matrix homeserver written in Rust.
@ -15,13 +11,11 @@ It's a community continuation of the [conduwuit](https://github.com/girlbossceo/
<!-- ANCHOR: body -->
[![forgejo.ellis.link](https://img.shields.io/badge/Ellis%20Git-main+packages-green?style=flat&logo=forgejo&labelColor=fff)](https://forgejo.ellis.link/continuwuation/continuwuity) [![Stars](https://forgejo.ellis.link/continuwuation/continuwuity/badges/stars.svg?style=flat)](https://forgejo.ellis.link/continuwuation/continuwuity/stars) [![Issues](https://forgejo.ellis.link/continuwuation/continuwuity/badges/issues/open.svg?style=flat)](https://forgejo.ellis.link/continuwuation/continuwuity/issues?state=open) [![Pull Requests](https://forgejo.ellis.link/continuwuation/continuwuity/badges/pulls/open.svg?style=flat)](https://forgejo.ellis.link/continuwuation/continuwuity/pulls?state=open)
[![forgejo.ellis.link](https://img.shields.io/badge/Ellis%20Git-main+packages-green?style=flat&logo=forgejo&labelColor=fff)](https://forgejo.ellis.link/continuwuation/continuwuity) ![](https://forgejo.ellis.link/continuwuation/continuwuity/badges/stars.svg?style=flat) [![](https://forgejo.ellis.link/continuwuation/continuwuity/badges/issues/open.svg?style=flat)](https://forgejo.ellis.link/continuwuation/continuwuity/issues?state=open) [![](https://forgejo.ellis.link/continuwuation/continuwuity/badges/pulls/open.svg?style=flat)](https://forgejo.ellis.link/continuwuation/continuwuity/pulls?state=open)
[![GitHub](https://img.shields.io/badge/GitHub-mirror-blue?style=flat&logo=github&labelColor=fff&logoColor=24292f)](https://github.com/continuwuity/continuwuity) [![Stars](https://img.shields.io/github/stars/continuwuity/continuwuity?style=flat)](https://github.com/continuwuity/continuwuity/stargazers)
[![GitHub](https://img.shields.io/badge/GitHub-mirror-blue?style=flat&logo=github&labelColor=fff&logoColor=24292f)](https://github.com/continuwuity/continuwuity) ![](https://img.shields.io/github/stars/continuwuity/continuwuity?style=flat)
[![GitLab](https://img.shields.io/badge/GitLab-mirror-blue?style=flat&logo=gitlab&labelColor=fff)](https://gitlab.com/continuwuity/continuwuity) [![Stars](https://img.shields.io/gitlab/stars/continuwuity/continuwuity?style=flat)](https://gitlab.com/continuwuity/continuwuity/-/starrers)
[![Codeberg](https://img.shields.io/badge/Codeberg-mirror-2185D0?style=flat&logo=codeberg&labelColor=fff)](https://codeberg.org/continuwuity/continuwuity) [![Stars](https://codeberg.org/continuwuity/continuwuity/badges/stars.svg?style=flat)](https://codeberg.org/continuwuity/continuwuity/stars)
[![Codeberg](https://img.shields.io/badge/Codeberg-mirror-2185D0?style=flat&logo=codeberg&labelColor=fff)](https://codeberg.org/nexy7574/continuwuity) ![](https://codeberg.org/nexy7574/continuwuity/badges/stars.svg?style=flat)
### Why does this exist?
@ -65,6 +59,8 @@ There are currently no open registration Continuwuity instances available.
We're working our way through all of the issues in the [Forgejo project](https://forgejo.ellis.link/continuwuation/continuwuity/issues).
- [Replacing old conduwuit links with working continuwuity links](https://forgejo.ellis.link/continuwuation/continuwuity/issues/742)
- [Getting CI and docs deployment working on the new Forgejo project](https://forgejo.ellis.link/continuwuation/continuwuity/issues/740)
- [Packaging & availability in more places](https://forgejo.ellis.link/continuwuation/continuwuity/issues/747)
- [Appservices bugs & features](https://forgejo.ellis.link/continuwuation/continuwuity/issues?q=&type=all&state=open&labels=178&milestone=0&assignee=0&poster=0)
- [Improving compatibility and spec compliance](https://forgejo.ellis.link/continuwuation/continuwuity/issues?labels=119)

View file

@ -6,7 +6,6 @@ After=network-online.target
Documentation=https://continuwuity.org/
RequiresMountsFor=/var/lib/private/conduwuit
Alias=matrix-conduwuit.service
[Service]
DynamicUser=yes
Type=notify-reload
@ -60,8 +59,7 @@ StateDirectory=conduwuit
RuntimeDirectory=conduwuit
RuntimeDirectoryMode=0750
Environment=CONTINUWUITY_CONFIG=${CREDENTIALS_DIRECTORY}/config.toml
LoadCredential=config.toml:/etc/conduwuit/conduwuit.toml
Environment="CONTINUWUITY_CONFIG=/etc/conduwuit/conduwuit.toml"
BindPaths=/var/lib/private/conduwuit:/var/lib/matrix-conduit
BindPaths=/var/lib/private/conduwuit:/var/lib/private/matrix-conduit

View file

@ -1,3 +0,0 @@
style = "conventional"
subject_length = 72
allowed_types = ["ci", "build", "fix", "feat", "chore", "docs", "style", "refactor", "perf", "test"]

View file

@ -398,17 +398,6 @@
#
#allow_registration = false
# If registration is enabled, and this setting is true, new users
# registered after the first admin user will be automatically suspended
# and will require an admin to run `!admin users unsuspend <user_id>`.
#
# Suspended users are still able to read messages, make profile updates,
# leave rooms, and deactivate their account, however cannot send messages,
# invites, or create/join or otherwise modify rooms.
# They are effectively read-only.
#
#suspend_on_register = false
# Enabling this setting opens registration to anyone without restrictions.
# This makes your server vulnerable to abuse
#

View file

@ -68,22 +68,31 @@ do this if Rust supported workspace-level features to begin with.
## List of forked dependencies
During Continuwuity (and prior projects) development, we have had to fork some dependencies to support our use-cases.
These forks exist for various reasons including features that upstream projects won't accept,
faster-paced development, Continuwuity-specific usecases, or lack of time to upstream changes.
During Continuwuity development, we have had to fork
some dependencies to support our use-cases in some areas. This ranges from
things said upstream project won't accept for any reason, faster-paced
development (unresponsive or slow upstream), Continuwuity-specific usecases, or
lack of time to upstream some things.
All forked dependencies are maintained under the [continuwuation organization on Forgejo](https://forgejo.ellis.link/continuwuation):
- [ruwuma][continuwuation-ruwuma] - Fork of [ruma/ruma][ruma] with various performance improvements, more features and better client/server interop
- [rocksdb][continuwuation-rocksdb] - Fork of [facebook/rocksdb][rocksdb] via [`@zaidoon1`][8] with liburing build fixes and GCC debug build fixes
- [jemallocator][continuwuation-jemallocator] - Fork of [tikv/jemallocator][jemallocator] fixing musl builds, suspicious code,
and adding support for redzones in Valgrind
- [rustyline-async][continuwuation-rustyline-async] - Fork of [zyansheep/rustyline-async][rustyline-async] with tab completion callback
and `CTRL+\` signal quit event for Continuwuity console CLI
- [rust-rocksdb][continuwuation-rust-rocksdb] - Fork of [rust-rocksdb/rust-rocksdb][rust-rocksdb] fixing musl build issues,
removing unnecessary `gtest` include, and using our RocksDB and jemallocator forks
- [tracing][continuwuation-tracing] - Fork of [tokio-rs/tracing][tracing] implementing `Clone` for `EnvFilter` to
support dynamically changing tracing environments
- [ruma/ruma][1]: <https://github.com/girlbossceo/ruwuma> - various performance
improvements, more features, faster-paced development, better client/server interop
hacks upstream won't accept, etc
- [facebook/rocksdb][2]: <https://github.com/girlbossceo/rocksdb> - liburing
build fixes and GCC debug build fix
- [tikv/jemallocator][3]: <https://github.com/girlbossceo/jemallocator> - musl
builds seem to be broken on upstream, fixes some broken/suspicious code in
places, additional safety measures, and support redzones for Valgrind
- [zyansheep/rustyline-async][4]:
<https://github.com/girlbossceo/rustyline-async> - tab completion callback and
`CTRL+\` signal quit event for Continuwuity console CLI
- [rust-rocksdb/rust-rocksdb][5]:
<https://github.com/girlbossceo/rust-rocksdb-zaidoon1> - [`@zaidoon1`][8]'s fork
has quicker updates, more up to date dependencies, etc. Our fork fixes musl build
issues, removes unnecessary `gtest` include, and uses our RocksDB and jemallocator
forks.
- [tokio-rs/tracing][6]: <https://github.com/girlbossceo/tracing> - Implements
`Clone` for `EnvFilter` to support dynamically changing tracing envfilter's
alongside other logging/metrics things
## Debugging with `tokio-console`
@ -104,30 +113,12 @@ You will also need to enable the `tokio_console` config option in Continuwuity w
starting it. This was due to tokio-console causing gradual memory leak/usage
if left enabled.
## Building Docker Images
To build a Docker image for Continuwuity, use the standard Docker build command:
```bash
docker build -f docker/Dockerfile .
```
The image can be cross-compiled for different architectures.
[continuwuation-ruwuma]: https://forgejo.ellis.link/continuwuation/ruwuma
[continuwuation-rocksdb]: https://forgejo.ellis.link/continuwuation/rocksdb
[continuwuation-jemallocator]: https://forgejo.ellis.link/continuwuation/jemallocator
[continuwuation-rustyline-async]: https://forgejo.ellis.link/continuwuation/rustyline-async
[continuwuation-rust-rocksdb]: https://forgejo.ellis.link/continuwuation/rust-rocksdb
[continuwuation-tracing]: https://forgejo.ellis.link/continuwuation/tracing
[ruma]: https://github.com/ruma/ruma/
[rocksdb]: https://github.com/facebook/rocksdb/
[jemallocator]: https://github.com/tikv/jemallocator/
[rustyline-async]: https://github.com/zyansheep/rustyline-async/
[rust-rocksdb]: https://github.com/rust-rocksdb/rust-rocksdb/
[tracing]: https://github.com/tokio-rs/tracing/
[1]: https://github.com/ruma/ruma/
[2]: https://github.com/facebook/rocksdb/
[3]: https://github.com/tikv/jemallocator/
[4]: https://github.com/zyansheep/rustyline-async/
[5]: https://github.com/rust-rocksdb/rust-rocksdb/
[6]: https://github.com/tokio-rs/tracing/
[7]: https://docs.rs/tokio-console/latest/tokio_console/
[8]: https://github.com/zaidoon1/
[9]: https://github.com/rust-lang/cargo/issues/12162

View file

@ -9,7 +9,7 @@ use crate::{
};
#[derive(Debug, Parser)]
#[command(name = conduwuit_core::name(), version = conduwuit_core::version())]
#[command(name = "conduwuit", version = conduwuit::version())]
pub(super) enum AdminCommand {
#[command(subcommand)]
/// - Commands for managing appservices

View file

@ -7,14 +7,13 @@ use futures::{
io::{AsyncWriteExt, BufWriter},
lock::Mutex,
};
use ruma::{EventId, UserId};
use ruma::EventId;
pub(crate) struct Context<'a> {
pub(crate) services: &'a Services,
pub(crate) body: &'a [&'a str],
pub(crate) timer: SystemTime,
pub(crate) reply_id: Option<&'a EventId>,
pub(crate) sender: Option<&'a UserId>,
pub(crate) output: Mutex<BufWriter<Vec<u8>>>,
}
@ -37,10 +36,4 @@ impl Context<'_> {
output.write_all(s.as_bytes()).map_err(Into::into).await
})
}
/// Get the sender as a string, or service user ID if not available
pub(crate) fn sender_or_service_user(&self) -> &UserId {
self.sender
.unwrap_or_else(|| self.services.globals.server_user.as_ref())
}
}

View file

@ -63,7 +63,6 @@ async fn process_command(services: Arc<Services>, input: &CommandInput) -> Proce
body: &body,
timer: SystemTime::now(),
reply_id: input.reply_id.as_deref(),
sender: input.sender.as_deref(),
output: BufWriter::new(Vec::new()).into(),
};

View file

@ -1,7 +1,7 @@
use api::client::leave_room;
use clap::Subcommand;
use conduwuit::{
Err, Result, debug, info,
Err, Result, debug,
utils::{IterStream, ReadyExt},
warn,
};
@ -70,6 +70,7 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
};
debug!("Room specified is a room ID, banning room ID");
self.services.rooms.metadata.ban_room(room_id, true);
room_id.to_owned()
} else if room.is_room_alias_id() {
@ -89,25 +90,47 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
locally, if not using get_alias_helper to fetch room ID remotely"
);
match self
let room_id = match self
.services
.rooms
.alias
.resolve_alias(room_alias, None)
.resolve_local_alias(room_alias)
.await
{
| Ok((room_id, servers)) => {
| Ok(room_id) => room_id,
| _ => {
debug!(
?room_id,
?servers,
"Got federation response fetching room ID for room {room}"
"We don't have this room alias to a room ID locally, attempting to fetch \
room ID over federation"
);
room_id
match self
.services
.rooms
.alias
.resolve_alias(room_alias, None)
.await
{
| Ok((room_id, servers)) => {
debug!(
?room_id,
?servers,
"Got federation response fetching room ID for {room_id}"
);
room_id
},
| Err(e) => {
return Err!(
"Failed to resolve room alias {room_alias} to a room ID: {e}"
);
},
}
},
| Err(e) => {
return Err!("Failed to resolve room alias {room} to a room ID: {e}");
},
}
};
self.services.rooms.metadata.ban_room(&room_id, true);
room_id
} else {
return Err!(
"Room specified is not a room ID or room alias. Please note that this requires a \
@ -116,7 +139,7 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
);
};
info!("Making all users leave the room {room_id} and forgetting it");
debug!("Making all users leave the room {room_id} and forgetting it");
let mut users = self
.services
.rooms
@ -127,7 +150,7 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
.boxed();
while let Some(ref user_id) = users.next().await {
info!(
debug!(
"Attempting leave for user {user_id} in room {room_id} (ignoring all errors, \
evicting admins too)",
);
@ -157,9 +180,10 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
})
.await;
self.services.rooms.directory.set_not_public(&room_id); // remove from the room directory
self.services.rooms.metadata.ban_room(&room_id, true); // prevent further joins
self.services.rooms.metadata.disable_room(&room_id, true); // disable federation
// unpublish from room directory
self.services.rooms.directory.set_not_public(&room_id);
self.services.rooms.metadata.disable_room(&room_id, true);
self.write_str(
"Room banned, removed all our local users, and disabled incoming federation with room.",
@ -281,6 +305,8 @@ async fn ban_list_of_rooms(&self) -> Result {
}
for room_id in room_ids {
self.services.rooms.metadata.ban_room(&room_id, true);
debug!("Banned {room_id} successfully");
room_ban_count = room_ban_count.saturating_add(1);
@ -326,9 +352,9 @@ async fn ban_list_of_rooms(&self) -> Result {
})
.await;
self.services.rooms.metadata.ban_room(&room_id, true);
// unpublish from room directory, ignore errors
self.services.rooms.directory.set_not_public(&room_id);
self.services.rooms.metadata.disable_room(&room_id, true);
}

View file

@ -226,47 +226,6 @@ pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) ->
.await
}
#[admin_command]
pub(super) async fn suspend(&self, user_id: String) -> Result {
let user_id = parse_local_user_id(self.services, &user_id)?;
if user_id == self.services.globals.server_user {
return Err!("Not allowed to suspend 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 suspended.");
}
// TODO: Record the actual user that sent the suspension where possible
self.services
.users
.suspend_account(&user_id, self.sender_or_service_user())
.await;
self.write_str(&format!("User {user_id} has been suspended."))
.await
}
#[admin_command]
pub(super) async fn unsuspend(&self, user_id: String) -> Result {
let user_id = parse_local_user_id(self.services, &user_id)?;
if user_id == self.services.globals.server_user {
return Err!("Not allowed to unsuspend the server service account.",);
}
if !self.services.users.exists(&user_id).await {
return Err!("User {user_id} does not exist.");
}
self.services.users.unsuspend_account(&user_id).await;
self.write_str(&format!("User {user_id} has been unsuspended."))
.await
}
#[admin_command]
pub(super) async fn reset_password(&self, username: String, password: Option<String>) -> Result {
let user_id = parse_local_user_id(self.services, &username)?;

View file

@ -59,28 +59,6 @@ pub(super) enum UserCommand {
force: bool,
},
/// - Suspend a user
///
/// Suspended users are able to log in, sync, and read messages, but are not
/// able to send events nor redact them, cannot change their profile, and
/// are unable to join, invite to, or knock on rooms.
///
/// Suspended users can still leave rooms and deactivate their account.
/// Suspending them effectively makes them read-only.
Suspend {
/// Username of the user to suspend
user_id: String,
},
/// - Unsuspend a user
///
/// Reverses the effects of the `suspend` command, allowing the user to send
/// messages, change their profile, create room invites, etc.
Unsuspend {
/// Username of the user to unsuspend
user_id: String,
},
/// - List local users in the database
#[clap(alias = "list")]
ListUsers,

View file

@ -25,10 +25,7 @@ use ruma::{
},
events::{
GlobalAccountDataEventType, StateEventType,
room::{
message::RoomMessageEventContent,
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
},
room::power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
},
push,
};
@ -490,25 +487,6 @@ pub(crate) async fn register_route(
{
services.admin.make_user_admin(&user_id).await?;
warn!("Granting {user_id} admin privileges as the first user");
} else if services.config.suspend_on_register {
// This is not an admin, suspend them.
// Note that we can still do auto joins for suspended users
services
.users
.suspend_account(&user_id, &services.globals.server_user)
.await;
// And send an @room notice to the admin room, to prompt admins to review the
// new user and ideally unsuspend them if deemed appropriate.
if services.server.config.admin_room_notices {
services
.admin
.send_loud_message(RoomMessageEventContent::text_plain(format!(
"User {user_id} has been suspended as they are not the first user \
on this server. Please review and unsuspend them if appropriate."
)))
.await
.ok();
}
}
}
}

View file

@ -18,10 +18,6 @@ pub(crate) async fn create_alias_route(
body: Ruma<create_alias::v3::Request>,
) -> Result<create_alias::v3::Response> {
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
services
.rooms
.alias
@ -66,10 +62,6 @@ pub(crate) async fn delete_alias_route(
body: Ruma<delete_alias::v3::Request>,
) -> Result<delete_alias::v3::Response> {
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
services
.rooms
.alias

View file

@ -128,9 +128,6 @@ pub(crate) async fn set_room_visibility_route(
// Return 404 if the room doesn't exist
return Err!(Request(NotFound("Room not found")));
}
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
if services
.users

View file

@ -52,9 +52,6 @@ pub(crate) async fn create_content_route(
body: Ruma<create_content::v3::Request>,
) -> Result<create_content::v3::Response> {
let user = body.sender_user();
if services.users.is_suspended(user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let filename = body.filename.as_deref();
let content_type = body.content_type.as_deref();

View file

@ -20,10 +20,6 @@ pub(crate) async fn ban_user_route(
return Err!(Request(Forbidden("You cannot ban yourself.")));
}
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let current_member_content = services
@ -45,7 +41,6 @@ pub(crate) async fn ban_user_route(
is_direct: None,
join_authorized_via_users_server: None,
third_party_invite: None,
redact_events: body.redact_events,
..current_member_content
}),
sender_user,

View file

@ -25,9 +25,6 @@ pub(crate) async fn invite_user_route(
body: Ruma<invite_user::v3::Request>,
) -> Result<invite_user::v3::Response> {
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
if !services.users.is_admin(sender_user).await && services.config.block_non_admin_invites {
debug_error!(

View file

@ -65,9 +65,6 @@ pub(crate) async fn join_room_by_id_route(
body: Ruma<join_room_by_id::v3::Request>,
) -> Result<join_room_by_id::v3::Response> {
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
banned_room_check(
&services,
@ -139,9 +136,6 @@ pub(crate) async fn join_room_by_id_or_alias_route(
let sender_user = body.sender_user();
let appservice_info = &body.appservice_info;
let body = &body.body;
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let (servers, room_id) = match OwnedRoomId::try_from(body.room_id_or_alias.clone()) {
| Ok(room_id) => {
@ -284,32 +278,24 @@ pub async fn join_room_by_id_helper(
return Ok(join_room_by_id::v3::Response { room_id: room_id.into() });
}
if let Ok(membership) = services
.rooms
.state_accessor
.get_member(room_id, sender_user)
.await
{
if membership.membership == MembershipState::Ban {
debug_warn!("{sender_user} is banned from {room_id} but attempted to join");
return Err!(Request(Forbidden("You are banned from the room.")));
}
}
let server_in_room = services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), room_id)
.await;
// Only check our known membership if we're already in the room.
// See: https://forgejo.ellis.link/continuwuation/continuwuity/issues/855
let membership = if server_in_room {
services
.rooms
.state_accessor
.get_member(room_id, sender_user)
.await
} else {
debug!("Ignoring local state for join {room_id}, we aren't in the room yet.");
Ok(RoomMemberEventContent::new(MembershipState::Leave))
};
if let Ok(m) = membership {
if m.membership == MembershipState::Ban {
debug_warn!("{sender_user} is banned from {room_id} but attempted to join");
// TODO: return reason
return Err!(Request(Forbidden("You are banned from the room.")));
}
}
let local_join = server_in_room
|| servers.is_empty()
|| (servers.len() == 1 && services.globals.server_is_ours(&servers[0]));

View file

@ -14,10 +14,6 @@ pub(crate) async fn kick_user_route(
State(services): State<crate::State>,
body: Ruma<kick_user::v3::Request>,
) -> Result<kick_user::v3::Response> {
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let Ok(event) = services
@ -53,7 +49,7 @@ pub(crate) async fn kick_user_route(
third_party_invite: None,
..event
}),
sender_user,
body.sender_user(),
&body.room_id,
&state_lock,
)

View file

@ -52,9 +52,6 @@ pub(crate) async fn knock_room_route(
) -> Result<knock_room::v3::Response> {
let sender_user = body.sender_user();
let body = &body.body;
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let (servers, room_id) = match OwnedRoomId::try_from(body.room_id_or_alias.clone()) {
| Ok(room_id) => {

View file

@ -90,7 +90,6 @@ pub async fn leave_room(
displayname: None,
third_party_invite: None,
blurhash: None,
redact_events: None,
};
let is_banned = services.rooms.metadata.is_banned(room_id);

View file

@ -14,10 +14,6 @@ pub(crate) async fn unban_user_route(
State(services): State<crate::State>,
body: Ruma<unban_user::v3::Request>,
) -> Result<unban_user::v3::Response> {
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let current_member_content = services
@ -46,7 +42,7 @@ pub(crate) async fn unban_user_route(
is_direct: None,
..current_member_content
}),
sender_user,
body.sender_user(),
&body.room_id,
&state_lock,
)

View file

@ -36,9 +36,6 @@ pub(crate) async fn set_displayname_route(
body: Ruma<set_display_name::v3::Request>,
) -> Result<set_display_name::v3::Response> {
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
if *sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot update the profile of another user")));
@ -128,9 +125,6 @@ pub(crate) async fn set_avatar_url_route(
body: Ruma<set_avatar_url::v3::Request>,
) -> Result<set_avatar_url::v3::Response> {
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
if *sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot update the profile of another user")));
@ -351,7 +345,6 @@ pub async fn update_displayname(
reason: None,
is_direct: None,
third_party_invite: None,
redact_events: None,
});
Ok((pdu, room_id))
@ -401,7 +394,6 @@ pub async fn update_avatar_url(
reason: None,
is_direct: None,
third_party_invite: None,
redact_events: None,
});
Ok((pdu, room_id))

View file

@ -58,34 +58,29 @@ pub(crate) async fn set_read_marker_route(
}
if let Some(event) = &body.read_receipt {
if !services.users.is_suspended(sender_user).await? {
let receipt_content = BTreeMap::from_iter([(
event.to_owned(),
BTreeMap::from_iter([(
ReceiptType::Read,
BTreeMap::from_iter([(
sender_user.to_owned(),
ruma::events::receipt::Receipt {
ts: Some(MilliSecondsSinceUnixEpoch::now()),
thread: ReceiptThread::Unthreaded,
},
)]),
)]),
)]);
let receipt_content = BTreeMap::from_iter([(
event.to_owned(),
BTreeMap::from_iter([(
ReceiptType::Read,
BTreeMap::from_iter([(sender_user.to_owned(), ruma::events::receipt::Receipt {
ts: Some(MilliSecondsSinceUnixEpoch::now()),
thread: ReceiptThread::Unthreaded,
})]),
)]),
)]);
services
.rooms
.read_receipt
.readreceipt_update(
sender_user,
&body.room_id,
&ruma::events::receipt::ReceiptEvent {
content: ruma::events::receipt::ReceiptEventContent(receipt_content),
room_id: body.room_id.clone(),
},
)
.await;
}
services
.rooms
.read_receipt
.readreceipt_update(
sender_user,
&body.room_id,
&ruma::events::receipt::ReceiptEvent {
content: ruma::events::receipt::ReceiptEventContent(receipt_content),
room_id: body.room_id.clone(),
},
)
.await;
}
if let Some(event) = &body.private_read_receipt {

View file

@ -1,5 +1,5 @@
use axum::extract::State;
use conduwuit::{Err, Result, matrix::pdu::PduBuilder};
use conduwuit::{Result, matrix::pdu::PduBuilder};
use ruma::{
api::client::redact::redact_event, events::room::redaction::RoomRedactionEventContent,
};
@ -17,10 +17,6 @@ pub(crate) async fn redact_event_route(
) -> Result<redact_event::v3::Response> {
let sender_user = body.sender_user();
let body = &body.body;
if services.users.is_suspended(sender_user).await? {
// TODO: Users can redact their own messages while suspended
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;

View file

@ -1,4 +1,4 @@
use std::{fmt::Write as _, ops::Mul, time::Duration};
use std::time::Duration;
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
@ -6,28 +6,15 @@ use conduwuit::{Err, Result, debug_info, info, matrix::pdu::PduEvent, utils::Rea
use conduwuit_service::Services;
use rand::Rng;
use ruma::{
EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId,
api::client::{
report_user,
room::{report_content, report_room},
},
events::{Mentions, room::message::RoomMessageEventContent},
EventId, RoomId, UserId,
api::client::room::{report_content, report_room},
events::room::message,
int,
};
use tokio::time::sleep;
use crate::Ruma;
struct Report {
sender: OwnedUserId,
room_id: Option<OwnedRoomId>,
event_id: Option<OwnedEventId>,
user_id: Option<OwnedUserId>,
report_type: String,
reason: Option<String>,
score: Option<ruma::Int>,
}
/// # `POST /_matrix/client/v3/rooms/{roomId}/report`
///
/// Reports an abusive room to homeserver admins
@ -37,10 +24,14 @@ pub(crate) async fn report_room_route(
InsecureClientIp(client): InsecureClientIp,
body: Ruma<report_room::v3::Request>,
) -> Result<report_room::v3::Response> {
// user authentication
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
info!(
"Received room report by user {sender_user} for room {} with reason: \"{}\"",
body.room_id,
body.reason.as_deref().unwrap_or("")
);
if body.reason.as_ref().is_some_and(|s| s.len() > 750) {
return Err!(Request(
@ -60,23 +51,19 @@ pub(crate) async fn report_room_route(
"Room does not exist to us, no local users have joined at all"
)));
}
info!(
"Received room report by user {sender_user} for room {} with reason: \"{}\"",
body.room_id,
body.reason.as_deref().unwrap_or("")
);
let report = Report {
sender: sender_user.to_owned(),
room_id: Some(body.room_id.clone()),
event_id: None,
user_id: None,
report_type: "room".to_owned(),
reason: body.reason.clone(),
score: None,
};
services.admin.send_message(build_report(report)).await.ok();
// send admin room message that we received the report with an @room ping for
// urgency
services
.admin
.send_message(message::RoomMessageEventContent::text_markdown(format!(
"@room Room report received from {} -\n\nRoom ID: {}\n\nReport Reason: {}",
sender_user.to_owned(),
body.room_id,
body.reason.as_deref().unwrap_or("")
)))
.await
.ok();
Ok(report_room::v3::Response {})
}
@ -92,9 +79,14 @@ pub(crate) async fn report_event_route(
) -> Result<report_content::v3::Response> {
// user authentication
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
info!(
"Received event report by user {sender_user} for room {} and event ID {}, with reason: \
\"{}\"",
body.room_id,
body.event_id,
body.reason.as_deref().unwrap_or("")
);
delay_response().await;
@ -113,73 +105,27 @@ pub(crate) async fn report_event_route(
&pdu,
)
.await?;
info!(
"Received event report by user {sender_user} for room {} and event ID {}, with reason: \
\"{}\"",
body.room_id,
body.event_id,
body.reason.as_deref().unwrap_or("")
);
let report = Report {
sender: sender_user.to_owned(),
room_id: Some(body.room_id.clone()),
event_id: Some(body.event_id.clone()),
user_id: None,
report_type: "event".to_owned(),
reason: body.reason.clone(),
score: body.score,
};
services.admin.send_message(build_report(report)).await.ok();
// send admin room message that we received the report with an @room ping for
// urgency
services
.admin
.send_message(message::RoomMessageEventContent::text_markdown(format!(
"@room Event report received from {} -\n\nEvent ID: {}\nRoom ID: {}\nSent By: \
{}\n\nReport Score: {}\nReport Reason: {}",
sender_user.to_owned(),
pdu.event_id,
pdu.room_id,
pdu.sender,
body.score.unwrap_or_else(|| ruma::Int::from(0)),
body.reason.as_deref().unwrap_or("")
)))
.await
.ok();
Ok(report_content::v3::Response {})
}
#[tracing::instrument(skip_all, fields(%client), name = "report_user")]
pub(crate) async fn report_user_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<report_user::v3::Request>,
) -> Result<report_user::v3::Response> {
// user authentication
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
if body.reason.as_ref().is_some_and(|s| s.len() > 750) {
return Err!(Request(
InvalidParam("Reason too long, should be 750 characters or fewer",)
));
}
delay_response().await;
if !services.users.is_active_local(&body.user_id).await {
// return 200 as to not reveal if the user exists. Recommended by spec.
return Ok(report_user::v3::Response {});
}
let report = Report {
sender: sender_user.to_owned(),
room_id: None,
event_id: None,
user_id: Some(body.user_id.clone()),
report_type: "user".to_owned(),
reason: body.reason.clone(),
score: None,
};
info!(
"Received room report from {sender_user} for user {} with reason: \"{}\"",
body.user_id,
body.reason.as_deref().unwrap_or("")
);
services.admin.send_message(build_report(report)).await.ok();
Ok(report_user::v3::Response {})
}
/// in the following order:
///
/// check if the room ID from the URI matches the PDU's room ID
@ -227,29 +173,6 @@ async fn is_event_report_valid(
Ok(())
}
/// Builds a report message to be sent to the admin room.
fn build_report(report: Report) -> RoomMessageEventContent {
let mut text =
format!("@room New {} report received from {}:\n\n", report.report_type, report.sender);
if report.user_id.is_some() {
let _ = writeln!(text, "- Reported User ID: `{}`", report.user_id.unwrap());
}
if report.room_id.is_some() {
let _ = writeln!(text, "- Reported Room ID: `{}`", report.room_id.unwrap());
}
if report.event_id.is_some() {
let _ = writeln!(text, "- Reported Event ID: `{}`", report.event_id.unwrap());
}
if let Some(score) = report.score {
let _ = writeln!(text, "- User-supplied offensiveness score: {}%", score.mul(int!(-1)));
}
if let Some(reason) = report.reason {
let _ = writeln!(text, "- Report Reason: {reason}");
}
RoomMessageEventContent::text_markdown(text).add_mentions(Mentions::with_room_mention())
}
/// even though this is kinda security by obscurity, let's still make a small
/// random delay sending a response per spec suggestion regarding
/// enumerating for potential events existing in our server.

View file

@ -64,10 +64,6 @@ pub(crate) async fn create_room_route(
return Err!(Request(Forbidden("Room creation has been disabled.",)));
}
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let room_id: OwnedRoomId = match &body.room_id {
| Some(custom_room_id) => custom_room_id_check(&services, custom_room_id)?,
| _ => RoomId::new(&services.server.name),

View file

@ -43,9 +43,10 @@ pub(crate) async fn get_room_summary_legacy(
}
/// # `GET /_matrix/client/unstable/im.nheko.summary/summary/{roomIdOrAlias}`
/// # `GET /_matrix/client/v1/room_summary/{roomIdOrAlias}`
///
/// Returns a short description of the state of a room.
///
/// An implementation of [MSC3266](https://github.com/matrix-org/matrix-spec-proposals/pull/3266)
#[tracing::instrument(skip_all, fields(%client), name = "room_summary")]
pub(crate) async fn get_room_summary(
State(services): State<crate::State>,

View file

@ -2,7 +2,7 @@ use std::cmp::max;
use axum::extract::State;
use conduwuit::{
Err, Error, Event, Result, err, info,
Error, Event, Result, err, info,
matrix::{StateKey, pdu::PduBuilder},
};
use futures::StreamExt;
@ -63,10 +63,6 @@ pub(crate) async fn upgrade_room_route(
));
}
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
// Create a replacement room
let replacement_room = RoomId::new(services.globals.server_name());
@ -193,7 +189,6 @@ pub(crate) async fn upgrade_room_route(
blurhash: services.users.blurhash(sender_user).await.ok(),
reason: None,
join_authorized_via_users_server: None,
redact_events: None,
})
.expect("event is valid, we just created it"),
unsigned: None,

View file

@ -23,9 +23,6 @@ pub(crate) async fn send_message_event_route(
let sender_user = body.sender_user();
let sender_device = body.sender_device.as_deref();
let appservice_info = body.appservice_info.as_ref();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
// Forbid m.room.encrypted if encryption is disabled
if MessageLikeEventType::RoomEncrypted == body.event_type && !services.config.allow_encryption

View file

@ -121,9 +121,7 @@ where
.map(|(key, val)| (key, val.collect()))
.collect();
if populate {
rooms.push(summary_to_chunk(summary.clone()));
} else {
if !populate {
children = children
.iter()
.rev()
@ -146,8 +144,10 @@ where
.collect();
}
if !populate && queue.is_empty() && children.is_empty() {
break;
if populate {
rooms.push(summary_to_chunk(summary.clone()));
} else if queue.is_empty() && children.is_empty() {
return Err!(Request(InvalidParam("Room IDs in token were not found.")));
}
parents.insert(current_room.clone());

View file

@ -34,10 +34,6 @@ pub(crate) async fn send_state_event_for_key_route(
) -> Result<send_state_event::v3::Response> {
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
Ok(send_state_event::v3::Response {
event_id: send_state_event_for_key_helper(
&services,

View file

@ -1004,6 +1004,8 @@ async fn calculate_state_incremental<'a>(
) -> Result<StateChanges> {
let since_shortstatehash = since_shortstatehash.unwrap_or(current_shortstatehash);
let state_changed = since_shortstatehash != current_shortstatehash;
let encrypted_room = services
.rooms
.state_accessor
@ -1035,7 +1037,7 @@ async fn calculate_state_incremental<'a>(
})
.into();
let state_diff_ids: OptionFuture<_> = (!full_state)
let state_diff_ids: OptionFuture<_> = (!full_state && state_changed)
.then(|| {
StreamExt::into_future(
services

View file

@ -26,42 +26,41 @@ pub(crate) async fn create_typing_event_route(
{
return Err!(Request(Forbidden("You are not in this room.")));
}
if !services.users.is_suspended(sender_user).await? {
match body.state {
| Typing::Yes(duration) => {
let duration = utils::clamp(
duration.as_millis().try_into().unwrap_or(u64::MAX),
services
.server
.config
.typing_client_timeout_min_s
.try_mul(1000)?,
services
.server
.config
.typing_client_timeout_max_s
.try_mul(1000)?,
);
match body.state {
| Typing::Yes(duration) => {
let duration = utils::clamp(
duration.as_millis().try_into().unwrap_or(u64::MAX),
services
.rooms
.typing
.typing_add(
sender_user,
&body.room_id,
utils::millis_since_unix_epoch()
.checked_add(duration)
.expect("user typing timeout should not get this high"),
)
.await?;
},
| _ => {
.server
.config
.typing_client_timeout_min_s
.try_mul(1000)?,
services
.rooms
.typing
.typing_remove(sender_user, &body.room_id)
.await?;
},
}
.server
.config
.typing_client_timeout_max_s
.try_mul(1000)?,
);
services
.rooms
.typing
.typing_add(
sender_user,
&body.room_id,
utils::millis_since_unix_epoch()
.checked_add(duration)
.expect("user typing timeout should not get this high"),
)
.await?;
},
| _ => {
services
.rooms
.typing
.typing_remove(sender_user, &body.room_id)
.await?;
},
}
// ping presence

View file

@ -37,11 +37,7 @@ pub(crate) async fn get_supported_versions_route(
"v1.3".to_owned(),
"v1.4".to_owned(),
"v1.5".to_owned(),
"v1.8".to_owned(),
"v1.11".to_owned(),
"v1.12".to_owned(),
"v1.13".to_owned(),
"v1.14".to_owned(),
],
unstable_features: BTreeMap::from_iter([
("org.matrix.e2e_cross_signing".to_owned(), true),

View file

@ -94,7 +94,6 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
.ruma_route(&client::redact_event_route)
.ruma_route(&client::report_event_route)
.ruma_route(&client::report_room_route)
.ruma_route(&client::report_user_route)
.ruma_route(&client::create_alias_route)
.ruma_route(&client::delete_alias_route)
.ruma_route(&client::get_alias_route)

View file

@ -513,22 +513,6 @@ pub struct Config {
#[serde(default)]
pub allow_registration: bool,
/// If registration is enabled, and this setting is true, new users
/// registered after the first admin user will be automatically suspended
/// and will require an admin to run `!admin users unsuspend <user_id>`.
///
/// Suspended users are still able to read messages, make profile updates,
/// leave rooms, and deactivate their account, however cannot send messages,
/// invites, or create/join or otherwise modify rooms.
/// They are effectively read-only.
///
/// If you want to use this to screen people who register on your server,
/// you should add a room to `auto_join_rooms` that is public, and contains
/// information that new users can read (since they won't be able to DM
/// anyone, or send a message, and may be confused).
#[serde(default)]
pub suspend_on_register: bool,
/// Enabling this setting opens registration to anyone without restrictions.
/// This makes your server vulnerable to abuse
#[serde(default)]

View file

@ -378,10 +378,6 @@ pub(super) static MAPS: &[Descriptor] = &[
name: "userid_password",
..descriptor::RANDOM
},
Descriptor {
name: "userid_suspension",
..descriptor::RANDOM_SMALL
},
Descriptor {
name: "userid_presenceid",
..descriptor::RANDOM_SMALL

View file

@ -87,7 +87,7 @@ pub(crate) fn init(
let tracer = opentelemetry_jaeger::new_agent_pipeline()
.with_auto_split_batch(true)
.with_service_name(conduwuit_core::name())
.with_service_name("conduwuit")
.install_batch(opentelemetry_sdk::runtime::Tokio)
.expect("jaeger agent pipeline");

View file

@ -17,10 +17,7 @@ use futures::{Future, FutureExt, TryFutureExt};
use loole::{Receiver, Sender};
use ruma::{
OwnedEventId, OwnedRoomId, RoomId, UserId,
events::{
Mentions,
room::message::{Relation, RoomMessageEventContent},
},
events::room::message::{Relation, RoomMessageEventContent},
};
use tokio::sync::RwLock;
@ -47,13 +44,11 @@ struct Services {
services: StdRwLock<Option<Weak<crate::Services>>>,
}
/// Inputs to a command are a multi-line string, optional reply_id, and optional
/// sender.
/// Inputs to a command are a multi-line string and optional reply_id.
#[derive(Debug)]
pub struct CommandInput {
pub command: String,
pub reply_id: Option<OwnedEventId>,
pub sender: Option<Box<UserId>>,
}
/// Prototype of the tab-completer. The input is buffered text when tab
@ -167,39 +162,13 @@ impl Service {
.await
}
/// Sends a message, the same as send_message() but with an @room ping to
/// notify all users in the room.
pub async fn send_loud_message(
&self,
mut message_content: RoomMessageEventContent,
) -> Result<()> {
// Add @room ping
message_content = message_content.add_mentions(Mentions::with_room_mention());
self.send_message(message_content).await
}
/// Posts a command to the command processor queue and returns. Processing
/// will take place on the service worker's task asynchronously. Errors if
/// the queue is full.
pub fn command(&self, command: String, reply_id: Option<OwnedEventId>) -> Result<()> {
self.channel
.0
.send(CommandInput { command, reply_id, sender: None })
.map_err(|e| err!("Failed to enqueue admin command: {e:?}"))
}
/// Posts a command to the command processor queue with sender information
/// and returns. Processing will take place on the service worker's task
/// asynchronously. Errors if the queue is full.
pub fn command_with_sender(
&self,
command: String,
reply_id: Option<OwnedEventId>,
sender: Box<UserId>,
) -> Result<()> {
self.channel
.0
.send(CommandInput { command, reply_id, sender: Some(sender) })
.send(CommandInput { command, reply_id })
.map_err(|e| err!("Failed to enqueue admin command: {e:?}"))
}
@ -210,7 +179,7 @@ impl Service {
command: String,
reply_id: Option<OwnedEventId>,
) -> ProcessorResult {
self.process_command(CommandInput { command, reply_id, sender: None })
self.process_command(CommandInput { command, reply_id })
.await
}

View file

@ -348,11 +348,9 @@ where
self.services.search.index_pdu(shortroomid, &pdu_id, &body);
if self.services.admin.is_admin_command(pdu, &body).await {
self.services.admin.command_with_sender(
body,
Some((pdu.event_id()).into()),
pdu.sender.clone().into(),
)?;
self.services
.admin
.command(body, Some((pdu.event_id()).into()))?;
}
}
},

View file

@ -17,7 +17,6 @@ use ruma::{
uint,
};
use serde_json::value::to_raw_value;
use tracing::warn;
use super::RoomMutexGuard;
@ -102,19 +101,6 @@ pub async fn create_hash_and_sign_event(
}
}
if event_type != TimelineEventType::RoomCreate && prev_events.is_empty() {
return Err!(Request(Unknown("Event incorrectly had zero prev_events.")));
}
if state_key.is_none() && depth.lt(&uint!(2)) {
// The first two events in a room are always m.room.create and m.room.member,
// so any other events with that same depth are illegal.
warn!(
"Had unsafe depth {depth} when creating non-state event in {room_id}. Cowardly \
aborting"
);
return Err!(Request(Unknown("Unsafe depth for non-state event.")));
}
let mut pdu = PduEvent {
event_id: ruma::event_id!("$thiswillbefilledinlater").into(),
room_id: room_id.to_owned(),

View file

@ -16,21 +16,10 @@ use ruma::{
},
serde::Raw,
};
use serde::{Deserialize, Serialize};
use serde_json::json;
use crate::{Dep, account_data, admin, globals, rooms};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserSuspension {
/// Whether the user is currently suspended
pub suspended: bool,
/// When the user was suspended (Unix timestamp in milliseconds)
pub suspended_at: u64,
/// User ID of who suspended this user
pub suspended_by: String,
}
pub struct Service {
services: Services,
db: Data,
@ -63,7 +52,6 @@ struct Data {
userid_lastonetimekeyupdate: Arc<Map>,
userid_masterkeyid: Arc<Map>,
userid_password: Arc<Map>,
userid_suspension: Arc<Map>,
userid_selfsigningkeyid: Arc<Map>,
userid_usersigningkeyid: Arc<Map>,
useridprofilekey_value: Arc<Map>,
@ -99,7 +87,6 @@ impl crate::Service for Service {
userid_lastonetimekeyupdate: args.db["userid_lastonetimekeyupdate"].clone(),
userid_masterkeyid: args.db["userid_masterkeyid"].clone(),
userid_password: args.db["userid_password"].clone(),
userid_suspension: args.db["userid_suspension"].clone(),
userid_selfsigningkeyid: args.db["userid_selfsigningkeyid"].clone(),
userid_usersigningkeyid: args.db["userid_usersigningkeyid"].clone(),
useridprofilekey_value: args.db["useridprofilekey_value"].clone(),
@ -156,23 +143,6 @@ impl Service {
Ok(())
}
/// Suspend account, placing it in a read-only state
pub async fn suspend_account(&self, user_id: &UserId, suspending_user: &UserId) {
self.db.userid_suspension.raw_put(
user_id,
Json(UserSuspension {
suspended: true,
suspended_at: MilliSecondsSinceUnixEpoch::now().get().into(),
suspended_by: suspending_user.to_string(),
}),
);
}
/// Unsuspend account, placing it in a read-write state
pub async fn unsuspend_account(&self, user_id: &UserId) {
self.db.userid_suspension.remove(user_id);
}
/// Check if a user has an account on this homeserver.
#[inline]
pub async fn exists(&self, user_id: &UserId) -> bool {
@ -189,25 +159,6 @@ impl Service {
.await
}
/// Check if account is suspended
pub async fn is_suspended(&self, user_id: &UserId) -> Result<bool> {
match self
.db
.userid_suspension
.get(user_id)
.await
.deserialized::<UserSuspension>()
{
| Ok(s) => Ok(s.suspended),
| Err(e) =>
if e.is_not_found() {
Ok(false)
} else {
Err(e)
},
}
}
/// Check if account is active, infallible
pub async fn is_active(&self, user_id: &UserId) -> bool {
!self.is_deactivated(user_id).await.unwrap_or(true)