diff --git a/.forgejo/workflows/documentation.yml b/.forgejo/workflows/documentation.yml index 4f3e903c..7d95a317 100644 --- a/.forgejo/workflows/documentation.yml +++ b/.forgejo/workflows/documentation.yml @@ -17,7 +17,6 @@ jobs: docs: name: Build and Deploy Documentation runs-on: ubuntu-latest - if: secrets.CLOUDFLARE_API_TOKEN != '' steps: - name: Sync repository diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index fcfaade5..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -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 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 68e3a982..fc0f9d71 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1c091183..da426801 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 ` +- 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 + [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: -``` -[(optional scope)]: - -[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 diff --git a/Cargo.lock b/Cargo.lock index 82e7a20d..7852d2ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index b815e2b8..f3de5d7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", diff --git a/README.md b/README.md index 60dcf81d..e3eb807f 100644 --- a/README.md +++ b/README.md @@ -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) - - - [continuwuity] is a Matrix homeserver written in Rust. @@ -15,13 +11,11 @@ It's a community continuation of the [conduwuit](https://github.com/girlbossceo/ -[![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) diff --git a/arch/conduwuit.service b/arch/conduwuit.service index d5a65e4d..c86e37bd 100644 --- a/arch/conduwuit.service +++ b/arch/conduwuit.service @@ -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 diff --git a/committed.toml b/committed.toml deleted file mode 100644 index 59750fa5..00000000 --- a/committed.toml +++ /dev/null @@ -1,3 +0,0 @@ -style = "conventional" -subject_length = 72 -allowed_types = ["ci", "build", "fix", "feat", "chore", "docs", "style", "refactor", "perf", "test"] diff --git a/conduwuit-example.toml b/conduwuit-example.toml index 794ab870..1a8be2aa 100644 --- a/conduwuit-example.toml +++ b/conduwuit-example.toml @@ -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 `. -# -# 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 # diff --git a/docs/development.md b/docs/development.md index 68b963c8..1e344f41 100644 --- a/docs/development.md +++ b/docs/development.md @@ -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]: - various performance +improvements, more features, faster-paced development, better client/server interop +hacks upstream won't accept, etc +- [facebook/rocksdb][2]: - liburing +build fixes and GCC debug build fix +- [tikv/jemallocator][3]: - 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]: + - tab completion callback and +`CTRL+\` signal quit event for Continuwuity console CLI +- [rust-rocksdb/rust-rocksdb][5]: + - [`@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]: - 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 diff --git a/src/admin/admin.rs b/src/admin/admin.rs index 50b9db7c..0d636c72 100644 --- a/src/admin/admin.rs +++ b/src/admin/admin.rs @@ -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 diff --git a/src/admin/context.rs b/src/admin/context.rs index 3d3cffb7..270537be 100644 --- a/src/admin/context.rs +++ b/src/admin/context.rs @@ -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>>, } @@ -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()) - } } diff --git a/src/admin/processor.rs b/src/admin/processor.rs index e80000c1..ca3f8f1a 100644 --- a/src/admin/processor.rs +++ b/src/admin/processor.rs @@ -63,7 +63,6 @@ async fn process_command(services: Arc, 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(), }; diff --git a/src/admin/room/moderation.rs b/src/admin/room/moderation.rs index 5fb5bb3e..b2bef2a1 100644 --- a/src/admin/room/moderation.rs +++ b/src/admin/room/moderation.rs @@ -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); } diff --git a/src/admin/user/commands.rs b/src/admin/user/commands.rs index 86206c2b..f10a1720 100644 --- a/src/admin/user/commands.rs +++ b/src/admin/user/commands.rs @@ -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) -> Result { let user_id = parse_local_user_id(self.services, &username)?; diff --git a/src/admin/user/mod.rs b/src/admin/user/mod.rs index 645d3637..e789376a 100644 --- a/src/admin/user/mod.rs +++ b/src/admin/user/mod.rs @@ -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, diff --git a/src/api/client/account.rs b/src/api/client/account.rs index 12801e7d..7e9e6c37 100644 --- a/src/api/client/account.rs +++ b/src/api/client/account.rs @@ -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(); - } } } } diff --git a/src/api/client/alias.rs b/src/api/client/alias.rs index 97c1a1bd..336bbc8a 100644 --- a/src/api/client/alias.rs +++ b/src/api/client/alias.rs @@ -18,10 +18,6 @@ pub(crate) async fn create_alias_route( body: Ruma, ) -> Result { 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, ) -> Result { 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 diff --git a/src/api/client/directory.rs b/src/api/client/directory.rs index 00879274..e5c4cd81 100644 --- a/src/api/client/directory.rs +++ b/src/api/client/directory.rs @@ -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 diff --git a/src/api/client/media.rs b/src/api/client/media.rs index 3f491d54..4e223c2f 100644 --- a/src/api/client/media.rs +++ b/src/api/client/media.rs @@ -52,9 +52,6 @@ pub(crate) async fn create_content_route( body: Ruma, ) -> Result { 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(); diff --git a/src/api/client/membership/ban.rs b/src/api/client/membership/ban.rs index 339dcf2e..28d9c6be 100644 --- a/src/api/client/membership/ban.rs +++ b/src/api/client/membership/ban.rs @@ -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, diff --git a/src/api/client/membership/invite.rs b/src/api/client/membership/invite.rs index 018fb774..3a3a7661 100644 --- a/src/api/client/membership/invite.rs +++ b/src/api/client/membership/invite.rs @@ -25,9 +25,6 @@ pub(crate) async fn invite_user_route( body: Ruma, ) -> Result { 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!( diff --git a/src/api/client/membership/join.rs b/src/api/client/membership/join.rs index dc170cbf..0cd23051 100644 --- a/src/api/client/membership/join.rs +++ b/src/api/client/membership/join.rs @@ -65,9 +65,6 @@ pub(crate) async fn join_room_by_id_route( body: Ruma, ) -> Result { 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])); diff --git a/src/api/client/membership/kick.rs b/src/api/client/membership/kick.rs index 5e0e86e2..b33fb372 100644 --- a/src/api/client/membership/kick.rs +++ b/src/api/client/membership/kick.rs @@ -14,10 +14,6 @@ pub(crate) async fn kick_user_route( State(services): State, body: Ruma, ) -> Result { - 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, ) diff --git a/src/api/client/membership/knock.rs b/src/api/client/membership/knock.rs index 79f16631..801cc867 100644 --- a/src/api/client/membership/knock.rs +++ b/src/api/client/membership/knock.rs @@ -52,9 +52,6 @@ pub(crate) async fn knock_room_route( ) -> Result { 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) => { diff --git a/src/api/client/membership/leave.rs b/src/api/client/membership/leave.rs index f4f1666b..1246c6df 100644 --- a/src/api/client/membership/leave.rs +++ b/src/api/client/membership/leave.rs @@ -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); diff --git a/src/api/client/membership/unban.rs b/src/api/client/membership/unban.rs index 34c5eace..44e6a7f1 100644 --- a/src/api/client/membership/unban.rs +++ b/src/api/client/membership/unban.rs @@ -14,10 +14,6 @@ pub(crate) async fn unban_user_route( State(services): State, body: Ruma, ) -> Result { - 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, ) diff --git a/src/api/client/profile.rs b/src/api/client/profile.rs index 1882495c..123fdc19 100644 --- a/src/api/client/profile.rs +++ b/src/api/client/profile.rs @@ -36,9 +36,6 @@ pub(crate) async fn set_displayname_route( body: Ruma, ) -> Result { 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, ) -> Result { 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)) diff --git a/src/api/client/read_marker.rs b/src/api/client/read_marker.rs index 9d813294..069ef0da 100644 --- a/src/api/client/read_marker.rs +++ b/src/api/client/read_marker.rs @@ -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 { diff --git a/src/api/client/redact.rs b/src/api/client/redact.rs index 86d871ff..df5e05de 100644 --- a/src/api/client/redact.rs +++ b/src/api/client/redact.rs @@ -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 { 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; diff --git a/src/api/client/report.rs b/src/api/client/report.rs index 60a16e1a..35aef035 100644 --- a/src/api/client/report.rs +++ b/src/api/client/report.rs @@ -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, - event_id: Option, - user_id: Option, - report_type: String, - reason: Option, - score: Option, -} - /// # `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, ) -> Result { + // 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 { // 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, - InsecureClientIp(client): InsecureClientIp, - body: Ruma, -) -> Result { - // 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. diff --git a/src/api/client/room/create.rs b/src/api/client/room/create.rs index 238691d1..77160bc5 100644 --- a/src/api/client/room/create.rs +++ b/src/api/client/room/create.rs @@ -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), diff --git a/src/api/client/room/summary.rs b/src/api/client/room/summary.rs index 635f5a8a..a2cc7766 100644 --- a/src/api/client/room/summary.rs +++ b/src/api/client/room/summary.rs @@ -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, diff --git a/src/api/client/room/upgrade.rs b/src/api/client/room/upgrade.rs index ae632235..045f1e3b 100644 --- a/src/api/client/room/upgrade.rs +++ b/src/api/client/room/upgrade.rs @@ -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, diff --git a/src/api/client/send.rs b/src/api/client/send.rs index b87d1822..f753fa65 100644 --- a/src/api/client/send.rs +++ b/src/api/client/send.rs @@ -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 diff --git a/src/api/client/space.rs b/src/api/client/space.rs index 23b1e80f..92768926 100644 --- a/src/api/client/space.rs +++ b/src/api/client/space.rs @@ -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()); diff --git a/src/api/client/state.rs b/src/api/client/state.rs index c0f5fe7c..96b2c4f1 100644 --- a/src/api/client/state.rs +++ b/src/api/client/state.rs @@ -34,10 +34,6 @@ pub(crate) async fn send_state_event_for_key_route( ) -> Result { 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, diff --git a/src/api/client/sync/v3.rs b/src/api/client/sync/v3.rs index 01428c08..d753087a 100644 --- a/src/api/client/sync/v3.rs +++ b/src/api/client/sync/v3.rs @@ -1004,6 +1004,8 @@ async fn calculate_state_incremental<'a>( ) -> Result { 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 diff --git a/src/api/client/typing.rs b/src/api/client/typing.rs index 7b0df538..1d8d02fd 100644 --- a/src/api/client/typing.rs +++ b/src/api/client/typing.rs @@ -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 diff --git a/src/api/client/unversioned.rs b/src/api/client/unversioned.rs index a4136d1a..232d5b28 100644 --- a/src/api/client/unversioned.rs +++ b/src/api/client/unversioned.rs @@ -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), diff --git a/src/api/router.rs b/src/api/router.rs index d1b05a91..5416e9e9 100644 --- a/src/api/router.rs +++ b/src/api/router.rs @@ -94,7 +94,6 @@ pub fn build(router: Router, server: &Server) -> Router { .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) diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs index e3db4900..d4a10345 100644 --- a/src/core/config/mod.rs +++ b/src/core/config/mod.rs @@ -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 `. - /// - /// 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)] diff --git a/src/database/maps.rs b/src/database/maps.rs index 214dbf34..19f9ced4 100644 --- a/src/database/maps.rs +++ b/src/database/maps.rs @@ -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 diff --git a/src/main/logging.rs b/src/main/logging.rs index 36a8896c..b47caaf4 100644 --- a/src/main/logging.rs +++ b/src/main/logging.rs @@ -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"); diff --git a/src/service/admin/mod.rs b/src/service/admin/mod.rs index d971ce95..0c0e425a 100644 --- a/src/service/admin/mod.rs +++ b/src/service/admin/mod.rs @@ -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>>, } -/// 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, - pub sender: Option>, } /// 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) -> 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, - sender: Box, - ) -> 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, ) -> ProcessorResult { - self.process_command(CommandInput { command, reply_id, sender: None }) + self.process_command(CommandInput { command, reply_id }) .await } diff --git a/src/service/rooms/timeline/append.rs b/src/service/rooms/timeline/append.rs index 1d404e8a..9b8566d3 100644 --- a/src/service/rooms/timeline/append.rs +++ b/src/service/rooms/timeline/append.rs @@ -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()))?; } } }, diff --git a/src/service/rooms/timeline/create.rs b/src/service/rooms/timeline/create.rs index 20ccaf56..deded5a7 100644 --- a/src/service/rooms/timeline/create.rs +++ b/src/service/rooms/timeline/create.rs @@ -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(), diff --git a/src/service/users/mod.rs b/src/service/users/mod.rs index d2dfccd9..701561a8 100644 --- a/src/service/users/mod.rs +++ b/src/service/users/mod.rs @@ -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, userid_masterkeyid: Arc, userid_password: Arc, - userid_suspension: Arc, userid_selfsigningkeyid: Arc, userid_usersigningkeyid: Arc, useridprofilekey_value: Arc, @@ -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 { - match self - .db - .userid_suspension - .get(user_id) - .await - .deserialized::() - { - | 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)