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: docs:
name: Build and Deploy Documentation name: Build and Deploy Documentation
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: secrets.CLOUDFLARE_API_TOKEN != ''
steps: steps:
- name: Sync repository - 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 rev: v1.26.0
hooks: hooks:
- id: typos - 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 - repo: local
hooks: hooks:
@ -45,3 +37,14 @@ repos:
pass_filenames: false pass_filenames: false
stages: stages:
- pre-commit - 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 # 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. [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 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 ### Linting and Formatting
It is mandatory all your changes satisfy the lints (clippy, rustc, rustdoc, etc) 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 `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 weren't nightly-exclusive features, but they currently still are. CI's rustfmt
uses nightly. 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 lints. If a lint is wrong and provides a more inefficient solution or
suggestion, allow the lint and mention that in a comment. 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 To test, format, lint, etc that CI would do, install engage, allow the `.envrc`
- Typo detection (both in code and commit messages) file using `direnv allow`, and run `engage`.
- Checking for large files
- Ensuring proper line endings and no trailing whitespace
- Validating YAML, JSON, and TOML files
- Checking for merge conflicts
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 - `engage just <group>`
# Install prefligit using cargo-binstall - Example: `engage just lints`
cargo binstall prefligit
# Install git hooks to run checks automatically If you would like to run a specific engage task in a specific group, use `just
prefligit install <GROUP> [TASK]`: `engage just lints cargo-fmt`
# Run all checks The following binaries are used in [`engage.toml`][engage.toml]:
prefligit --all-files
```
Alternatively, you can use [pre-commit](https://pre-commit.com/): - [`engage`][engage]
```bash - `nix`
# Install pre-commit - [`direnv`][direnv]
pip install pre-commit - `rustc`
- `cargo`
# Install the hooks - `cargo-fmt`
pre-commit install - `rustdoc`
- `cargo-clippy`
# Run all checks manually - [`cargo-audit`][cargo-audit]
pre-commit run --all-files - [`cargo-deb`][cargo-deb]
``` - [`lychee`][lychee]
- [`markdownlint-cli`][markdownlint-cli]
These same checks are run in CI via the prefligit-checks workflow to ensure consistency. - `dpkg`
### 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
```
### Matrix tests ### 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 ### Writing documentation
Continuwuity's website uses [`mdbook`][mdbook] and is deployed via CI using Cloudflare Pages Continuwuity's website uses [`mdbook`][mdbook] and deployed via CI using GitHub
in the [`documentation.yml`][documentation.yml] workflow file. All documentation is in the `docs/` Pages in the [`documentation.yml`][documentation.yml] workflow file with Nix's
directory at the top level. 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: The output of the mdbook generation is in `result/`. mdbooks can be opened in
```bash your browser from the individual HTML files without any web server needed.
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.
### Inclusivity and Diversity ### 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 names, comments](https://rust-lang.github.io/api-guidelines/naming.html), etc
applies here. 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 ### Creating pull requests
Please try to keep contributions to the Forgejo Instance. While the mirrors of continuwuity 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 of it, especially when the CI completed successfully and everything so it
*looks* done. *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. 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 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. 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 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 [issues]: https://forgejo.ellis.link/continuwuation/continuwuity/issues
[continuwuity-matrix]: https://matrix.to/#/#continuwuity:continuwuity.org [continuwuity-matrix]: https://matrix.to/#/#continuwuity:continuwuity.org
[complement]: https://github.com/matrix-org/complement/ [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/ [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/ [mdbook]: https://rust-lang.github.io/mdBook/
[documentation.yml]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/.forgejo/workflows/documentation.yml [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]] [[package]]
name = "ruma" name = "ruma"
version = "0.10.1" 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 = [ dependencies = [
"assign", "assign",
"js_int", "js_int",
@ -3818,7 +3818,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-appservice-api" name = "ruma-appservice-api"
version = "0.10.0" 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 = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@ -3830,7 +3830,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-client-api" name = "ruma-client-api"
version = "0.18.0" 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 = [ dependencies = [
"as_variant", "as_variant",
"assign", "assign",
@ -3853,7 +3853,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-common" name = "ruma-common"
version = "0.13.0" 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 = [ dependencies = [
"as_variant", "as_variant",
"base64 0.22.1", "base64 0.22.1",
@ -3885,7 +3885,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-events" name = "ruma-events"
version = "0.28.1" 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 = [ dependencies = [
"as_variant", "as_variant",
"indexmap 2.9.0", "indexmap 2.9.0",
@ -3910,7 +3910,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-federation-api" name = "ruma-federation-api"
version = "0.9.0" 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 = [ dependencies = [
"bytes", "bytes",
"headers", "headers",
@ -3932,7 +3932,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-identifiers-validation" name = "ruma-identifiers-validation"
version = "0.9.5" 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 = [ dependencies = [
"js_int", "js_int",
"thiserror 2.0.12", "thiserror 2.0.12",
@ -3941,7 +3941,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-identity-service-api" name = "ruma-identity-service-api"
version = "0.9.0" 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 = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@ -3951,7 +3951,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-macros" name = "ruma-macros"
version = "0.13.0" 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 = [ dependencies = [
"cfg-if", "cfg-if",
"proc-macro-crate", "proc-macro-crate",
@ -3966,7 +3966,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-push-gateway-api" name = "ruma-push-gateway-api"
version = "0.9.0" 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 = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@ -3978,7 +3978,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-signatures" name = "ruma-signatures"
version = "0.15.0" 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 = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"ed25519-dalek", "ed25519-dalek",

View file

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

View file

@ -4,10 +4,6 @@
## A community-driven [Matrix](https://matrix.org/) homeserver in Rust ## 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 --> <!-- ANCHOR_END: catchphrase -->
[continuwuity] is a Matrix homeserver written in Rust. [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 --> <!-- 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/nexy7574/continuwuity) ![](https://codeberg.org/nexy7574/continuwuity/badges/stars.svg?style=flat)
[![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)
### Why does this exist? ### 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). 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) - [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) - [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) - [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/ Documentation=https://continuwuity.org/
RequiresMountsFor=/var/lib/private/conduwuit RequiresMountsFor=/var/lib/private/conduwuit
Alias=matrix-conduwuit.service Alias=matrix-conduwuit.service
[Service] [Service]
DynamicUser=yes DynamicUser=yes
Type=notify-reload Type=notify-reload
@ -60,8 +59,7 @@ StateDirectory=conduwuit
RuntimeDirectory=conduwuit RuntimeDirectory=conduwuit
RuntimeDirectoryMode=0750 RuntimeDirectoryMode=0750
Environment=CONTINUWUITY_CONFIG=${CREDENTIALS_DIRECTORY}/config.toml Environment="CONTINUWUITY_CONFIG=/etc/conduwuit/conduwuit.toml"
LoadCredential=config.toml:/etc/conduwuit/conduwuit.toml
BindPaths=/var/lib/private/conduwuit:/var/lib/matrix-conduit BindPaths=/var/lib/private/conduwuit:/var/lib/matrix-conduit
BindPaths=/var/lib/private/conduwuit:/var/lib/private/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 #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. # Enabling this setting opens registration to anyone without restrictions.
# This makes your server vulnerable to abuse # 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 ## List of forked dependencies
During Continuwuity (and prior projects) development, we have had to fork some dependencies to support our use-cases. During Continuwuity development, we have had to fork
These forks exist for various reasons including features that upstream projects won't accept, some dependencies to support our use-cases in some areas. This ranges from
faster-paced development, Continuwuity-specific usecases, or lack of time to upstream changes. 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): - [ruma/ruma][1]: <https://github.com/girlbossceo/ruwuma> - various performance
improvements, more features, faster-paced development, better client/server interop
- [ruwuma][continuwuation-ruwuma] - Fork of [ruma/ruma][ruma] with various performance improvements, more features and better client/server interop hacks upstream won't accept, etc
- [rocksdb][continuwuation-rocksdb] - Fork of [facebook/rocksdb][rocksdb] via [`@zaidoon1`][8] with liburing build fixes and GCC debug build fixes - [facebook/rocksdb][2]: <https://github.com/girlbossceo/rocksdb> - liburing
- [jemallocator][continuwuation-jemallocator] - Fork of [tikv/jemallocator][jemallocator] fixing musl builds, suspicious code, build fixes and GCC debug build fix
and adding support for redzones in Valgrind - [tikv/jemallocator][3]: <https://github.com/girlbossceo/jemallocator> - musl
- [rustyline-async][continuwuation-rustyline-async] - Fork of [zyansheep/rustyline-async][rustyline-async] with tab completion callback builds seem to be broken on upstream, fixes some broken/suspicious code in
and `CTRL+\` signal quit event for Continuwuity console CLI places, additional safety measures, and support redzones for Valgrind
- [rust-rocksdb][continuwuation-rust-rocksdb] - Fork of [rust-rocksdb/rust-rocksdb][rust-rocksdb] fixing musl build issues, - [zyansheep/rustyline-async][4]:
removing unnecessary `gtest` include, and using our RocksDB and jemallocator forks <https://github.com/girlbossceo/rustyline-async> - tab completion callback and
- [tracing][continuwuation-tracing] - Fork of [tokio-rs/tracing][tracing] implementing `Clone` for `EnvFilter` to `CTRL+\` signal quit event for Continuwuity console CLI
support dynamically changing tracing environments - [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` ## 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 starting it. This was due to tokio-console causing gradual memory leak/usage
if left enabled. if left enabled.
## Building Docker Images [1]: https://github.com/ruma/ruma/
[2]: https://github.com/facebook/rocksdb/
To build a Docker image for Continuwuity, use the standard Docker build command: [3]: https://github.com/tikv/jemallocator/
[4]: https://github.com/zyansheep/rustyline-async/
```bash [5]: https://github.com/rust-rocksdb/rust-rocksdb/
docker build -f docker/Dockerfile . [6]: https://github.com/tokio-rs/tracing/
```
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/
[7]: https://docs.rs/tokio-console/latest/tokio_console/ [7]: https://docs.rs/tokio-console/latest/tokio_console/
[8]: https://github.com/zaidoon1/ [8]: https://github.com/zaidoon1/
[9]: https://github.com/rust-lang/cargo/issues/12162 [9]: https://github.com/rust-lang/cargo/issues/12162

View file

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

View file

@ -7,14 +7,13 @@ use futures::{
io::{AsyncWriteExt, BufWriter}, io::{AsyncWriteExt, BufWriter},
lock::Mutex, lock::Mutex,
}; };
use ruma::{EventId, UserId}; use ruma::EventId;
pub(crate) struct Context<'a> { pub(crate) struct Context<'a> {
pub(crate) services: &'a Services, pub(crate) services: &'a Services,
pub(crate) body: &'a [&'a str], pub(crate) body: &'a [&'a str],
pub(crate) timer: SystemTime, pub(crate) timer: SystemTime,
pub(crate) reply_id: Option<&'a EventId>, pub(crate) reply_id: Option<&'a EventId>,
pub(crate) sender: Option<&'a UserId>,
pub(crate) output: Mutex<BufWriter<Vec<u8>>>, pub(crate) output: Mutex<BufWriter<Vec<u8>>>,
} }
@ -37,10 +36,4 @@ impl Context<'_> {
output.write_all(s.as_bytes()).map_err(Into::into).await 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, body: &body,
timer: SystemTime::now(), timer: SystemTime::now(),
reply_id: input.reply_id.as_deref(), reply_id: input.reply_id.as_deref(),
sender: input.sender.as_deref(),
output: BufWriter::new(Vec::new()).into(), output: BufWriter::new(Vec::new()).into(),
}; };

View file

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

View file

@ -59,28 +59,6 @@ pub(super) enum UserCommand {
force: bool, 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 /// - List local users in the database
#[clap(alias = "list")] #[clap(alias = "list")]
ListUsers, ListUsers,

View file

@ -25,10 +25,7 @@ use ruma::{
}, },
events::{ events::{
GlobalAccountDataEventType, StateEventType, GlobalAccountDataEventType, StateEventType,
room::{ room::power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
message::RoomMessageEventContent,
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
},
}, },
push, push,
}; };
@ -490,25 +487,6 @@ pub(crate) async fn register_route(
{ {
services.admin.make_user_admin(&user_id).await?; services.admin.make_user_admin(&user_id).await?;
warn!("Granting {user_id} admin privileges as the first user"); 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>, body: Ruma<create_alias::v3::Request>,
) -> Result<create_alias::v3::Response> { ) -> Result<create_alias::v3::Response> {
let sender_user = body.sender_user(); 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 services
.rooms .rooms
.alias .alias
@ -66,10 +62,6 @@ pub(crate) async fn delete_alias_route(
body: Ruma<delete_alias::v3::Request>, body: Ruma<delete_alias::v3::Request>,
) -> Result<delete_alias::v3::Response> { ) -> Result<delete_alias::v3::Response> {
let sender_user = body.sender_user(); 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 services
.rooms .rooms
.alias .alias

View file

@ -128,9 +128,6 @@ pub(crate) async fn set_room_visibility_route(
// Return 404 if the room doesn't exist // Return 404 if the room doesn't exist
return Err!(Request(NotFound("Room not found"))); 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 if services
.users .users

View file

@ -52,9 +52,6 @@ pub(crate) async fn create_content_route(
body: Ruma<create_content::v3::Request>, body: Ruma<create_content::v3::Request>,
) -> Result<create_content::v3::Response> { ) -> Result<create_content::v3::Response> {
let user = body.sender_user(); 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 filename = body.filename.as_deref();
let content_type = body.content_type.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."))); 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 state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let current_member_content = services let current_member_content = services
@ -45,7 +41,6 @@ pub(crate) async fn ban_user_route(
is_direct: None, is_direct: None,
join_authorized_via_users_server: None, join_authorized_via_users_server: None,
third_party_invite: None, third_party_invite: None,
redact_events: body.redact_events,
..current_member_content ..current_member_content
}), }),
sender_user, sender_user,

View file

@ -25,9 +25,6 @@ pub(crate) async fn invite_user_route(
body: Ruma<invite_user::v3::Request>, body: Ruma<invite_user::v3::Request>,
) -> Result<invite_user::v3::Response> { ) -> Result<invite_user::v3::Response> {
let sender_user = body.sender_user(); 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 { if !services.users.is_admin(sender_user).await && services.config.block_non_admin_invites {
debug_error!( 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>, body: Ruma<join_room_by_id::v3::Request>,
) -> Result<join_room_by_id::v3::Response> { ) -> Result<join_room_by_id::v3::Response> {
let sender_user = body.sender_user(); 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( banned_room_check(
&services, &services,
@ -139,9 +136,6 @@ pub(crate) async fn join_room_by_id_or_alias_route(
let sender_user = body.sender_user(); let sender_user = body.sender_user();
let appservice_info = &body.appservice_info; let appservice_info = &body.appservice_info;
let body = &body.body; 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()) { let (servers, room_id) = match OwnedRoomId::try_from(body.room_id_or_alias.clone()) {
| Ok(room_id) => { | 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() }); 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 let server_in_room = services
.rooms .rooms
.state_cache .state_cache
.server_in_room(services.globals.server_name(), room_id) .server_in_room(services.globals.server_name(), room_id)
.await; .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 let local_join = server_in_room
|| servers.is_empty() || servers.is_empty()
|| (servers.len() == 1 && services.globals.server_is_ours(&servers[0])); || (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>, State(services): State<crate::State>,
body: Ruma<kick_user::v3::Request>, body: Ruma<kick_user::v3::Request>,
) -> Result<kick_user::v3::Response> { ) -> 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 state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let Ok(event) = services let Ok(event) = services
@ -53,7 +49,7 @@ pub(crate) async fn kick_user_route(
third_party_invite: None, third_party_invite: None,
..event ..event
}), }),
sender_user, body.sender_user(),
&body.room_id, &body.room_id,
&state_lock, &state_lock,
) )

View file

@ -52,9 +52,6 @@ pub(crate) async fn knock_room_route(
) -> Result<knock_room::v3::Response> { ) -> Result<knock_room::v3::Response> {
let sender_user = body.sender_user(); let sender_user = body.sender_user();
let body = &body.body; 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()) { let (servers, room_id) = match OwnedRoomId::try_from(body.room_id_or_alias.clone()) {
| Ok(room_id) => { | Ok(room_id) => {

View file

@ -90,7 +90,6 @@ pub async fn leave_room(
displayname: None, displayname: None,
third_party_invite: None, third_party_invite: None,
blurhash: None, blurhash: None,
redact_events: None,
}; };
let is_banned = services.rooms.metadata.is_banned(room_id); 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>, State(services): State<crate::State>,
body: Ruma<unban_user::v3::Request>, body: Ruma<unban_user::v3::Request>,
) -> Result<unban_user::v3::Response> { ) -> 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 state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let current_member_content = services let current_member_content = services
@ -46,7 +42,7 @@ pub(crate) async fn unban_user_route(
is_direct: None, is_direct: None,
..current_member_content ..current_member_content
}), }),
sender_user, body.sender_user(),
&body.room_id, &body.room_id,
&state_lock, &state_lock,
) )

View file

@ -36,9 +36,6 @@ pub(crate) async fn set_displayname_route(
body: Ruma<set_display_name::v3::Request>, body: Ruma<set_display_name::v3::Request>,
) -> Result<set_display_name::v3::Response> { ) -> Result<set_display_name::v3::Response> {
let sender_user = body.sender_user(); 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() { if *sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot update the profile of another user"))); 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>, body: Ruma<set_avatar_url::v3::Request>,
) -> Result<set_avatar_url::v3::Response> { ) -> Result<set_avatar_url::v3::Response> {
let sender_user = body.sender_user(); 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() { if *sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot update the profile of another user"))); return Err!(Request(Forbidden("You cannot update the profile of another user")));
@ -351,7 +345,6 @@ pub async fn update_displayname(
reason: None, reason: None,
is_direct: None, is_direct: None,
third_party_invite: None, third_party_invite: None,
redact_events: None,
}); });
Ok((pdu, room_id)) Ok((pdu, room_id))
@ -401,7 +394,6 @@ pub async fn update_avatar_url(
reason: None, reason: None,
is_direct: None, is_direct: None,
third_party_invite: None, third_party_invite: None,
redact_events: None,
}); });
Ok((pdu, room_id)) 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 let Some(event) = &body.read_receipt {
if !services.users.is_suspended(sender_user).await? { let receipt_content = BTreeMap::from_iter([(
let receipt_content = BTreeMap::from_iter([( event.to_owned(),
event.to_owned(), BTreeMap::from_iter([(
BTreeMap::from_iter([( ReceiptType::Read,
ReceiptType::Read, BTreeMap::from_iter([(sender_user.to_owned(), ruma::events::receipt::Receipt {
BTreeMap::from_iter([( ts: Some(MilliSecondsSinceUnixEpoch::now()),
sender_user.to_owned(), thread: ReceiptThread::Unthreaded,
ruma::events::receipt::Receipt { })]),
ts: Some(MilliSecondsSinceUnixEpoch::now()), )]),
thread: ReceiptThread::Unthreaded, )]);
},
)]),
)]),
)]);
services services
.rooms .rooms
.read_receipt .read_receipt
.readreceipt_update( .readreceipt_update(
sender_user, sender_user,
&body.room_id, &body.room_id,
&ruma::events::receipt::ReceiptEvent { &ruma::events::receipt::ReceiptEvent {
content: ruma::events::receipt::ReceiptEventContent(receipt_content), content: ruma::events::receipt::ReceiptEventContent(receipt_content),
room_id: body.room_id.clone(), room_id: body.room_id.clone(),
}, },
) )
.await; .await;
}
} }
if let Some(event) = &body.private_read_receipt { if let Some(event) = &body.private_read_receipt {

View file

@ -1,5 +1,5 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{Err, Result, matrix::pdu::PduBuilder}; use conduwuit::{Result, matrix::pdu::PduBuilder};
use ruma::{ use ruma::{
api::client::redact::redact_event, events::room::redaction::RoomRedactionEventContent, 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> { ) -> Result<redact_event::v3::Response> {
let sender_user = body.sender_user(); let sender_user = body.sender_user();
let body = &body.body; 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; 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::extract::State;
use axum_client_ip::InsecureClientIp; 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 conduwuit_service::Services;
use rand::Rng; use rand::Rng;
use ruma::{ use ruma::{
EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId, EventId, RoomId, UserId,
api::client::{ api::client::room::{report_content, report_room},
report_user, events::room::message,
room::{report_content, report_room},
},
events::{Mentions, room::message::RoomMessageEventContent},
int, int,
}; };
use tokio::time::sleep; use tokio::time::sleep;
use crate::Ruma; 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` /// # `POST /_matrix/client/v3/rooms/{roomId}/report`
/// ///
/// Reports an abusive room to homeserver admins /// Reports an abusive room to homeserver admins
@ -37,10 +24,14 @@ pub(crate) async fn report_room_route(
InsecureClientIp(client): InsecureClientIp, InsecureClientIp(client): InsecureClientIp,
body: Ruma<report_room::v3::Request>, body: Ruma<report_room::v3::Request>,
) -> Result<report_room::v3::Response> { ) -> Result<report_room::v3::Response> {
// user authentication
let sender_user = body.sender_user(); 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) { if body.reason.as_ref().is_some_and(|s| s.len() > 750) {
return Err!(Request( 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" "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 { // send admin room message that we received the report with an @room ping for
sender: sender_user.to_owned(), // urgency
room_id: Some(body.room_id.clone()), services
event_id: None, .admin
user_id: None, .send_message(message::RoomMessageEventContent::text_markdown(format!(
report_type: "room".to_owned(), "@room Room report received from {} -\n\nRoom ID: {}\n\nReport Reason: {}",
reason: body.reason.clone(), sender_user.to_owned(),
score: None, body.room_id,
}; body.reason.as_deref().unwrap_or("")
)))
services.admin.send_message(build_report(report)).await.ok(); .await
.ok();
Ok(report_room::v3::Response {}) Ok(report_room::v3::Response {})
} }
@ -92,9 +79,14 @@ pub(crate) async fn report_event_route(
) -> Result<report_content::v3::Response> { ) -> Result<report_content::v3::Response> {
// user authentication // user authentication
let sender_user = body.sender_user(); 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; delay_response().await;
@ -113,73 +105,27 @@ pub(crate) async fn report_event_route(
&pdu, &pdu,
) )
.await?; .await?;
info!(
"Received event report by user {sender_user} for room {} and event ID {}, with reason: \ // send admin room message that we received the report with an @room ping for
\"{}\"", // urgency
body.room_id, services
body.event_id, .admin
body.reason.as_deref().unwrap_or("") .send_message(message::RoomMessageEventContent::text_markdown(format!(
); "@room Event report received from {} -\n\nEvent ID: {}\nRoom ID: {}\nSent By: \
let report = Report { {}\n\nReport Score: {}\nReport Reason: {}",
sender: sender_user.to_owned(), sender_user.to_owned(),
room_id: Some(body.room_id.clone()), pdu.event_id,
event_id: Some(body.event_id.clone()), pdu.room_id,
user_id: None, pdu.sender,
report_type: "event".to_owned(), body.score.unwrap_or_else(|| ruma::Int::from(0)),
reason: body.reason.clone(), body.reason.as_deref().unwrap_or("")
score: body.score, )))
}; .await
services.admin.send_message(build_report(report)).await.ok(); .ok();
Ok(report_content::v3::Response {}) 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: /// in the following order:
/// ///
/// check if the room ID from the URI matches the PDU's room ID /// 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(()) 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 /// even though this is kinda security by obscurity, let's still make a small
/// random delay sending a response per spec suggestion regarding /// random delay sending a response per spec suggestion regarding
/// enumerating for potential events existing in our server. /// 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.",))); 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 { let room_id: OwnedRoomId = match &body.room_id {
| Some(custom_room_id) => custom_room_id_check(&services, custom_room_id)?, | Some(custom_room_id) => custom_room_id_check(&services, custom_room_id)?,
| _ => RoomId::new(&services.server.name), | _ => 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/unstable/im.nheko.summary/summary/{roomIdOrAlias}`
/// # `GET /_matrix/client/v1/room_summary/{roomIdOrAlias}`
/// ///
/// Returns a short description of the state of a room. /// 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")] #[tracing::instrument(skip_all, fields(%client), name = "room_summary")]
pub(crate) async fn get_room_summary( pub(crate) async fn get_room_summary(
State(services): State<crate::State>, State(services): State<crate::State>,

View file

@ -2,7 +2,7 @@ use std::cmp::max;
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Err, Error, Event, Result, err, info, Error, Event, Result, err, info,
matrix::{StateKey, pdu::PduBuilder}, matrix::{StateKey, pdu::PduBuilder},
}; };
use futures::StreamExt; 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 // Create a replacement room
let replacement_room = RoomId::new(services.globals.server_name()); 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(), blurhash: services.users.blurhash(sender_user).await.ok(),
reason: None, reason: None,
join_authorized_via_users_server: None, join_authorized_via_users_server: None,
redact_events: None,
}) })
.expect("event is valid, we just created it"), .expect("event is valid, we just created it"),
unsigned: None, unsigned: None,

View file

@ -23,9 +23,6 @@ pub(crate) async fn send_message_event_route(
let sender_user = body.sender_user(); let sender_user = body.sender_user();
let sender_device = body.sender_device.as_deref(); let sender_device = body.sender_device.as_deref();
let appservice_info = body.appservice_info.as_ref(); 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 // Forbid m.room.encrypted if encryption is disabled
if MessageLikeEventType::RoomEncrypted == body.event_type && !services.config.allow_encryption if MessageLikeEventType::RoomEncrypted == body.event_type && !services.config.allow_encryption

View file

@ -121,9 +121,7 @@ where
.map(|(key, val)| (key, val.collect())) .map(|(key, val)| (key, val.collect()))
.collect(); .collect();
if populate { if !populate {
rooms.push(summary_to_chunk(summary.clone()));
} else {
children = children children = children
.iter() .iter()
.rev() .rev()
@ -146,8 +144,10 @@ where
.collect(); .collect();
} }
if !populate && queue.is_empty() && children.is_empty() { if populate {
break; 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()); 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> { ) -> Result<send_state_event::v3::Response> {
let sender_user = body.sender_user(); 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 { Ok(send_state_event::v3::Response {
event_id: send_state_event_for_key_helper( event_id: send_state_event_for_key_helper(
&services, &services,

View file

@ -1004,6 +1004,8 @@ async fn calculate_state_incremental<'a>(
) -> Result<StateChanges> { ) -> Result<StateChanges> {
let since_shortstatehash = since_shortstatehash.unwrap_or(current_shortstatehash); let since_shortstatehash = since_shortstatehash.unwrap_or(current_shortstatehash);
let state_changed = since_shortstatehash != current_shortstatehash;
let encrypted_room = services let encrypted_room = services
.rooms .rooms
.state_accessor .state_accessor
@ -1035,7 +1037,7 @@ async fn calculate_state_incremental<'a>(
}) })
.into(); .into();
let state_diff_ids: OptionFuture<_> = (!full_state) let state_diff_ids: OptionFuture<_> = (!full_state && state_changed)
.then(|| { .then(|| {
StreamExt::into_future( StreamExt::into_future(
services 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."))); return Err!(Request(Forbidden("You are not in this room.")));
} }
if !services.users.is_suspended(sender_user).await? {
match body.state { match body.state {
| Typing::Yes(duration) => { | Typing::Yes(duration) => {
let duration = utils::clamp( let duration = utils::clamp(
duration.as_millis().try_into().unwrap_or(u64::MAX), 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)?,
);
services services
.rooms .server
.typing .config
.typing_add( .typing_client_timeout_min_s
sender_user, .try_mul(1000)?,
&body.room_id,
utils::millis_since_unix_epoch()
.checked_add(duration)
.expect("user typing timeout should not get this high"),
)
.await?;
},
| _ => {
services services
.rooms .server
.typing .config
.typing_remove(sender_user, &body.room_id) .typing_client_timeout_max_s
.await?; .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 // ping presence

View file

@ -37,11 +37,7 @@ pub(crate) async fn get_supported_versions_route(
"v1.3".to_owned(), "v1.3".to_owned(),
"v1.4".to_owned(), "v1.4".to_owned(),
"v1.5".to_owned(), "v1.5".to_owned(),
"v1.8".to_owned(),
"v1.11".to_owned(), "v1.11".to_owned(),
"v1.12".to_owned(),
"v1.13".to_owned(),
"v1.14".to_owned(),
], ],
unstable_features: BTreeMap::from_iter([ unstable_features: BTreeMap::from_iter([
("org.matrix.e2e_cross_signing".to_owned(), true), ("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::redact_event_route)
.ruma_route(&client::report_event_route) .ruma_route(&client::report_event_route)
.ruma_route(&client::report_room_route) .ruma_route(&client::report_room_route)
.ruma_route(&client::report_user_route)
.ruma_route(&client::create_alias_route) .ruma_route(&client::create_alias_route)
.ruma_route(&client::delete_alias_route) .ruma_route(&client::delete_alias_route)
.ruma_route(&client::get_alias_route) .ruma_route(&client::get_alias_route)

View file

@ -513,22 +513,6 @@ pub struct Config {
#[serde(default)] #[serde(default)]
pub allow_registration: bool, 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. /// Enabling this setting opens registration to anyone without restrictions.
/// This makes your server vulnerable to abuse /// This makes your server vulnerable to abuse
#[serde(default)] #[serde(default)]

View file

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

View file

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

View file

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

View file

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

View file

@ -17,7 +17,6 @@ use ruma::{
uint, uint,
}; };
use serde_json::value::to_raw_value; use serde_json::value::to_raw_value;
use tracing::warn;
use super::RoomMutexGuard; 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 { let mut pdu = PduEvent {
event_id: ruma::event_id!("$thiswillbefilledinlater").into(), event_id: ruma::event_id!("$thiswillbefilledinlater").into(),
room_id: room_id.to_owned(), room_id: room_id.to_owned(),

View file

@ -16,21 +16,10 @@ use ruma::{
}, },
serde::Raw, serde::Raw,
}; };
use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
use crate::{Dep, account_data, admin, globals, rooms}; 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 { pub struct Service {
services: Services, services: Services,
db: Data, db: Data,
@ -63,7 +52,6 @@ struct Data {
userid_lastonetimekeyupdate: Arc<Map>, userid_lastonetimekeyupdate: Arc<Map>,
userid_masterkeyid: Arc<Map>, userid_masterkeyid: Arc<Map>,
userid_password: Arc<Map>, userid_password: Arc<Map>,
userid_suspension: Arc<Map>,
userid_selfsigningkeyid: Arc<Map>, userid_selfsigningkeyid: Arc<Map>,
userid_usersigningkeyid: Arc<Map>, userid_usersigningkeyid: Arc<Map>,
useridprofilekey_value: Arc<Map>, useridprofilekey_value: Arc<Map>,
@ -99,7 +87,6 @@ impl crate::Service for Service {
userid_lastonetimekeyupdate: args.db["userid_lastonetimekeyupdate"].clone(), userid_lastonetimekeyupdate: args.db["userid_lastonetimekeyupdate"].clone(),
userid_masterkeyid: args.db["userid_masterkeyid"].clone(), userid_masterkeyid: args.db["userid_masterkeyid"].clone(),
userid_password: args.db["userid_password"].clone(), userid_password: args.db["userid_password"].clone(),
userid_suspension: args.db["userid_suspension"].clone(),
userid_selfsigningkeyid: args.db["userid_selfsigningkeyid"].clone(), userid_selfsigningkeyid: args.db["userid_selfsigningkeyid"].clone(),
userid_usersigningkeyid: args.db["userid_usersigningkeyid"].clone(), userid_usersigningkeyid: args.db["userid_usersigningkeyid"].clone(),
useridprofilekey_value: args.db["useridprofilekey_value"].clone(), useridprofilekey_value: args.db["useridprofilekey_value"].clone(),
@ -156,23 +143,6 @@ impl Service {
Ok(()) 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. /// Check if a user has an account on this homeserver.
#[inline] #[inline]
pub async fn exists(&self, user_id: &UserId) -> bool { pub async fn exists(&self, user_id: &UserId) -> bool {
@ -189,25 +159,6 @@ impl Service {
.await .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 /// Check if account is active, infallible
pub async fn is_active(&self, user_id: &UserId) -> bool { pub async fn is_active(&self, user_id: &UserId) -> bool {
!self.is_deactivated(user_id).await.unwrap_or(true) !self.is_deactivated(user_id).await.unwrap_or(true)