Compare commits

...

42 Commits

Author SHA1 Message Date
Jade Ellis 7a8c409ff9 refactor: Use snafu
This should gradtly improve the debugging experience by allowing
tracking backtraces to get more context from the error.
A lot of the touced files here are just cleaning up the old way of
creating errors.
2026-02-22 14:06:15 +00:00
Renovate Bot 688ef727e5 chore(deps): update rust crate nix to 0.31.0 2026-02-21 16:33:05 +00:00
Shannon Sterz 3de026160e docs: express forbidden_remote_server_names as valid regex
this field expects a regex not a glob, so the correct value should be
".*" if one wants to block all remote server names. otherwise, setting
"*" as documented results in an error on start because the configuration
could not be properly parsed.
2026-02-21 16:27:59 +00:00
Ginger 9fe761513d chore: Clippy & prek fixes 2026-02-21 11:27:39 -05:00
Renovate Bot abf1e1195a chore(deps): update rust crate libloading to 0.9.0 2026-02-21 01:55:48 +00:00
Ginger d9537e9b55 fix: Forbid registering users with a non-local localpart 2026-02-20 20:54:19 -05:00
Jade Ellis 0d1de70d8f fix(deps): Update lockfile 2026-02-21 00:22:42 +00:00
Ben Botwin 4aa03a71eb fix(nix): Added unstable flag to buildDeps 2026-02-21 00:15:53 +00:00
aviac f847918575 fix(nix): Fix all-features build
The build was broken since we started using an unstable reqwest version
which requires setting an extra feature flag
2026-02-21 00:15:53 +00:00
Renovate Bot 7569a0545b chore(deps): update dependency lddtree to 0.5.0 2026-02-20 22:59:34 +00:00
Jade Ellis b6c5991e1f chore(deps): Update rand
A couple indirect deps are still on rand_core 0.6 but we can deal
2026-02-20 22:57:45 +00:00
Katie Kloss efd879fcd8 docs: Add news fragment 2026-02-20 10:13:54 +00:00
Katie Kloss 92a848f74d fix: Crash before starting on OpenBSD
core_affinity doesn't return any cores on OpenBSD, so we try to
clamp(1, 0). This is Less Good than fixing that crate, but at
least allows the server to start up.
2026-02-20 10:13:54 +00:00
Renovate Bot 776b5865ba chore(deps): update sentry-rust monorepo to 0.46.0 2026-02-19 14:56:25 +00:00
timedout 722bacbe89 chore: Fix busted lockfile merge 2026-02-19 02:33:41 +00:00
Jade Ellis 46907e3dce chore: Migrate to axum 0.8
Co-authored-by: dasha_uwu
2026-02-19 02:18:29 +00:00
timedout 31e2195e56 fix: Remove non-compliant and non-functional non-authoritative directory queries
chore: Add news frag
2026-02-19 01:37:42 +00:00
Terry 7ecac93ddc fix: Remove rocksdb secondary mode 2026-02-18 23:11:53 +00:00
Terry 6a0b103722 docs: Changelog 2026-02-18 23:11:53 +00:00
Terry 23d77b614f fix: Remove ability to set rocksdb as read only 2026-02-18 23:11:53 +00:00
stratself e01aa44b16 fix: add nodejs URL in CONTRIBUTING.md page 2026-02-18 23:07:29 +00:00
stratself a08739c246 docs: rewrite how to load docs with new rspress engine 2026-02-18 23:07:29 +00:00
Ginger c14864b881 fix: Wording fixes 2026-02-18 14:41:03 +00:00
Ginger 1773e72e68 feat(docs): Add a note about !779 to the troubleshooting page 2026-02-18 14:41:03 +00:00
kraem 0f94d55689 fix: don't warn about needed backfill via federation for non-federated rooms 2026-02-18 14:27:14 +00:00
Renovate Bot abfb6377c2 chore(deps): update rust-patch-updates 2026-02-18 14:26:49 +00:00
Renovate Bot 91d64f5b24 chore(deps): update rust crate askama to 0.15.0 2026-02-18 05:04:23 +00:00
Jade Ellis 9a3f3f6e78 ci: Explicitly enable Dependency Dashboard 2026-02-17 21:33:30 +00:00
Jade Ellis b3e31a4aad ci(deps): Automerge typos updates 2026-02-17 21:33:13 +00:00
Jade Ellis 8cda431cc6 ci(deps): Group npm patch updates 2026-02-17 21:30:51 +00:00
Renovate Bot 02b9a3f713 chore(deps): update pre-commit hook crate-ci/typos to v1.43.5 2026-02-17 05:03:45 +00:00
timedout d40893730c chore: Lighten the phrasing 2026-02-17 02:07:19 +00:00
timedout 28fae58cf6 chore: Add news frag & rebuild config 2026-02-17 02:07:19 +00:00
timedout f458f6ab76 chore: Disable presence by default, and add warnings to other heavy ops 2026-02-17 02:07:19 +00:00
Shane Jaroch fdf9cea533 fix(admin-cli): concatenation/formatting error, i.e.,
**NOTE:** If there are any features, tools, or admin internals dependent on this output that would break, let me know!
I'm hoping this is acceptable, since it's a human-readable command.

Current output:

```shell
uwu> server list-backups
    #1 Mon, 9 Feb 2026 20:36:25 +0000: 66135580 bytes, 595 files#2 Wed, 11 Feb 2026 02:33:15 +0000: 270963746 bytes, 1002 files#3 Sat, 14 Feb 2026 22:11:19 +0000: 675905487 bytes, 2139 files
```

Should be:

```shell
uwu> server list-backups
    #1 Mon, 9 Feb 2026 20:36:25 +0000: 66135580 bytes, 595 files
    #2 Wed, 11 Feb 2026 02:33:15 +0000: 270963746 bytes, 1002 files
    #3 Sat, 14 Feb 2026 22:11:19 +0000: 675905487 bytes, 2139 files
```
2026-02-16 00:52:02 -05:00
Jade Ellis ecb1b73c84 style: Trailing whitespace 2026-02-16 03:47:16 +00:00
rooot e03082480a docs(livekit): document nginx websockets too
Signed-off-by: rooot <hey@rooot.gay>
2026-02-16 03:43:43 +00:00
rooot f9e7f019ad docs(livekit): fix port in caddy config example
Signed-off-by: rooot <hey@rooot.gay>
2026-02-16 03:43:43 +00:00
rooot 12069e7c86 docs(livekit): add nginx proxy example
Signed-off-by: rooot <hey@rooot.gay>
2026-02-16 03:43:42 +00:00
Jade Ellis 77928a62b4 docs: Document BSD community room 2026-02-16 03:31:56 +00:00
elisaado c73cb5c1bf feat(docs): Add Kubernetes documentation with sample (#1387)
Reviewed-on: https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1387
Reviewed-by: Jade Ellis <jade@ellis.link>
Co-authored-by: elisaado <forgejoellis@elisaado.com>
Co-committed-by: elisaado <forgejoellis@elisaado.com>
2026-02-16 03:14:29 +00:00
Jade Ellis a140eacb04 docs: Fix trailing list 2026-02-16 03:12:50 +00:00
92 changed files with 1811 additions and 1247 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ repos:
- id: check-added-large-files - id: check-added-large-files
- repo: https://github.com/crate-ci/typos - repo: https://github.com/crate-ci/typos
rev: v1.43.4 rev: v1.43.5
hooks: hooks:
- id: typos - id: typos
- id: typos - id: typos
+16 -8
View File
@@ -85,24 +85,31 @@ If your changes are done to fix Matrix tests, please note that in your pull requ
### Writing documentation ### Writing documentation
Continuwuity's website uses [`mdbook`][mdbook] and is deployed via CI using Cloudflare Pages Continuwuity's website uses [`rspress`][rspress] and is deployed via CI using Cloudflare Pages
in the [`documentation.yml`][documentation.yml] workflow file. All documentation is in the `docs/` in the [`documentation.yml`][documentation.yml] workflow file. All documentation is in the `docs/`
directory at the top level. directory at the top level.
To build the documentation locally: To load the documentation locally:
1. Install NodeJS and npm from their [official website][nodejs-download] or via your package manager of choice
2. From the project's root directory, install the relevant npm modules
1. Install mdbook if you don't have it already:
```bash ```bash
cargo install mdbook # or cargo binstall, or another method npm ci
``` ```
2. Build the documentation: 3. Make changes to the document pages as you see fit
4. Generate a live preview of the documentation
```bash ```bash
mdbook build npm run docs:dev
``` ```
The output of the mdbook generation is in `public/`. You can open the HTML files directly in your browser without needing a web server. A webserver for the docs will be spun up for you (e.g. at `http://localhost:3000`). Any changes you make to the documentation will be live-reloaded on the webpage.
Alternatively, you can build the documentation using `npm run docs:build` - the output of this will be in the `/doc_build` directory. Once you're happy with your documentation updates, you can commit the changes.
### Commit Messages ### Commit Messages
@@ -169,5 +176,6 @@ continuwuity Matrix rooms for Code of Conduct violations.
[continuwuity-matrix]: https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org [continuwuity-matrix]: https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org
[complement]: https://github.com/matrix-org/complement/ [complement]: https://github.com/matrix-org/complement/
[sytest]: https://github.com/matrix-org/sytest/ [sytest]: https://github.com/matrix-org/sytest/
[mdbook]: https://rust-lang.github.io/mdBook/ [nodejs-download]: https://nodejs.org/en/download
[rspress]: https://rspress.rs/
[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
Generated
+511 -529
View File
File diff suppressed because it is too large Load Diff
+21 -15
View File
@@ -68,7 +68,7 @@ default-features = false
version = "0.1.3" version = "0.1.3"
[workspace.dependencies.rand] [workspace.dependencies.rand]
version = "0.8.5" version = "0.10.0"
# Used for the http request / response body type for Ruma endpoints used with reqwest # Used for the http request / response body type for Ruma endpoints used with reqwest
[workspace.dependencies.bytes] [workspace.dependencies.bytes]
@@ -84,7 +84,7 @@ version = "1.3.1"
version = "1.11.1" version = "1.11.1"
[workspace.dependencies.axum] [workspace.dependencies.axum]
version = "0.7.9" version = "0.8.8"
default-features = false default-features = false
features = [ features = [
"form", "form",
@@ -97,7 +97,7 @@ features = [
] ]
[workspace.dependencies.axum-extra] [workspace.dependencies.axum-extra]
version = "0.9.6" version = "0.10.1"
default-features = false default-features = false
features = ["typed-header", "tracing"] features = ["typed-header", "tracing"]
@@ -110,7 +110,7 @@ default-features = false
version = "0.7" version = "0.7"
[workspace.dependencies.axum-client-ip] [workspace.dependencies.axum-client-ip]
version = "0.6.1" version = "0.7"
[workspace.dependencies.tower] [workspace.dependencies.tower]
version = "0.5.2" version = "0.5.2"
@@ -118,7 +118,7 @@ default-features = false
features = ["util"] features = ["util"]
[workspace.dependencies.tower-http] [workspace.dependencies.tower-http]
version = "0.6.2" version = "0.6.8"
default-features = false default-features = false
features = [ features = [
"add-extension", "add-extension",
@@ -253,7 +253,7 @@ features = [
version = "0.4.0" version = "0.4.0"
[workspace.dependencies.libloading] [workspace.dependencies.libloading]
version = "0.8.6" version = "0.9.0"
# Validating urls in config, was already a transitive dependency # Validating urls in config, was already a transitive dependency
[workspace.dependencies.url] [workspace.dependencies.url]
@@ -298,7 +298,7 @@ default-features = false
features = ["env", "toml"] features = ["env", "toml"]
[workspace.dependencies.hickory-resolver] [workspace.dependencies.hickory-resolver]
version = "0.25.1" version = "0.25.2"
default-features = false default-features = false
features = [ features = [
"serde", "serde",
@@ -307,9 +307,14 @@ features = [
] ]
# Used for conduwuit::Error type # Used for conduwuit::Error type
[workspace.dependencies.thiserror] [workspace.dependencies.snafu]
version = "2.0.12" version = "0.8"
default-features = false default-features = false
features = ["std", "rust_1_81"]
# Used for macro name generation
[workspace.dependencies.paste]
version = "1.0"
# Used when hashing the state # Used when hashing the state
[workspace.dependencies.ring] [workspace.dependencies.ring]
@@ -342,7 +347,8 @@ version = "0.1.2"
# Used for matrix spec type definitions and helpers # Used for matrix spec type definitions and helpers
[workspace.dependencies.ruma] [workspace.dependencies.ruma]
git = "https://forgejo.ellis.link/continuwuation/ruwuma" git = "https://forgejo.ellis.link/continuwuation/ruwuma"
rev = "b496b7f38d517149361a882e75d3fd4faf210441" #branch = "conduwuit-changes"
rev = "e087ff15888156942ca2ffe6097d1b4c3fd27628"
features = [ features = [
"compat", "compat",
"rand", "rand",
@@ -424,7 +430,7 @@ features = ["http", "grpc-tonic", "trace", "logs", "metrics"]
# optional sentry metrics for crash/panic reporting # optional sentry metrics for crash/panic reporting
[workspace.dependencies.sentry] [workspace.dependencies.sentry]
version = "0.45.0" version = "0.46.0"
default-features = false default-features = false
features = [ features = [
"backtrace", "backtrace",
@@ -440,9 +446,9 @@ features = [
] ]
[workspace.dependencies.sentry-tracing] [workspace.dependencies.sentry-tracing]
version = "0.45.0" version = "0.46.0"
[workspace.dependencies.sentry-tower] [workspace.dependencies.sentry-tower]
version = "0.45.0" version = "0.46.0"
# jemalloc usage # jemalloc usage
[workspace.dependencies.tikv-jemalloc-sys] [workspace.dependencies.tikv-jemalloc-sys]
@@ -471,7 +477,7 @@ features = ["use_std"]
version = "0.5" version = "0.5"
[workspace.dependencies.nix] [workspace.dependencies.nix]
version = "0.30.1" version = "0.31.0"
default-features = false default-features = false
features = ["resource"] features = ["resource"]
@@ -553,7 +559,7 @@ version = "0.7.5"
version = "1.0.1" version = "1.0.1"
[workspace.dependencies.askama] [workspace.dependencies.askama]
version = "0.14.0" version = "0.15.0"
# #
# Patches # Patches
+1
View File
@@ -0,0 +1 @@
Removed non-compliant nor functional room alias lookups over federation. Contributed by @nex
+1
View File
@@ -0,0 +1 @@
Outgoing presence is now disabled by default, and the config option documentation has been adjusted to more accurately represent the weight of presence, typing indicators, and read receipts. Contributed by @nex.
+1
View File
@@ -0,0 +1 @@
Removed ability to set rocksdb as read only. Doing so would cause unintentional and buggy behaviour. Contributed by @Terryiscool160.
+1
View File
@@ -0,0 +1 @@
Fixed a startup crash in the sender service if we can't detect the number of CPU cores, even if the `sender_workers' config option is set correctly. Contributed by @katie.
@@ -0,0 +1 @@
Updated `list-backups` admin command to output one backup per line.
+27 -20
View File
@@ -1056,14 +1056,6 @@
# #
#rocksdb_repair = false #rocksdb_repair = false
# This item is undocumented. Please contribute documentation for it.
#
#rocksdb_read_only = false
# This item is undocumented. Please contribute documentation for it.
#
#rocksdb_secondary = false
# Enables idle CPU priority for compaction thread. This is not enabled by # Enables idle CPU priority for compaction thread. This is not enabled by
# default to prevent compaction from falling too far behind on busy # default to prevent compaction from falling too far behind on busy
# systems. # systems.
@@ -1120,27 +1112,34 @@
# Allow local (your server only) presence updates/requests. # Allow local (your server only) presence updates/requests.
# #
# Note that presence on continuwuity is very fast unlike Synapse's. If # Local presence must be enabled for outgoing presence to function.
# using outgoing presence, this MUST be enabled. #
# Note that local presence is not as heavy on the CPU as federated
# presence, but will still become more expensive the more local users you
# have.
# #
#allow_local_presence = true #allow_local_presence = true
# Allow incoming federated presence updates/requests. # Allow incoming federated presence updates.
# #
# This option receives presence updates from other servers, but does not # This option enables processing inbound presence updates from other
# send any unless `allow_outgoing_presence` is true. Note that presence on # servers. Without it, remote users will appear as if they are always
# continuwuity is very fast unlike Synapse's. # offline to your local users. This does not affect typing indicators or
# read receipts.
# #
#allow_incoming_presence = true #allow_incoming_presence = true
# Allow outgoing presence updates/requests. # Allow outgoing presence updates/requests.
# #
# This option sends presence updates to other servers, but does not # This option sends presence updates to other servers, and requires that
# receive any unless `allow_incoming_presence` is true. Note that presence # `allow_local_presence` is also enabled.
# on continuwuity is very fast unlike Synapse's. If using outgoing
# presence, you MUST enable `allow_local_presence` as well.
# #
#allow_outgoing_presence = true # Note that outgoing presence is very heavy on the CPU and network, and
# will typically cause extreme strain and slowdowns for no real benefit.
# There are only a few clients that even implement presence, so you
# probably don't want to enable this.
#
#allow_outgoing_presence = false
# How many seconds without presence updates before you become idle. # How many seconds without presence updates before you become idle.
# Defaults to 5 minutes. # Defaults to 5 minutes.
@@ -1174,6 +1173,10 @@
# Allow sending read receipts to remote servers. # Allow sending read receipts to remote servers.
# #
# Note that sending read receipts to remote servers in large rooms with
# lots of other homeservers may cause additional strain on the CPU and
# network.
#
#allow_outgoing_read_receipts = true #allow_outgoing_read_receipts = true
# Allow local typing updates. # Allow local typing updates.
@@ -1185,6 +1188,10 @@
# Allow outgoing typing updates to federation. # Allow outgoing typing updates to federation.
# #
# Note that sending typing indicators to remote servers in large rooms
# with lots of other homeservers may cause additional strain on the CPU
# and network.
#
#allow_outgoing_typing = true #allow_outgoing_typing = true
# Allow incoming typing updates from federation. # Allow incoming typing updates from federation.
@@ -1318,7 +1325,7 @@
# sender user's server name, inbound federation X-Matrix origin, and # sender user's server name, inbound federation X-Matrix origin, and
# outbound federation handler. # outbound federation handler.
# #
# You can set this to ["*"] to block all servers by default, and then # You can set this to [".*"] to block all servers by default, and then
# use `allowed_remote_server_names` to allow only specific servers. # use `allowed_remote_server_names` to allow only specific servers.
# #
# example: ["badserver\\.tld$", "badphrase", "19dollarfortnitecards"] # example: ["badserver\\.tld$", "badphrase", "19dollarfortnitecards"]
+1 -1
View File
@@ -52,7 +52,7 @@ ENV BINSTALL_VERSION=1.17.5
# renovate: datasource=github-releases depName=psastras/sbom-rs # renovate: datasource=github-releases depName=psastras/sbom-rs
ENV CARGO_SBOM_VERSION=0.9.1 ENV CARGO_SBOM_VERSION=0.9.1
# renovate: datasource=crate depName=lddtree # renovate: datasource=crate depName=lddtree
ENV LDDTREE_VERSION=0.4.0 ENV LDDTREE_VERSION=0.5.0
# renovate: datasource=crate depName=timelord-cli # renovate: datasource=crate depName=timelord-cli
ENV TIMELORD_VERSION=3.0.1 ENV TIMELORD_VERSION=3.0.1
+1 -1
View File
@@ -22,7 +22,7 @@ ENV BINSTALL_VERSION=1.17.5
# renovate: datasource=github-releases depName=psastras/sbom-rs # renovate: datasource=github-releases depName=psastras/sbom-rs
ENV CARGO_SBOM_VERSION=0.9.1 ENV CARGO_SBOM_VERSION=0.9.1
# renovate: datasource=crate depName=lddtree # renovate: datasource=crate depName=lddtree
ENV LDDTREE_VERSION=0.4.0 ENV LDDTREE_VERSION=0.5.0
# Install unpackaged tools # Install unpackaged tools
RUN <<EOF RUN <<EOF
+41 -2
View File
@@ -137,7 +137,7 @@ By default, all routes should be forwarded to Livekit with the exception of the
# for lk-jwt-service # for lk-jwt-service
@lk-jwt-service path /sfu/get* /healthz* /get_token* @lk-jwt-service path /sfu/get* /healthz* /get_token*
route @lk-jwt-service { route @lk-jwt-service {
reverse_proxy 127.0.0.1:8080 reverse_proxy 127.0.0.1:8081
} }
# for livekit # for livekit
@@ -146,6 +146,46 @@ By default, all routes should be forwarded to Livekit with the exception of the
``` ```
</details> </details>
<details>
<summary>Example nginx config</summary>
```
server {
server_name matrix-rtc.example.com;
# for lk-jwt-service
location ~ ^/(sfu/get|healthz|get_token) {
proxy_pass http://127.0.0.1:8081$request_uri;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_buffering off;
}
# for livekit
location / {
proxy_pass http://127.0.0.1:7880$request_uri;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_buffering off;
# websocket
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
```
Note that for websockets to work, you need to have this somewhere outside your server block:
```
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
```
</details>
<details> <details>
<summary>Example traefik router</summary> <summary>Example traefik router</summary>
``` ```
@@ -226,4 +266,3 @@ turn:
- [Synapse documentation](https://github.com/element-hq/element-call/blob/livekit/docs/self-hosting.md) - [Synapse documentation](https://github.com/element-hq/element-call/blob/livekit/docs/self-hosting.md)
- [Community guide](https://tomfos.tr/matrix/livekit/) - [Community guide](https://tomfos.tr/matrix/livekit/)
- [Community guide](https://blog.kimiblock.top/2024/12/24/hosting-element-call/) - [Community guide](https://blog.kimiblock.top/2024/12/24/hosting-element-call/)
-
+2
View File
@@ -3,3 +3,5 @@
Continuwuity currently does not provide FreeBSD builds or FreeBSD packaging. However, Continuwuity does build and work on FreeBSD using the system-provided RocksDB. Continuwuity currently does not provide FreeBSD builds or FreeBSD packaging. However, Continuwuity does build and work on FreeBSD using the system-provided RocksDB.
Contributions to get Continuwuity packaged for FreeBSD are welcome. Contributions to get Continuwuity packaged for FreeBSD are welcome.
Please join our [Continuwuity BSD](https://matrix.to/#/%23bsd:continuwuity.org) community room.
+103 -1
View File
@@ -1,7 +1,109 @@
# Continuwuity for Kubernetes # Continuwuity for Kubernetes
Continuwuity doesn't support horizontal scalability or distributed loading Continuwuity doesn't support horizontal scalability or distributed loading
natively. However, [a community-maintained Helm Chart is available here to run natively. However, a deployment in Kubernetes is very similar to the docker
setup. This is because Continuwuity can be fully configured using environment
variables. A sample StatefulSet is shared below. The only thing missing is
a PVC definition (named `continuwuity-data`) for the volume mounted to
the StatefulSet, an Ingress resources to point your webserver to the
Continuwuity Pods, and a Service resource (targeting `app.kubernetes.io/name: continuwuity`)
to glue the Ingress and Pod together.
Carefully go through the `env` section and add, change, and remove any env vars you like using the [Configuration reference](https://continuwuity.org/reference/config.html)
```yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: continuwuity
namespace: matrix
labels:
app.kubernetes.io/name: continuwuity
spec:
replicas: 1
serviceName: continuwuity
podManagementPolicy: Parallel
selector:
matchLabels:
app.kubernetes.io/name: continuwuity
template:
metadata:
labels:
app.kubernetes.io/name: continuwuity
spec:
securityContext:
sysctls:
- name: net.ipv4.ip_unprivileged_port_start
value: "0"
containers:
- name: continuwuity
# use a sha hash <3
image: forgejo.ellis.link/continuwuation/continuwuity:latest
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
volumeMounts:
- mountPath: /data
name: data
subPath: data
securityContext:
capabilities:
add:
- NET_BIND_SERVICE
env:
- name: TOKIO_WORKER_THREADS
value: "2"
- name: CONTINUWUITY_SERVER_NAME
value: "example.com"
- name: CONTINUWUITY_DATABASE_PATH
value: "/data/db"
- name: CONTINUWUITY_DATABASE_BACKEND
value: "rocksdb"
- name: CONTINUWUITY_PORT
value: "80"
- name: CONTINUWUITY_MAX_REQUEST_SIZE
value: "20000000"
- name: CONTINUWUITY_ALLOW_FEDERATION
value: "true"
- name: CONTINUWUITY_TRUSTED_SERVERS
value: '["matrix.org"]'
- name: CONTINUWUITY_ADDRESS
value: "0.0.0.0"
- name: CONTINUWUITY_ROCKSDB_PARALLELISM_THREADS
value: "1"
- name: CONTINUWUITY_WELL_KNOWN__SERVER
value: "matrix.example.com:443"
- name: CONTINUWUITY_WELL_KNOWN__CLIENT
value: "https://matrix.example.com"
- name: CONTINUWUITY_ALLOW_REGISTRATION
value: "false"
- name: RUST_LOG
value: info
readinessProbe:
httpGet:
path: /_matrix/federation/v1/version
port: http
periodSeconds: 4
failureThreshold: 5
resources:
# Continuwuity might use quite some RAM :3
requests:
cpu: "2"
memory: "512Mi"
limits:
cpu: "4"
memory: "2048Mi"
volumes:
- name: data
persistentVolumeClaim:
claimName: continuwuity-data
```
---
Apart from manually configuring the containers,
[a community-maintained Helm Chart is available here to run
conduwuit on Kubernetes](https://gitlab.cronce.io/charts/conduwuit) conduwuit on Kubernetes](https://gitlab.cronce.io/charts/conduwuit)
This should be compatible with Continuwuity, but you will need to change the image reference. This should be compatible with Continuwuity, but you will need to change the image reference.
+20 -5
View File
@@ -1,13 +1,28 @@
# Troubleshooting Continuwuity # Troubleshooting Continuwuity
> **Docker users ⚠️** :::warning{title="Docker users:"}
> Docker can be difficult to use and debug. It's common for Docker
> Docker can be difficult to use and debug. It's common for Docker misconfigurations to cause issues, particularly with networking and permissions.
> misconfigurations to cause issues, particularly with networking and permissions. Please check that your issues are not due to problems with your Docker setup.
> Please check that your issues are not due to problems with your Docker setup. :::
## Continuwuity and Matrix issues ## Continuwuity and Matrix issues
### Slow joins to rooms
Some slowness is to be expected if you're the first person on your homserver to join a room (which will
always be the case for single-user homeservers). In this situation, your homeserver has to verify the signatures of
all of the state events sent by other servers before your join. To make this process as fast as possible, make sure you have
multiple fast, trusted servers listed in `trusted_servers` in your configuration, and ensure
`query_trusted_key_servers_first_on_join` is set to true (the default).
If you need suggestions for trusted servers, ask in the Continuwuity main room.
However, _very_ slow joins, especially to rooms with only a few users in them or rooms created by another user
on your homeserver, may be caused by [issue !779](https://forgejo.ellis.link/continuwuation/continuwuity/issues/779),
which is a longstanding bug with synchronizing room joins to clients. In this situation, you did succeed in joining the room, but
the bug caused your homeserver to forget to tell your client. **To fix this, clear your client's cache.** Both Element and Cinny
have a button to clear their cache in the "About" section of their settings.
### Lost access to admin room ### Lost access to admin room
You can reinvite yourself to the admin room through the following methods: You can reinvite yourself to the admin room through the following methods:
+13 -2
View File
@@ -77,7 +77,12 @@ rec {
craneLib.buildDepsOnly ( craneLib.buildDepsOnly (
(commonAttrs commonAttrsArgs) (commonAttrs commonAttrsArgs)
// { // {
env = uwuenv.buildDepsOnlyEnv // (makeRocksDBEnv { inherit rocksdb; }); env = uwuenv.buildDepsOnlyEnv
// (makeRocksDBEnv { inherit rocksdb; })
// {
# required since we started using unstable reqwest apparently ... otherwise the all-features build will fail
RUSTFLAGS = "--cfg reqwest_unstable";
};
inherit (features) cargoExtraArgs; inherit (features) cargoExtraArgs;
} }
@@ -102,7 +107,13 @@ rec {
''; '';
cargoArtifacts = deps; cargoArtifacts = deps;
doCheck = true; doCheck = true;
env = uwuenv.buildPackageEnv // rocksdbEnv; env =
uwuenv.buildPackageEnv
// rocksdbEnv
// {
# required since we started using unstable reqwest apparently ... otherwise the all-features build will fail
RUSTFLAGS = "--cfg reqwest_unstable";
};
passthru.env = uwuenv.buildPackageEnv // rocksdbEnv; passthru.env = uwuenv.buildPackageEnv // rocksdbEnv;
meta.mainProgram = crateInfo.pname; meta.mainProgram = crateInfo.pname;
inherit (features) cargoExtraArgs; inherit (features) cargoExtraArgs;
+14
View File
@@ -1,6 +1,7 @@
{ {
"$schema": "https://docs.renovatebot.com/renovate-schema.json", "$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended", "replacements:all"], "extends": ["config:recommended", "replacements:all"],
"dependencyDashboard": true,
"osvVulnerabilityAlerts": true, "osvVulnerabilityAlerts": true,
"lockFileMaintenance": { "lockFileMaintenance": {
"enabled": true, "enabled": true,
@@ -57,12 +58,25 @@
"matchUpdateTypes": ["minor", "patch"], "matchUpdateTypes": ["minor", "patch"],
"groupName": "github-actions-non-major" "groupName": "github-actions-non-major"
}, },
{
"description": "Batch patch-level Node.js dependency updates",
"matchManagers": ["npm"],
"matchUpdateTypes": ["patch"],
"groupName": "node-patch-updates"
},
{ {
"description": "Pin forgejo artifact actions to prevent breaking changes", "description": "Pin forgejo artifact actions to prevent breaking changes",
"matchManagers": ["github-actions"], "matchManagers": ["github-actions"],
"matchPackageNames": ["forgejo/upload-artifact", "forgejo/download-artifact"], "matchPackageNames": ["forgejo/upload-artifact", "forgejo/download-artifact"],
"enabled": false "enabled": false
}, },
{
"description": "Auto-merge crate-ci/typos minor updates",
"matchPackageNames": ["crate-ci/typos"],
"matchUpdateTypes": ["minor", "patch"],
"automerge": true,
"automergeStrategy": "fast-forward"
},
{ {
"description": "Auto-merge renovatebot docker image updates", "description": "Auto-merge renovatebot docker image updates",
"matchDatasources": ["docker"], "matchDatasources": ["docker"],
+3 -15
View File
@@ -89,13 +89,7 @@ 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 match self.services.rooms.alias.resolve_alias(room_alias).await {
.services
.rooms
.alias
.resolve_alias(room_alias, None)
.await
{
| Ok((room_id, servers)) => { | Ok((room_id, servers)) => {
debug!( debug!(
%room_id, %room_id,
@@ -235,7 +229,7 @@ async fn ban_list_of_rooms(&self) -> Result {
.services .services
.rooms .rooms
.alias .alias
.resolve_alias(room_alias, None) .resolve_alias(room_alias)
.await .await
{ {
| Ok((room_id, servers)) => { | Ok((room_id, servers)) => {
@@ -388,13 +382,7 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
room ID over federation" room ID over federation"
); );
match self match self.services.rooms.alias.resolve_alias(room_alias).await {
.services
.rooms
.alias
.resolve_alias(room_alias, None)
.await
{
| Ok((room_id, servers)) => { | Ok((room_id, servers)) => {
debug!( debug!(
%room_id, %room_id,
+1 -1
View File
@@ -86,7 +86,7 @@ pub(super) async fn list_backups(&self) -> Result {
.db .db
.backup_list()? .backup_list()?
.try_stream() .try_stream()
.try_for_each(|result| write!(self, "{result}")) .try_for_each(|result| writeln!(self, "{result}"))
.await .await
} }
+14 -7
View File
@@ -3,7 +3,7 @@ use std::fmt::Write;
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{ use conduwuit::{
Err, Error, Event, Result, debug_info, err, error, info, Err, Event, Result, debug_info, err, error, info,
matrix::pdu::PduBuilder, matrix::pdu::PduBuilder,
utils::{self, ReadyExt, stream::BroadbandExt}, utils::{self, ReadyExt, stream::BroadbandExt},
warn, warn,
@@ -252,6 +252,13 @@ pub(crate) async fn register_route(
} }
} }
// Don't allow registration with user IDs that aren't local
if !services.globals.user_is_local(&user_id) {
return Err!(Request(InvalidUsername(
"Username {body_username} is not local to this server"
)));
}
user_id user_id
}, },
| Err(e) => { | Err(e) => {
@@ -380,7 +387,7 @@ pub(crate) async fn register_route(
) )
.await?; .await?;
if !worked { if !worked {
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
} }
// Success! // Success!
}, },
@@ -394,7 +401,7 @@ pub(crate) async fn register_route(
&uiaainfo, &uiaainfo,
json, json,
); );
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
}, },
| _ => { | _ => {
return Err!(Request(NotJson("JSON body is not valid"))); return Err!(Request(NotJson("JSON body is not valid")));
@@ -654,7 +661,7 @@ pub(crate) async fn change_password_route(
.await?; .await?;
if !worked { if !worked {
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
} }
// Success! // Success!
@@ -666,7 +673,7 @@ pub(crate) async fn change_password_route(
.uiaa .uiaa
.create(sender_user, body.sender_device(), &uiaainfo, json); .create(sender_user, body.sender_device(), &uiaainfo, json);
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
}, },
| _ => { | _ => {
return Err!(Request(NotJson("JSON body is not valid"))); return Err!(Request(NotJson("JSON body is not valid")));
@@ -784,7 +791,7 @@ pub(crate) async fn deactivate_route(
.await?; .await?;
if !worked { if !worked {
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
} }
// Success! // Success!
}, },
@@ -795,7 +802,7 @@ pub(crate) async fn deactivate_route(
.uiaa .uiaa
.create(sender_user, body.sender_device(), &uiaainfo, json); .create(sender_user, body.sender_device(), &uiaainfo, json);
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
}, },
| _ => { | _ => {
return Err!(Request(NotJson("JSON body is not valid"))); return Err!(Request(NotJson("JSON body is not valid")));
+3 -65
View File
@@ -1,12 +1,6 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{Err, Result, debug}; use conduwuit::{Err, Result};
use conduwuit_service::Services; use ruma::api::client::alias::{create_alias, delete_alias, get_alias};
use futures::StreamExt;
use rand::seq::SliceRandom;
use ruma::{
OwnedServerName, RoomAliasId, RoomId,
api::client::alias::{create_alias, delete_alias, get_alias},
};
use crate::Ruma; use crate::Ruma;
@@ -96,65 +90,9 @@ pub(crate) async fn get_alias_route(
) -> Result<get_alias::v3::Response> { ) -> Result<get_alias::v3::Response> {
let room_alias = body.body.room_alias; let room_alias = body.body.room_alias;
let Ok((room_id, servers)) = services.rooms.alias.resolve_alias(&room_alias, None).await let Ok((room_id, servers)) = services.rooms.alias.resolve_alias(&room_alias).await else {
else {
return Err!(Request(NotFound("Room with alias not found."))); return Err!(Request(NotFound("Room with alias not found.")));
}; };
let servers = room_available_servers(&services, &room_id, &room_alias, servers).await;
debug!(%room_alias, %room_id, "available servers: {servers:?}");
Ok(get_alias::v3::Response::new(room_id, servers)) Ok(get_alias::v3::Response::new(room_id, servers))
} }
async fn room_available_servers(
services: &Services,
room_id: &RoomId,
room_alias: &RoomAliasId,
pre_servers: Vec<OwnedServerName>,
) -> Vec<OwnedServerName> {
// find active servers in room state cache to suggest
let mut servers: Vec<OwnedServerName> = services
.rooms
.state_cache
.room_servers(room_id)
.map(ToOwned::to_owned)
.collect()
.await;
// push any servers we want in the list already (e.g. responded remote alias
// servers, room alias server itself)
servers.extend(pre_servers);
servers.sort_unstable();
servers.dedup();
// shuffle list of servers randomly after sort and dedupe
servers.shuffle(&mut rand::thread_rng());
// insert our server as the very first choice if in list, else check if we can
// prefer the room alias server first
match servers
.iter()
.position(|server_name| services.globals.server_is_ours(server_name))
{
| Some(server_index) => {
servers.swap_remove(server_index);
servers.insert(0, services.globals.server_name().to_owned());
},
| _ => {
match servers
.iter()
.position(|server| server == room_alias.server_name())
{
| Some(alias_server_index) => {
servers.swap_remove(alias_server_index);
servers.insert(0, room_alias.server_name().into());
},
| _ => {},
}
},
}
servers
}
+4 -4
View File
@@ -1,6 +1,6 @@
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{Err, Error, Result, debug, err, utils}; use conduwuit::{Err, Result, debug, err, utils};
use futures::StreamExt; use futures::StreamExt;
use ruma::{ use ruma::{
MilliSecondsSinceUnixEpoch, OwnedDeviceId, MilliSecondsSinceUnixEpoch, OwnedDeviceId,
@@ -232,7 +232,7 @@ pub(crate) async fn delete_devices_route(
.await?; .await?;
if !worked { if !worked {
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
} }
// Success! // Success!
}, },
@@ -243,10 +243,10 @@ pub(crate) async fn delete_devices_route(
.uiaa .uiaa
.create(sender_user, sender_device, &uiaainfo, json); .create(sender_user, sender_device, &uiaainfo, json);
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
}, },
| _ => { | _ => {
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json.")); return Err!(BadRequest(ErrorKind::NotJson, "Not json."));
}, },
}, },
} }
+6 -6
View File
@@ -5,7 +5,7 @@ use std::{
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Err, Error, Result, debug, debug_warn, err, Err, Result, debug, debug_warn, err,
result::NotFound, result::NotFound,
utils, utils,
utils::{IterStream, stream::WidebandExt}, utils::{IterStream, stream::WidebandExt},
@@ -215,7 +215,7 @@ pub(crate) async fn upload_signing_keys_route(
.await?; .await?;
if !worked { if !worked {
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
} }
// Success! // Success!
}, },
@@ -226,10 +226,10 @@ pub(crate) async fn upload_signing_keys_route(
.uiaa .uiaa
.create(sender_user, sender_device, &uiaainfo, json); .create(sender_user, sender_device, &uiaainfo, json);
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
}, },
| _ => { | _ => {
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json.")); return Err!(BadRequest(ErrorKind::NotJson, "Not json."));
}, },
}, },
} }
@@ -396,12 +396,12 @@ pub(crate) async fn get_key_changes_route(
let from = body let from = body
.from .from
.parse() .parse()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `from`."))?; .map_err(|_| err!(BadRequest(ErrorKind::InvalidParam, "Invalid `from`.")))?;
let to = body let to = body
.to .to
.parse() .parse()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `to`."))?; .map_err(|_| err!(BadRequest(ErrorKind::InvalidParam, "Invalid `to`.")))?;
device_list_updates.extend( device_list_updates.extend(
services services
+2 -2
View File
@@ -3,7 +3,7 @@ use std::time::Duration;
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{ use conduwuit::{
Err, Result, err, Err, Result, err, error,
utils::{self, content_disposition::make_content_disposition, math::ruma_from_usize}, utils::{self, content_disposition::make_content_disposition, math::ruma_from_usize},
}; };
use conduwuit_service::{ use conduwuit_service::{
@@ -69,7 +69,7 @@ pub(crate) async fn create_content_route(
.create(mxc, Some(user), Some(&content_disposition), content_type, &body.file) .create(mxc, Some(user), Some(&content_disposition), content_type, &body.file)
.await .await
{ {
err!("Failed to save uploaded media: {e}"); error!("Failed to save uploaded media: {e}");
return Err!(Request(Unknown("Failed to save uploaded media"))); return Err!(Request(Unknown("Failed to save uploaded media")));
} }
+1 -5
View File
@@ -198,11 +198,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
(servers, room_id) (servers, room_id)
}, },
| Err(room_alias) => { | Err(room_alias) => {
let (room_id, mut servers) = services let (room_id, mut servers) = services.rooms.alias.resolve_alias(&room_alias).await?;
.rooms
.alias
.resolve_alias(&room_alias, Some(body.via.clone()))
.await?;
banned_room_check( banned_room_check(
&services, &services,
+1 -5
View File
@@ -102,11 +102,7 @@ pub(crate) async fn knock_room_route(
(servers, room_id) (servers, room_id)
}, },
| Err(room_alias) => { | Err(room_alias) => {
let (room_id, mut servers) = services let (room_id, mut servers) = services.rooms.alias.resolve_alias(&room_alias).await?;
.rooms
.alias
.resolve_alias(&room_alias, Some(body.via.clone()))
.await?;
banned_room_check( banned_room_check(
&services, &services,
+3 -3
View File
@@ -1,7 +1,7 @@
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{ use conduwuit::{
Err, Error, Result, at, debug_warn, Err, Result, at, debug_warn,
matrix::{ matrix::{
event::{Event, Matches}, event::{Event, Matches},
pdu::PduCount, pdu::PduCount,
@@ -322,7 +322,7 @@ where
if server_ignored { if server_ignored {
// the sender's server is ignored, so ignore this event // the sender's server is ignored, so ignore this event
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::SenderIgnored { sender: None }, ErrorKind::SenderIgnored { sender: None },
"The sender's server is ignored by this server.", "The sender's server is ignored by this server.",
)); ));
@@ -331,7 +331,7 @@ where
if user_ignored && !services.config.send_messages_from_ignored_users_to_client { if user_ignored && !services.config.send_messages_from_ignored_users_to_client {
// the recipient of this PDU has the sender ignored, and we're not // the recipient of this PDU has the sender ignored, and we're not
// configured to send ignored messages to clients // configured to send ignored messages to clients
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::SenderIgnored { sender: Some(event.sender().to_owned()) }, ErrorKind::SenderIgnored { sender: Some(event.sender().to_owned()) },
"You have ignored this sender.", "You have ignored this sender.",
)); ));
+16 -16
View File
@@ -1,5 +1,5 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{Err, Error, Result, err}; use conduwuit::{Err, Result, err};
use conduwuit_service::Services; use conduwuit_service::Services;
use ruma::{ use ruma::{
CanonicalJsonObject, CanonicalJsonValue, CanonicalJsonObject, CanonicalJsonValue,
@@ -243,27 +243,27 @@ pub(crate) async fn set_pushrule_route(
body.before.as_deref(), body.before.as_deref(),
) { ) {
let err = match error { let err = match error {
| InsertPushRuleError::ServerDefaultRuleId => Error::BadRequest( | InsertPushRuleError::ServerDefaultRuleId => err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Rule IDs starting with a dot are reserved for server-default rules.", "Rule IDs starting with a dot are reserved for server-default rules.",
), )),
| InsertPushRuleError::InvalidRuleId => Error::BadRequest( | InsertPushRuleError::InvalidRuleId => err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Rule ID containing invalid characters.", "Rule ID containing invalid characters.",
), )),
| InsertPushRuleError::RelativeToServerDefaultRule => Error::BadRequest( | InsertPushRuleError::RelativeToServerDefaultRule => err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Can't place a push rule relatively to a server-default rule.", "Can't place a push rule relatively to a server-default rule.",
), )),
| InsertPushRuleError::UnknownRuleId => Error::BadRequest( | InsertPushRuleError::UnknownRuleId => err!(BadRequest(
ErrorKind::NotFound, ErrorKind::NotFound,
"The before or after rule could not be found.", "The before or after rule could not be found.",
), )),
| InsertPushRuleError::BeforeHigherThanAfter => Error::BadRequest( | InsertPushRuleError::BeforeHigherThanAfter => err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"The before rule has a higher priority than the after rule.", "The before rule has a higher priority than the after rule.",
), )),
| _ => Error::BadRequest(ErrorKind::InvalidParam, "Invalid data."), | _ => err!(BadRequest(ErrorKind::InvalidParam, "Invalid data.")),
}; };
return Err(err); return Err(err);
@@ -433,13 +433,13 @@ pub(crate) async fn delete_pushrule_route(
.remove(body.kind.clone(), &body.rule_id) .remove(body.kind.clone(), &body.rule_id)
{ {
let err = match error { let err = match error {
| RemovePushRuleError::ServerDefault => Error::BadRequest( | RemovePushRuleError::ServerDefault => err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Cannot delete a server-default pushrule.", "Cannot delete a server-default pushrule.",
), )),
| RemovePushRuleError::NotFound => | RemovePushRuleError::NotFound =>
Error::BadRequest(ErrorKind::NotFound, "Push rule not found."), err!(BadRequest(ErrorKind::NotFound, "Push rule not found.")),
| _ => Error::BadRequest(ErrorKind::InvalidParam, "Invalid data."), | _ => err!(BadRequest(ErrorKind::InvalidParam, "Invalid data.")),
}; };
return Err(err); return Err(err);
+1 -2
View File
@@ -4,7 +4,6 @@ use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{Err, Event, Result, debug_info, info, matrix::pdu::PduEvent, utils::ReadyExt}; use conduwuit::{Err, Event, Result, debug_info, info, matrix::pdu::PduEvent, utils::ReadyExt};
use conduwuit_service::Services; use conduwuit_service::Services;
use rand::Rng;
use ruma::{ use ruma::{
EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId, EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId,
api::client::{ api::client::{
@@ -244,7 +243,7 @@ fn build_report(report: Report) -> RoomMessageEventContent {
/// 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.
async fn delay_response() { async fn delay_response() {
let time_to_wait = rand::thread_rng().gen_range(2..5); let time_to_wait = rand::random_range(2..5);
debug_info!( debug_info!(
"Got successful /report request, waiting {time_to_wait} seconds before sending \ "Got successful /report request, waiting {time_to_wait} seconds before sending \
successful response." successful response."
+6 -6
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, RoomVersion, debug, err, info, Err, Event, Result, RoomVersion, debug, err, info,
matrix::{StateKey, pdu::PduBuilder}, matrix::{StateKey, pdu::PduBuilder},
}; };
use futures::{FutureExt, StreamExt}; use futures::{FutureExt, StreamExt};
@@ -58,7 +58,7 @@ pub(crate) async fn upgrade_room_route(
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if !services.server.supported_room_version(&body.new_version) { if !services.server.supported_room_version(&body.new_version) {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::UnsupportedRoomVersion, ErrorKind::UnsupportedRoomVersion,
"This server does not support that room version.", "This server does not support that room version.",
)); ));
@@ -170,7 +170,7 @@ pub(crate) async fn upgrade_room_route(
"creator".into(), "creator".into(),
json!(&sender_user).try_into().map_err(|e| { json!(&sender_user).try_into().map_err(|e| {
info!("Error forming creation event: {e}"); info!("Error forming creation event: {e}");
Error::BadRequest(ErrorKind::BadJson, "Error forming creation event") err!(BadRequest(ErrorKind::BadJson, "Error forming creation event"))
})?, })?,
); );
}, },
@@ -186,13 +186,13 @@ pub(crate) async fn upgrade_room_route(
"room_version".into(), "room_version".into(),
json!(&body.new_version) json!(&body.new_version)
.try_into() .try_into()
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?, .map_err(|_| err!(BadRequest(ErrorKind::BadJson, "Error forming creation event")))?,
); );
create_event_content.insert( create_event_content.insert(
"predecessor".into(), "predecessor".into(),
json!(predecessor) json!(predecessor)
.try_into() .try_into()
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?, .map_err(|_| err!(BadRequest(ErrorKind::BadJson, "Error forming creation event")))?,
); );
// Validate creation event content // Validate creation event content
@@ -203,7 +203,7 @@ pub(crate) async fn upgrade_room_route(
) )
.is_err() .is_err()
{ {
return Err(Error::BadRequest(ErrorKind::BadJson, "Error forming creation event")); return Err!(BadRequest(ErrorKind::BadJson, "Error forming creation event"));
} }
let create_event_id = services let create_event_id = services
+4 -4
View File
@@ -3,7 +3,7 @@ use std::time::Duration;
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{ use conduwuit::{
Err, Error, Result, debug, err, info, Err, Result, debug, err, info,
utils::{self, ReadyExt, hash}, utils::{self, ReadyExt, hash},
warn, warn,
}; };
@@ -191,7 +191,7 @@ pub(crate) async fn handle_login(
} }
if services.users.is_locked(&user_id).await? { if services.users.is_locked(&user_id).await? {
return Err(Error::BadRequest(ErrorKind::UserLocked, "This account has been locked.")); return Err!(BadRequest(ErrorKind::UserLocked, "This account has been locked."));
} }
if services.users.is_login_disabled(&user_id).await { if services.users.is_login_disabled(&user_id).await {
@@ -390,7 +390,7 @@ pub(crate) async fn login_token_route(
.await?; .await?;
if !worked { if !worked {
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
} }
// Success! // Success!
@@ -402,7 +402,7 @@ pub(crate) async fn login_token_route(
.uiaa .uiaa
.create(sender_user, sender_device, &uiaainfo, json); .create(sender_user, sender_device, &uiaainfo, json);
return Err(Error::Uiaa(uiaainfo)); return Err!(Uiaa(uiaainfo));
}, },
| _ => { | _ => {
return Err!(Request(NotJson("No JSON body was sent when required."))); return Err!(Request(NotJson("No JSON body was sent when required.")));
+2 -2
View File
@@ -342,10 +342,10 @@ async fn allowed_to_send_state_event(
} }
for alias in aliases { for alias in aliases {
let (alias_room_id, _servers) = services let (alias_room_id, _) = services
.rooms .rooms
.alias .alias
.resolve_alias(&alias, None) .resolve_alias(&alias)
.await .await
.map_err(|e| { .map_err(|e| {
err!(Request(Unknown("Failed resolving alias \"{alias}\": {e}"))) err!(Request(Unknown("Failed resolving alias \"{alias}\": {e}")))
+2 -2
View File
@@ -1,7 +1,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use axum::extract::State; use axum::extract::State;
use conduwuit::{Error, Result}; use conduwuit::{Result, err};
use conduwuit_service::sending::EduBuf; use conduwuit_service::sending::EduBuf;
use futures::StreamExt; use futures::StreamExt;
use ruma::{ use ruma::{
@@ -66,7 +66,7 @@ pub(crate) async fn send_event_to_device_route(
let event = event let event = event
.deserialize_as() .deserialize_as()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid"))?; .map_err(|_| err!(BadRequest(ErrorKind::InvalidParam, "Event is invalid")))?;
match target_device_id_maybe { match target_device_id_maybe {
| DeviceIdOrAllDevices::DeviceId(target_device_id) => { | DeviceIdOrAllDevices::DeviceId(target_device_id) => {
+7 -10
View File
@@ -1,11 +1,8 @@
use axum::{Json, extract::State, response::IntoResponse}; use axum::{Json, extract::State, response::IntoResponse};
use conduwuit::{Error, Result}; use conduwuit::{Err, Result};
use ruma::api::client::{ use ruma::api::client::discovery::{
discovery::{ discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo},
discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo}, discover_support::{self, Contact},
discover_support::{self, Contact},
},
error::ErrorKind,
}; };
use crate::Ruma; use crate::Ruma;
@@ -19,7 +16,7 @@ pub(crate) async fn well_known_client(
) -> Result<discover_homeserver::Response> { ) -> Result<discover_homeserver::Response> {
let client_url = match services.config.well_known.client.as_ref() { let client_url = match services.config.well_known.client.as_ref() {
| Some(url) => url.to_string(), | Some(url) => url.to_string(),
| None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")), | None => return Err!(BadRequest(ErrorKind::NotFound, "Not found.")),
}; };
Ok(discover_homeserver::Response { Ok(discover_homeserver::Response {
@@ -88,7 +85,7 @@ pub(crate) async fn well_known_support(
if contacts.is_empty() && support_page.is_none() { if contacts.is_empty() && support_page.is_none() {
// No admin room, no configured contacts, and no support page // No admin room, no configured contacts, and no support page
return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")); return Err!(BadRequest(ErrorKind::NotFound, "Not found."));
} }
Ok(discover_support::Response { contacts, support_page }) Ok(discover_support::Response { contacts, support_page })
@@ -105,7 +102,7 @@ pub(crate) async fn syncv3_client_server_json(
| Some(url) => url.to_string(), | Some(url) => url.to_string(),
| None => match services.config.well_known.server.as_ref() { | None => match services.config.well_known.server.as_ref() {
| Some(url) => url.to_string(), | Some(url) => url.to_string(),
| None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")), | None => return Err!(BadRequest(ErrorKind::NotFound, "Not found.")),
}, },
}; };
+16 -16
View File
@@ -122,23 +122,23 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
// Ruma doesn't have support for multiple paths for a single endpoint yet, and these routes // Ruma doesn't have support for multiple paths for a single endpoint yet, and these routes
// share one Ruma request / response type pair with {get,send}_state_event_for_key_route // share one Ruma request / response type pair with {get,send}_state_event_for_key_route
.route( .route(
"/_matrix/client/r0/rooms/:room_id/state/:event_type", "/_matrix/client/r0/rooms/{room_id}/state/{event_type}",
get(client::get_state_events_for_empty_key_route) get(client::get_state_events_for_empty_key_route)
.put(client::send_state_event_for_empty_key_route), .put(client::send_state_event_for_empty_key_route),
) )
.route( .route(
"/_matrix/client/v3/rooms/:room_id/state/:event_type", "/_matrix/client/v3/rooms/{room_id}/state/{event_type}",
get(client::get_state_events_for_empty_key_route) get(client::get_state_events_for_empty_key_route)
.put(client::send_state_event_for_empty_key_route), .put(client::send_state_event_for_empty_key_route),
) )
// These two endpoints allow trailing slashes // These two endpoints allow trailing slashes
.route( .route(
"/_matrix/client/r0/rooms/:room_id/state/:event_type/", "/_matrix/client/r0/rooms/{room_id}/state/{event_type}/",
get(client::get_state_events_for_empty_key_route) get(client::get_state_events_for_empty_key_route)
.put(client::send_state_event_for_empty_key_route), .put(client::send_state_event_for_empty_key_route),
) )
.route( .route(
"/_matrix/client/v3/rooms/:room_id/state/:event_type/", "/_matrix/client/v3/rooms/{room_id}/state/{event_type}/",
get(client::get_state_events_for_empty_key_route) get(client::get_state_events_for_empty_key_route)
.put(client::send_state_event_for_empty_key_route), .put(client::send_state_event_for_empty_key_route),
) )
@@ -177,7 +177,7 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
.ruma_route(&client::get_mutual_rooms_route) .ruma_route(&client::get_mutual_rooms_route)
.ruma_route(&client::get_room_summary) .ruma_route(&client::get_room_summary)
.route( .route(
"/_matrix/client/unstable/im.nheko.summary/rooms/:room_id_or_alias/summary", "/_matrix/client/unstable/im.nheko.summary/rooms/{room_id_or_alias}/summary",
get(client::get_room_summary_legacy) get(client::get_room_summary_legacy)
) )
.ruma_route(&client::get_suspended_status) .ruma_route(&client::get_suspended_status)
@@ -196,7 +196,7 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
.ruma_route(&server::get_server_version_route) .ruma_route(&server::get_server_version_route)
.route("/_matrix/key/v2/server", get(server::get_server_keys_route)) .route("/_matrix/key/v2/server", get(server::get_server_keys_route))
.route( .route(
"/_matrix/key/v2/server/:key_id", "/_matrix/key/v2/server/{key_id}",
get(server::get_server_keys_deprecated_route), get(server::get_server_keys_deprecated_route),
) )
.ruma_route(&server::get_public_rooms_route) .ruma_route(&server::get_public_rooms_route)
@@ -232,9 +232,9 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
.route("/_continuwuity/local_user_count", get(client::conduwuit_local_user_count)); .route("/_continuwuity/local_user_count", get(client::conduwuit_local_user_count));
} else { } else {
router = router router = router
.route("/_matrix/federation/*path", any(federation_disabled)) .route("/_matrix/federation/{*path}", any(federation_disabled))
.route("/.well-known/matrix/server", any(federation_disabled)) .route("/.well-known/matrix/server", any(federation_disabled))
.route("/_matrix/key/*path", any(federation_disabled)) .route("/_matrix/key/{*path}", any(federation_disabled))
.route("/_conduwuit/local_user_count", any(federation_disabled)) .route("/_conduwuit/local_user_count", any(federation_disabled))
.route("/_continuwuity/local_user_count", any(federation_disabled)); .route("/_continuwuity/local_user_count", any(federation_disabled));
} }
@@ -253,27 +253,27 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
get(client::get_media_preview_legacy_legacy_route), get(client::get_media_preview_legacy_legacy_route),
) )
.route( .route(
"/_matrix/media/v1/download/:server_name/:media_id", "/_matrix/media/v1/download/{server_name}/{media_id}",
get(client::get_content_legacy_legacy_route), get(client::get_content_legacy_legacy_route),
) )
.route( .route(
"/_matrix/media/v1/download/:server_name/:media_id/:file_name", "/_matrix/media/v1/download/{server_name}/{media_id}/{file_name}",
get(client::get_content_as_filename_legacy_legacy_route), get(client::get_content_as_filename_legacy_legacy_route),
) )
.route( .route(
"/_matrix/media/v1/thumbnail/:server_name/:media_id", "/_matrix/media/v1/thumbnail/{server_name}/{media_id}",
get(client::get_content_thumbnail_legacy_legacy_route), get(client::get_content_thumbnail_legacy_legacy_route),
); );
} else { } else {
router = router router = router
.route("/_matrix/media/v1/*path", any(legacy_media_disabled)) .route("/_matrix/media/v1/{*path}", any(legacy_media_disabled))
.route("/_matrix/media/v3/config", any(legacy_media_disabled)) .route("/_matrix/media/v3/config", any(legacy_media_disabled))
.route("/_matrix/media/v3/download/*path", any(legacy_media_disabled)) .route("/_matrix/media/v3/download/{*path}", any(legacy_media_disabled))
.route("/_matrix/media/v3/thumbnail/*path", any(legacy_media_disabled)) .route("/_matrix/media/v3/thumbnail/{*path}", any(legacy_media_disabled))
.route("/_matrix/media/v3/preview_url", any(redirect_legacy_preview)) .route("/_matrix/media/v3/preview_url", any(redirect_legacy_preview))
.route("/_matrix/media/r0/config", any(legacy_media_disabled)) .route("/_matrix/media/r0/config", any(legacy_media_disabled))
.route("/_matrix/media/r0/download/*path", any(legacy_media_disabled)) .route("/_matrix/media/r0/download/{*path}", any(legacy_media_disabled))
.route("/_matrix/media/r0/thumbnail/*path", any(legacy_media_disabled)) .route("/_matrix/media/r0/thumbnail/{*path}", any(legacy_media_disabled))
.route("/_matrix/media/r0/preview_url", any(redirect_legacy_preview)); .route("/_matrix/media/r0/preview_url", any(redirect_legacy_preview));
} }
-2
View File
@@ -1,6 +1,5 @@
use std::{mem, ops::Deref}; use std::{mem, ops::Deref};
use async_trait::async_trait;
use axum::{body::Body, extract::FromRequest}; use axum::{body::Body, extract::FromRequest};
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{BufMut, Bytes, BytesMut};
use conduwuit::{Error, Result, debug, debug_warn, err, trace, utils::string::EMPTY}; use conduwuit::{Error, Result, debug, debug_warn, err, trace, utils::string::EMPTY};
@@ -79,7 +78,6 @@ where
fn deref(&self) -> &Self::Target { &self.body } fn deref(&self) -> &Self::Target { &self.body }
} }
#[async_trait]
impl<T> FromRequest<State, Body> for Args<T> impl<T> FromRequest<State, Body> for Args<T>
where where
T: IncomingRequest + Send + Sync + 'static, T: IncomingRequest + Send + Sync + 'static,
+12 -11
View File
@@ -4,7 +4,7 @@ use axum_extra::{
headers::{Authorization, authorization::Bearer}, headers::{Authorization, authorization::Bearer},
typed_header::TypedHeaderRejectionReason, typed_header::TypedHeaderRejectionReason,
}; };
use conduwuit::{Err, Error, Result, debug_error, err, warn}; use conduwuit::{Err, Result, debug_error, err, warn};
use futures::{ use futures::{
TryFutureExt, TryFutureExt,
future::{ future::{
@@ -54,7 +54,8 @@ pub(super) async fn auth(
json_body: Option<&CanonicalJsonValue>, json_body: Option<&CanonicalJsonValue>,
metadata: &Metadata, metadata: &Metadata,
) -> Result<Auth> { ) -> Result<Auth> {
let bearer: Option<TypedHeader<Authorization<Bearer>>> = request.parts.extract().await?; let bearer: Option<TypedHeader<Authorization<Bearer>>> =
request.parts.extract().await.unwrap_or(None);
let token = match &bearer { let token = match &bearer {
| Some(TypedHeader(Authorization(bearer))) => Some(bearer.token()), | Some(TypedHeader(Authorization(bearer))) => Some(bearer.token()),
| None => request.query.access_token.as_deref(), | None => request.query.access_token.as_deref(),
@@ -76,7 +77,7 @@ pub(super) async fn auth(
// already // already
}, },
| Token::None | Token::Invalid => { | Token::None | Token::Invalid => {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::MissingToken, ErrorKind::MissingToken,
"Missing or invalid access token.", "Missing or invalid access token.",
)); ));
@@ -95,7 +96,7 @@ pub(super) async fn auth(
// already // already
}, },
| Token::None | Token::Invalid => { | Token::None | Token::Invalid => {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::MissingToken, ErrorKind::MissingToken,
"Missing or invalid access token.", "Missing or invalid access token.",
)); ));
@@ -129,10 +130,10 @@ pub(super) async fn auth(
appservice_info: None, appservice_info: None,
}) })
} else { } else {
Err(Error::BadRequest(ErrorKind::MissingToken, "Missing access token.")) Err!(BadRequest(ErrorKind::MissingToken, "Missing access token."))
} }
}, },
| _ => Err(Error::BadRequest(ErrorKind::MissingToken, "Missing access token.")), | _ => Err!(BadRequest(ErrorKind::MissingToken, "Missing access token.")),
}, },
| ( | (
AuthScheme::AccessToken | AuthScheme::AccessTokenOptional | AuthScheme::None, AuthScheme::AccessToken | AuthScheme::AccessTokenOptional | AuthScheme::None,
@@ -148,7 +149,7 @@ pub(super) async fn auth(
&ruma::api::client::session::logout::v3::Request::METADATA &ruma::api::client::session::logout::v3::Request::METADATA
| &ruma::api::client::session::logout_all::v3::Request::METADATA | &ruma::api::client::session::logout_all::v3::Request::METADATA
) { ) {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::UserLocked, ErrorKind::UserLocked,
"This account has been locked.", "This account has been locked.",
)); ));
@@ -173,11 +174,11 @@ pub(super) async fn auth(
appservice_info: None, appservice_info: None,
}), }),
| (AuthScheme::ServerSignatures, Token::Appservice(_) | Token::User(_)) => | (AuthScheme::ServerSignatures, Token::Appservice(_) | Token::User(_)) =>
Err(Error::BadRequest( Err!(BadRequest(
ErrorKind::Unauthorized, ErrorKind::Unauthorized,
"Only server signatures should be used on this endpoint.", "Only server signatures should be used on this endpoint.",
)), )),
| (AuthScheme::AppserviceToken, Token::User(_)) => Err(Error::BadRequest( | (AuthScheme::AppserviceToken, Token::User(_)) => Err!(BadRequest(
ErrorKind::Unauthorized, ErrorKind::Unauthorized,
"Only appservice access tokens should be used on this endpoint.", "Only appservice access tokens should be used on this endpoint.",
)), )),
@@ -195,13 +196,13 @@ pub(super) async fn auth(
appservice_info: None, appservice_info: None,
}) })
} else { } else {
Err(Error::BadRequest( Err!(BadRequest(
ErrorKind::UnknownToken { soft_logout: false }, ErrorKind::UnknownToken { soft_logout: false },
"Unknown access token.", "Unknown access token.",
)) ))
} }
}, },
| (_, Token::Invalid) => Err(Error::BadRequest( | (_, Token::Invalid) => Err!(BadRequest(
ErrorKind::UnknownToken { soft_logout: false }, ErrorKind::UnknownToken { soft_logout: false },
"Unknown access token.", "Unknown access token.",
)), )),
+3 -6
View File
@@ -1,12 +1,9 @@
use std::{borrow::Borrow, iter::once}; use std::{borrow::Borrow, iter::once};
use axum::extract::State; use axum::extract::State;
use conduwuit::{Err, Error, Result, info, utils::stream::ReadyExt}; use conduwuit::{Err, Error, Result, err, info, utils::stream::ReadyExt};
use futures::StreamExt; use futures::StreamExt;
use ruma::{ use ruma::{RoomId, api::federation::authorization::get_event_authorization};
RoomId,
api::{client::error::ErrorKind, federation::authorization::get_event_authorization},
};
use super::AccessCheck; use super::AccessCheck;
use crate::Ruma; use crate::Ruma;
@@ -47,7 +44,7 @@ pub(crate) async fn get_event_authorization_route(
.timeline .timeline
.get_pdu_json(&body.event_id) .get_pdu_json(&body.event_id)
.await .await
.map_err(|_| Error::BadRequest(ErrorKind::NotFound, "Event not found."))?; .map_err(|_| err!(BadRequest(ErrorKind::NotFound, "Event not found.")))?;
let room_id_str = event let room_id_str = event
.get("room_id") .get("room_id")
+2 -2
View File
@@ -2,7 +2,7 @@ use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use base64::{Engine as _, engine::general_purpose}; use base64::{Engine as _, engine::general_purpose};
use conduwuit::{ use conduwuit::{
Err, Error, PduEvent, Result, err, error, Err, PduEvent, Result, err, error,
matrix::{Event, event::gen_event_id}, matrix::{Event, event::gen_event_id},
utils::{self, hash::sha256}, utils::{self, hash::sha256},
warn, warn,
@@ -33,7 +33,7 @@ pub(crate) async fn create_invite_route(
.await?; .await?;
if !services.server.supported_room_version(&body.room_version) { if !services.server.supported_room_version(&body.room_version) {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::IncompatibleRoomVersion { room_version: body.room_version.clone() }, ErrorKind::IncompatibleRoomVersion { room_version: body.room_version.clone() },
"Server does not support this room version.", "Server does not support this room version.",
)); ));
+2 -2
View File
@@ -1,7 +1,7 @@
use std::borrow::ToOwned; use std::borrow::ToOwned;
use axum::extract::State; use axum::extract::State;
use conduwuit::{Err, Error, Result, debug, debug_info, info, matrix::pdu::PduBuilder, warn}; use conduwuit::{Err, Result, debug, debug_info, info, matrix::pdu::PduBuilder, warn};
use conduwuit_service::Services; use conduwuit_service::Services;
use futures::StreamExt; use futures::StreamExt;
use ruma::{ use ruma::{
@@ -80,7 +80,7 @@ pub(crate) async fn create_join_event_template_route(
let room_version_id = services.rooms.state.get_room_version(&body.room_id).await?; let room_version_id = services.rooms.state.get_room_version(&body.room_id).await?;
if !body.ver.contains(&room_version_id) { if !body.ver.contains(&room_version_id) {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::IncompatibleRoomVersion { room_version: room_version_id }, ErrorKind::IncompatibleRoomVersion { room_version: room_version_id },
"Room version not supported.", "Room version not supported.",
)); ));
+3 -3
View File
@@ -1,6 +1,6 @@
use RoomVersionId::*; use RoomVersionId::*;
use axum::extract::State; use axum::extract::State;
use conduwuit::{Err, Error, Result, debug_warn, info, matrix::pdu::PduBuilder, warn}; use conduwuit::{Err, Result, debug_warn, info, matrix::pdu::PduBuilder, warn};
use ruma::{ use ruma::{
RoomVersionId, RoomVersionId,
api::{client::error::ErrorKind, federation::knock::create_knock_event_template}, api::{client::error::ErrorKind, federation::knock::create_knock_event_template},
@@ -67,14 +67,14 @@ pub(crate) async fn create_knock_event_template_route(
let room_version_id = services.rooms.state.get_room_version(&body.room_id).await?; let room_version_id = services.rooms.state.get_room_version(&body.room_id).await?;
if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6) { if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6) {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::IncompatibleRoomVersion { room_version: room_version_id }, ErrorKind::IncompatibleRoomVersion { room_version: room_version_id },
"Room version does not support knocking.", "Room version does not support knocking.",
)); ));
} }
if !body.ver.contains(&room_version_id) { if !body.ver.contains(&room_version_id) {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::IncompatibleRoomVersion { room_version: room_version_id }, ErrorKind::IncompatibleRoomVersion { room_version: room_version_id },
"Your homeserver does not support the features required to knock on this room.", "Your homeserver does not support the features required to knock on this room.",
)); ));
+11 -5
View File
@@ -1,6 +1,6 @@
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{Error, Result}; use conduwuit::{Err, Result, err};
use ruma::{ use ruma::{
api::{ api::{
client::error::ErrorKind, client::error::ErrorKind,
@@ -25,7 +25,7 @@ pub(crate) async fn get_public_rooms_filtered_route(
.config .config
.allow_public_room_directory_over_federation .allow_public_room_directory_over_federation
{ {
return Err(Error::BadRequest(ErrorKind::forbidden(), "Room directory is not public")); return Err!(BadRequest(ErrorKind::forbidden(), "Room directory is not public"));
} }
let response = crate::client::get_public_rooms_filtered_helper( let response = crate::client::get_public_rooms_filtered_helper(
@@ -38,7 +38,10 @@ pub(crate) async fn get_public_rooms_filtered_route(
) )
.await .await
.map_err(|_| { .map_err(|_| {
Error::BadRequest(ErrorKind::Unknown, "Failed to return this server's public room list.") err!(BadRequest(
ErrorKind::Unknown,
"Failed to return this server's public room list."
))
})?; })?;
Ok(get_public_rooms_filtered::v1::Response { Ok(get_public_rooms_filtered::v1::Response {
@@ -62,7 +65,7 @@ pub(crate) async fn get_public_rooms_route(
.globals .globals
.allow_public_room_directory_over_federation() .allow_public_room_directory_over_federation()
{ {
return Err(Error::BadRequest(ErrorKind::forbidden(), "Room directory is not public")); return Err!(BadRequest(ErrorKind::forbidden(), "Room directory is not public"));
} }
let response = crate::client::get_public_rooms_filtered_helper( let response = crate::client::get_public_rooms_filtered_helper(
@@ -75,7 +78,10 @@ pub(crate) async fn get_public_rooms_route(
) )
.await .await
.map_err(|_| { .map_err(|_| {
Error::BadRequest(ErrorKind::Unknown, "Failed to return this server's public room list.") err!(BadRequest(
ErrorKind::Unknown,
"Failed to return this server's public room list."
))
})?; })?;
Ok(get_public_rooms::v1::Response { Ok(get_public_rooms::v1::Response {
+6 -7
View File
@@ -1,7 +1,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use axum::extract::State; use axum::extract::State;
use conduwuit::{Error, Result, err}; use conduwuit::{Err, Result, err};
use futures::StreamExt; use futures::StreamExt;
use get_profile_information::v1::ProfileField; use get_profile_information::v1::ProfileField;
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
@@ -40,7 +40,7 @@ pub(crate) async fn get_room_information_route(
servers.sort_unstable(); servers.sort_unstable();
servers.dedup(); servers.dedup();
servers.shuffle(&mut rand::thread_rng()); servers.shuffle(&mut rand::rng());
// insert our server as the very first choice if in list // insert our server as the very first choice if in list
if let Some(server_index) = servers if let Some(server_index) = servers
@@ -67,17 +67,16 @@ pub(crate) async fn get_profile_information_route(
.config .config
.allow_inbound_profile_lookup_federation_requests .allow_inbound_profile_lookup_federation_requests
{ {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::forbidden(), ErrorKind::forbidden(),
"Profile lookup over federation is not allowed on this homeserver.", "Profile lookup over federation is not allowed on this homeserver.",
)); ));
} }
if !services.globals.server_is_ours(body.user_id.server_name()) { if !services.globals.server_is_ours(body.user_id.server_name()) {
return Err(Error::BadRequest( return Err!(
ErrorKind::InvalidParam, BadRequest(ErrorKind::InvalidParam, "User does not belong to this server.",)
"User does not belong to this server.", );
));
} }
let mut displayname = None; let mut displayname = None;
+1 -1
View File
@@ -114,7 +114,7 @@ pub(crate) async fn send_transaction_message_route(
); );
for (id, result) in &results { for (id, result) in &results {
if let Err(e) = result { if let Err(e) = result {
if matches!(e, Error::BadRequest(ErrorKind::NotFound, _)) { if matches!(e, Error::BadRequest { kind: ErrorKind::NotFound, .. }) {
warn!("Incoming PDU failed {id}: {e:?}"); warn!("Incoming PDU failed {id}: {e:?}");
} }
} }
+6 -7
View File
@@ -1,7 +1,7 @@
use std::time::Duration; use std::time::Duration;
use axum::extract::State; use axum::extract::State;
use conduwuit::{Error, Result}; use conduwuit::{Err, Result};
use futures::{FutureExt, StreamExt, TryFutureExt}; use futures::{FutureExt, StreamExt, TryFutureExt};
use ruma::api::{ use ruma::api::{
client::error::ErrorKind, client::error::ErrorKind,
@@ -24,7 +24,7 @@ pub(crate) async fn get_devices_route(
body: Ruma<get_devices::v1::Request>, body: Ruma<get_devices::v1::Request>,
) -> Result<get_devices::v1::Response> { ) -> Result<get_devices::v1::Response> {
if !services.globals.user_is_local(&body.user_id) { if !services.globals.user_is_local(&body.user_id) {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Tried to access user from other server.", "Tried to access user from other server.",
)); ));
@@ -86,10 +86,9 @@ pub(crate) async fn get_keys_route(
.iter() .iter()
.any(|(u, _)| !services.globals.user_is_local(u)) .any(|(u, _)| !services.globals.user_is_local(u))
{ {
return Err(Error::BadRequest( return Err!(
ErrorKind::InvalidParam, BadRequest(ErrorKind::InvalidParam, "User does not belong to this server.",)
"User does not belong to this server.", );
));
} }
let result = get_keys_helper( let result = get_keys_helper(
@@ -121,7 +120,7 @@ pub(crate) async fn claim_keys_route(
.iter() .iter()
.any(|(u, _)| !services.globals.user_is_local(u)) .any(|(u, _)| !services.globals.user_is_local(u))
{ {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Tried to access user from other server.", "Tried to access user from other server.",
)); ));
+3 -3
View File
@@ -1,6 +1,6 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{Error, Result}; use conduwuit::{Err, Result};
use ruma::api::{client::error::ErrorKind, federation::discovery::discover_homeserver}; use ruma::api::federation::discovery::discover_homeserver;
use crate::Ruma; use crate::Ruma;
@@ -14,7 +14,7 @@ pub(crate) async fn well_known_server(
Ok(discover_homeserver::Response { Ok(discover_homeserver::Response {
server: match services.server.config.well_known.server.as_ref() { server: match services.server.config.well_known.server.as_ref() {
| Some(server_name) => server_name.to_owned(), | Some(server_name) => server_name.to_owned(),
| None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")), | None => return Err!(BadRequest(ErrorKind::NotFound, "Not found.")),
}, },
}) })
} }
+3 -1
View File
@@ -86,6 +86,7 @@ libloading.optional = true
log.workspace = true log.workspace = true
num-traits.workspace = true num-traits.workspace = true
rand.workspace = true rand.workspace = true
rand_core = { version = "0.6.4", features = ["getrandom"] }
regex.workspace = true regex.workspace = true
reqwest.workspace = true reqwest.workspace = true
ring.workspace = true ring.workspace = true
@@ -97,7 +98,8 @@ serde-saphyr.workspace = true
serde.workspace = true serde.workspace = true
smallvec.workspace = true smallvec.workspace = true
smallstr.workspace = true smallstr.workspace = true
thiserror.workspace = true snafu.workspace = true
paste.workspace = true
tikv-jemallocator.optional = true tikv-jemallocator.optional = true
tikv-jemallocator.workspace = true tikv-jemallocator.workspace = true
tikv-jemalloc-ctl.optional = true tikv-jemalloc-ctl.optional = true
+27 -18
View File
@@ -1244,12 +1244,6 @@ pub struct Config {
#[serde(default)] #[serde(default)]
pub rocksdb_repair: bool, pub rocksdb_repair: bool,
#[serde(default)]
pub rocksdb_read_only: bool,
#[serde(default)]
pub rocksdb_secondary: bool,
/// Enables idle CPU priority for compaction thread. This is not enabled by /// Enables idle CPU priority for compaction thread. This is not enabled by
/// default to prevent compaction from falling too far behind on busy /// default to prevent compaction from falling too far behind on busy
/// systems. /// systems.
@@ -1309,26 +1303,33 @@ pub struct Config {
/// Allow local (your server only) presence updates/requests. /// Allow local (your server only) presence updates/requests.
/// ///
/// Note that presence on continuwuity is very fast unlike Synapse's. If /// Local presence must be enabled for outgoing presence to function.
/// using outgoing presence, this MUST be enabled. ///
/// Note that local presence is not as heavy on the CPU as federated
/// presence, but will still become more expensive the more local users you
/// have.
#[serde(default = "true_fn")] #[serde(default = "true_fn")]
pub allow_local_presence: bool, pub allow_local_presence: bool,
/// Allow incoming federated presence updates/requests. /// Allow incoming federated presence updates.
/// ///
/// This option receives presence updates from other servers, but does not /// This option enables processing inbound presence updates from other
/// send any unless `allow_outgoing_presence` is true. Note that presence on /// servers. Without it, remote users will appear as if they are always
/// continuwuity is very fast unlike Synapse's. /// offline to your local users. This does not affect typing indicators or
/// read receipts.
#[serde(default = "true_fn")] #[serde(default = "true_fn")]
pub allow_incoming_presence: bool, pub allow_incoming_presence: bool,
/// Allow outgoing presence updates/requests. /// Allow outgoing presence updates/requests.
/// ///
/// This option sends presence updates to other servers, but does not /// This option sends presence updates to other servers, and requires that
/// receive any unless `allow_incoming_presence` is true. Note that presence /// `allow_local_presence` is also enabled.
/// on continuwuity is very fast unlike Synapse's. If using outgoing ///
/// presence, you MUST enable `allow_local_presence` as well. /// Note that outgoing presence is very heavy on the CPU and network, and
#[serde(default = "true_fn")] /// will typically cause extreme strain and slowdowns for no real benefit.
/// There are only a few clients that even implement presence, so you
/// probably don't want to enable this.
#[serde(default)]
pub allow_outgoing_presence: bool, pub allow_outgoing_presence: bool,
/// How many seconds without presence updates before you become idle. /// How many seconds without presence updates before you become idle.
@@ -1366,6 +1367,10 @@ pub struct Config {
pub allow_incoming_read_receipts: bool, pub allow_incoming_read_receipts: bool,
/// Allow sending read receipts to remote servers. /// Allow sending read receipts to remote servers.
///
/// Note that sending read receipts to remote servers in large rooms with
/// lots of other homeservers may cause additional strain on the CPU and
/// network.
#[serde(default = "true_fn")] #[serde(default = "true_fn")]
pub allow_outgoing_read_receipts: bool, pub allow_outgoing_read_receipts: bool,
@@ -1377,6 +1382,10 @@ pub struct Config {
pub allow_local_typing: bool, pub allow_local_typing: bool,
/// Allow outgoing typing updates to federation. /// Allow outgoing typing updates to federation.
///
/// Note that sending typing indicators to remote servers in large rooms
/// with lots of other homeservers may cause additional strain on the CPU
/// and network.
#[serde(default = "true_fn")] #[serde(default = "true_fn")]
pub allow_outgoing_typing: bool, pub allow_outgoing_typing: bool,
@@ -1516,7 +1525,7 @@ pub struct Config {
/// sender user's server name, inbound federation X-Matrix origin, and /// sender user's server name, inbound federation X-Matrix origin, and
/// outbound federation handler. /// outbound federation handler.
/// ///
/// You can set this to ["*"] to block all servers by default, and then /// You can set this to [".*"] to block all servers by default, and then
/// use `allowed_remote_server_names` to allow only specific servers. /// use `allowed_remote_server_names` to allow only specific servers.
/// ///
/// example: ["badserver\\.tld$", "badphrase", "19dollarfortnitecards"] /// example: ["badserver\\.tld$", "badphrase", "19dollarfortnitecards"]
+129 -30
View File
@@ -45,63 +45,162 @@ macro_rules! Err {
macro_rules! err { macro_rules! err {
(Request(Forbidden($level:ident!($($args:tt)+)))) => {{ (Request(Forbidden($level:ident!($($args:tt)+)))) => {{
let mut buf = String::new(); let mut buf = String::new();
$crate::error::Error::Request( $crate::error::Error::Request {
$crate::ruma::api::client::error::ErrorKind::forbidden(), kind: $crate::ruma::api::client::error::ErrorKind::forbidden(),
$crate::err_log!(buf, $level, $($args)+), message: $crate::err_log!(buf, $level, $($args)+),
$crate::http::StatusCode::BAD_REQUEST code: $crate::http::StatusCode::BAD_REQUEST,
) backtrace: Some($crate::snafu::Backtrace::capture()),
}
}}; }};
(Request(Forbidden($($args:tt)+))) => { (Request(Forbidden($($args:tt)+))) => {
$crate::error::Error::Request( {
$crate::ruma::api::client::error::ErrorKind::forbidden(), let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
$crate::format_maybe!($($args)+), $crate::error::Error::Request {
$crate::http::StatusCode::BAD_REQUEST kind: $crate::ruma::api::client::error::ErrorKind::forbidden(),
) message,
code: $crate::http::StatusCode::BAD_REQUEST,
backtrace: Some($crate::snafu::Backtrace::capture()),
}
}
};
(Request(NotFound($level:ident!($($args:tt)+)))) => {{
let mut buf = String::new();
$crate::error::Error::Request {
kind: $crate::ruma::api::client::error::ErrorKind::NotFound,
message: $crate::err_log!(buf, $level, $($args)+),
code: $crate::http::StatusCode::BAD_REQUEST,
backtrace: None,
}
}};
(Request(NotFound($($args:tt)+))) => {
{
let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
$crate::error::Error::Request {
kind: $crate::ruma::api::client::error::ErrorKind::NotFound,
message,
code: $crate::http::StatusCode::BAD_REQUEST,
backtrace: None,
}
}
}; };
(Request($variant:ident($level:ident!($($args:tt)+)))) => {{ (Request($variant:ident($level:ident!($($args:tt)+)))) => {{
let mut buf = String::new(); let mut buf = String::new();
$crate::error::Error::Request( $crate::error::Error::Request {
$crate::ruma::api::client::error::ErrorKind::$variant, kind: $crate::ruma::api::client::error::ErrorKind::$variant,
$crate::err_log!(buf, $level, $($args)+), message: $crate::err_log!(buf, $level, $($args)+),
$crate::http::StatusCode::BAD_REQUEST code: $crate::http::StatusCode::BAD_REQUEST,
) backtrace: Some($crate::snafu::Backtrace::capture()),
}
}}; }};
(Request($variant:ident($($args:tt)+))) => { (Request($variant:ident($($args:tt)+))) => {
$crate::error::Error::Request( {
$crate::ruma::api::client::error::ErrorKind::$variant, let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
$crate::format_maybe!($($args)+), $crate::error::Error::Request {
$crate::http::StatusCode::BAD_REQUEST kind: $crate::ruma::api::client::error::ErrorKind::$variant,
) message,
code: $crate::http::StatusCode::BAD_REQUEST,
backtrace: Some($crate::snafu::Backtrace::capture()),
}
}
}; };
(Config($item:literal, $($args:tt)+)) => {{ (Config($item:literal, $($args:tt)+)) => {{
let mut buf = String::new(); let mut buf = String::new();
$crate::error::Error::Config($item, $crate::err_log!(buf, error, config = %$item, $($args)+)) $crate::error::ConfigSnafu {
directive: $item,
message: $crate::err_log!(buf, error, config = %$item, $($args)+),
}.build()
}}; }};
(BadRequest(ErrorKind::NotFound, $($args:tt)+)) => {
{
let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
$crate::error::Error::Request {
kind: $crate::ruma::api::client::error::ErrorKind::NotFound,
message,
code: $crate::http::StatusCode::BAD_REQUEST,
backtrace: None,
}
}
};
(BadRequest($kind:expr, $($args:tt)+)) => {
{
let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
$crate::error::BadRequestSnafu {
kind: $kind,
message,
}.build()
}
};
(FeatureDisabled($($args:tt)+)) => {
{
let feature: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
$crate::error::FeatureDisabledSnafu { feature }.build()
}
};
(Federation($server:expr, $error:expr $(,)?)) => {
{
$crate::error::FederationSnafu {
server: $server,
error: $error,
}.build()
}
};
(InconsistentRoomState($message:expr, $room_id:expr $(,)?)) => {
{
$crate::error::InconsistentRoomStateSnafu {
message: $message,
room_id: $room_id,
}.build()
}
};
(Uiaa($info:expr $(,)?)) => {
{
$crate::error::UiaaSnafu {
info: $info,
}.build()
}
};
($variant:ident($level:ident!($($args:tt)+))) => {{ ($variant:ident($level:ident!($($args:tt)+))) => {{
let mut buf = String::new(); let mut buf = String::new();
$crate::error::Error::$variant($crate::err_log!(buf, $level, $($args)+)) $crate::paste::paste! {
$crate::error::[<$variant Snafu>] {
message: $crate::err_log!(buf, $level, $($args)+),
}.build()
}
}}; }};
($variant:ident($($args:ident),+)) => {
$crate::error::Error::$variant($($args),+)
};
($variant:ident($($args:tt)+)) => { ($variant:ident($($args:tt)+)) => {
$crate::error::Error::$variant($crate::format_maybe!($($args)+)) $crate::paste::paste! {
{
let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
$crate::error::[<$variant Snafu>] { message }.build()
}
}
}; };
($level:ident!($($args:tt)+)) => {{ ($level:ident!($($args:tt)+)) => {{
let mut buf = String::new(); let mut buf = String::new();
$crate::error::Error::Err($crate::err_log!(buf, $level, $($args)+)) let message: std::borrow::Cow<'static, str> = $crate::err_log!(buf, $level, $($args)+);
$crate::error::ErrSnafu { message }.build()
}}; }};
($($args:tt)+) => { ($($args:tt)+) => {
$crate::error::Error::Err($crate::format_maybe!($($args)+)) {
let message: std::borrow::Cow<'static, str> = $crate::format_maybe!($($args)+);
$crate::error::ErrSnafu { message }.build()
}
}; };
} }
@@ -134,7 +233,7 @@ macro_rules! err_log {
}; };
($crate::error::visit)(&mut $out, LEVEL, &__CALLSITE, &mut valueset_all!(__CALLSITE.metadata().fields(), $($fields)+)); ($crate::error::visit)(&mut $out, LEVEL, &__CALLSITE, &mut valueset_all!(__CALLSITE.metadata().fields(), $($fields)+));
($out).into() std::borrow::Cow::<'static, str>::from($out)
}} }}
} }
+448 -139
View File
@@ -6,151 +6,391 @@ mod serde;
use std::{any::Any, borrow::Cow, convert::Infallible, sync::PoisonError}; use std::{any::Any, borrow::Cow, convert::Infallible, sync::PoisonError};
use snafu::{IntoError, prelude::*};
pub use self::{err::visit, log::*}; pub use self::{err::visit, log::*};
#[derive(thiserror::Error)] #[derive(Debug, Snafu)]
#[snafu(visibility(pub))]
pub enum Error { pub enum Error {
#[error("PANIC!")] #[snafu(display("PANIC!"))]
PanicAny(Box<dyn Any + Send>), PanicAny {
#[error("PANIC! {0}")] panic: Box<dyn Any + Send>,
Panic(&'static str, Box<dyn Any + Send + 'static>), backtrace: snafu::Backtrace,
},
#[snafu(display("PANIC! {message}"))]
Panic {
message: &'static str,
panic: Box<dyn Any + Send + 'static>,
backtrace: snafu::Backtrace,
},
// std // std
#[error(transparent)] #[snafu(display("Format error: {source}"))]
Fmt(#[from] std::fmt::Error), Fmt {
#[error(transparent)] source: std::fmt::Error,
FromUtf8(#[from] std::string::FromUtf8Error), backtrace: snafu::Backtrace,
#[error("I/O error: {0}")] },
Io(#[from] std::io::Error),
#[error(transparent)] #[snafu(display("UTF-8 conversion error: {source}"))]
ParseFloat(#[from] std::num::ParseFloatError), FromUtf8 {
#[error(transparent)] source: std::string::FromUtf8Error,
ParseInt(#[from] std::num::ParseIntError), backtrace: snafu::Backtrace,
#[error(transparent)] },
Std(#[from] Box<dyn std::error::Error + Send>),
#[error(transparent)] #[snafu(display("I/O error: {source}"))]
ThreadAccessError(#[from] std::thread::AccessError), Io {
#[error(transparent)] source: std::io::Error,
TryFromInt(#[from] std::num::TryFromIntError), backtrace: snafu::Backtrace,
#[error(transparent)] },
TryFromSlice(#[from] std::array::TryFromSliceError),
#[error(transparent)] #[snafu(display("Parse float error: {source}"))]
Utf8(#[from] std::str::Utf8Error), ParseFloat {
source: std::num::ParseFloatError,
backtrace: snafu::Backtrace,
},
#[snafu(display("Parse int error: {source}"))]
ParseInt {
source: std::num::ParseIntError,
backtrace: snafu::Backtrace,
},
#[snafu(display("Error: {source}"))]
Std {
source: Box<dyn std::error::Error + Send>,
backtrace: snafu::Backtrace,
},
#[snafu(display("Thread access error: {source}"))]
ThreadAccessError {
source: std::thread::AccessError,
backtrace: snafu::Backtrace,
},
#[snafu(display("Integer conversion error: {source}"))]
TryFromInt {
source: std::num::TryFromIntError,
backtrace: snafu::Backtrace,
},
#[snafu(display("Slice conversion error: {source}"))]
TryFromSlice {
source: std::array::TryFromSliceError,
backtrace: snafu::Backtrace,
},
#[snafu(display("UTF-8 error: {source}"))]
Utf8 {
source: std::str::Utf8Error,
backtrace: snafu::Backtrace,
},
// third-party // third-party
#[error(transparent)] #[snafu(display("Capacity error: {source}"))]
CapacityError(#[from] arrayvec::CapacityError), CapacityError {
#[error(transparent)] source: arrayvec::CapacityError,
CargoToml(#[from] cargo_toml::Error), backtrace: snafu::Backtrace,
#[error(transparent)] },
Clap(#[from] clap::error::Error),
#[error(transparent)] #[snafu(display("Cargo.toml error: {source}"))]
Extension(#[from] axum::extract::rejection::ExtensionRejection), CargoToml {
#[error(transparent)] source: cargo_toml::Error,
Figment(#[from] figment::error::Error), backtrace: snafu::Backtrace,
#[error(transparent)] },
Http(#[from] http::Error),
#[error(transparent)] #[snafu(display("Clap error: {source}"))]
HttpHeader(#[from] http::header::InvalidHeaderValue), Clap {
#[error("Join error: {0}")] source: clap::error::Error,
JoinError(#[from] tokio::task::JoinError), backtrace: snafu::Backtrace,
#[error(transparent)] },
Json(#[from] serde_json::Error),
#[error(transparent)] #[snafu(display("Extension rejection: {source}"))]
JsParseInt(#[from] ruma::JsParseIntError), // js_int re-export Extension {
#[error(transparent)] source: axum::extract::rejection::ExtensionRejection,
JsTryFromInt(#[from] ruma::JsTryFromIntError), // js_int re-export backtrace: snafu::Backtrace,
#[error(transparent)] },
Path(#[from] axum::extract::rejection::PathRejection),
#[error("Mutex poisoned: {0}")] #[snafu(display("Figment error: {source}"))]
Poison(Cow<'static, str>), Figment {
#[error("Regex error: {0}")] source: figment::error::Error,
Regex(#[from] regex::Error), backtrace: snafu::Backtrace,
#[error("Request error: {0}")] },
Reqwest(#[from] reqwest::Error),
#[error("{0}")] #[snafu(display("HTTP error: {source}"))]
SerdeDe(Cow<'static, str>), Http {
#[error("{0}")] source: http::Error,
SerdeSer(Cow<'static, str>), backtrace: snafu::Backtrace,
#[error(transparent)] },
TomlDe(#[from] toml::de::Error),
#[error(transparent)] #[snafu(display("Invalid HTTP header value: {source}"))]
TomlSer(#[from] toml::ser::Error), HttpHeader {
#[error("Tracing filter error: {0}")] source: http::header::InvalidHeaderValue,
TracingFilter(#[from] tracing_subscriber::filter::ParseError), backtrace: snafu::Backtrace,
#[error("Tracing reload error: {0}")] },
TracingReload(#[from] tracing_subscriber::reload::Error),
#[error(transparent)] #[snafu(display("Join error: {source}"))]
TypedHeader(#[from] axum_extra::typed_header::TypedHeaderRejection), JoinError {
#[error(transparent)] source: tokio::task::JoinError,
YamlDe(#[from] serde_saphyr::Error), backtrace: snafu::Backtrace,
#[error(transparent)] },
YamlSer(#[from] serde_saphyr::ser_error::Error),
#[snafu(display("JSON error: {source}"))]
Json {
source: serde_json::Error,
backtrace: snafu::Backtrace,
},
#[snafu(display("JS parse int error: {source}"))]
JsParseInt {
source: ruma::JsParseIntError,
backtrace: snafu::Backtrace,
},
#[snafu(display("JS try from int error: {source}"))]
JsTryFromInt {
source: ruma::JsTryFromIntError,
backtrace: snafu::Backtrace,
},
#[snafu(display("Path rejection: {source}"))]
Path {
source: axum::extract::rejection::PathRejection,
backtrace: snafu::Backtrace,
},
#[snafu(display("Mutex poisoned: {message}"))]
Poison {
message: Cow<'static, str>,
backtrace: snafu::Backtrace,
},
#[snafu(display("Regex error: {source}"))]
Regex {
source: regex::Error,
backtrace: snafu::Backtrace,
},
#[snafu(display("Request error: {source}"))]
Reqwest {
source: reqwest::Error,
backtrace: snafu::Backtrace,
},
#[snafu(display("{message}"))]
SerdeDe {
message: Cow<'static, str>,
backtrace: snafu::Backtrace,
},
#[snafu(display("{message}"))]
SerdeSer {
message: Cow<'static, str>,
backtrace: snafu::Backtrace,
},
#[snafu(display("TOML deserialization error: {source}"))]
TomlDe {
source: toml::de::Error,
backtrace: snafu::Backtrace,
},
#[snafu(display("TOML serialization error: {source}"))]
TomlSer {
source: toml::ser::Error,
backtrace: snafu::Backtrace,
},
#[snafu(display("Tracing filter error: {source}"))]
TracingFilter {
source: tracing_subscriber::filter::ParseError,
backtrace: snafu::Backtrace,
},
#[snafu(display("Tracing reload error: {source}"))]
TracingReload {
source: tracing_subscriber::reload::Error,
backtrace: snafu::Backtrace,
},
#[snafu(display("Typed header rejection: {source}"))]
TypedHeader {
source: axum_extra::typed_header::TypedHeaderRejection,
backtrace: snafu::Backtrace,
},
#[snafu(display("YAML deserialization error: {source}"))]
YamlDe {
source: serde_saphyr::Error,
backtrace: snafu::Backtrace,
},
#[snafu(display("YAML serialization error: {source}"))]
YamlSer {
source: serde_saphyr::ser_error::Error,
backtrace: snafu::Backtrace,
},
// ruma/conduwuit // ruma/conduwuit
#[error("Arithmetic operation failed: {0}")] #[snafu(display("Arithmetic operation failed: {message}"))]
Arithmetic(Cow<'static, str>), Arithmetic {
#[error("{0}: {1}")] message: Cow<'static, str>,
BadRequest(ruma::api::client::error::ErrorKind, &'static str), //TODO: remove backtrace: snafu::Backtrace,
#[error("{0}")] },
BadServerResponse(Cow<'static, str>),
#[error(transparent)] #[snafu(display("{kind}: {message}"))]
CanonicalJson(#[from] ruma::CanonicalJsonError), BadRequest {
#[error("There was a problem with the '{0}' directive in your configuration: {1}")] kind: ruma::api::client::error::ErrorKind,
Config(&'static str, Cow<'static, str>), message: Cow<'static, str>,
#[error("{0}")] backtrace: snafu::Backtrace,
Conflict(Cow<'static, str>), // This is only needed for when a room alias already exists },
#[error(transparent)]
ContentDisposition(#[from] ruma::http_headers::ContentDispositionParseError), #[snafu(display("{message}"))]
#[error("{0}")] BadServerResponse {
Database(Cow<'static, str>), message: Cow<'static, str>,
#[error("Feature '{0}' is not available on this server.")] backtrace: snafu::Backtrace,
FeatureDisabled(Cow<'static, str>), },
#[error("Remote server {0} responded with: {1}")]
Federation(ruma::OwnedServerName, ruma::api::client::error::Error), #[snafu(display("Canonical JSON error: {source}"))]
#[error("{0} in {1}")] CanonicalJson {
InconsistentRoomState(&'static str, ruma::OwnedRoomId), source: ruma::CanonicalJsonError,
#[error(transparent)] backtrace: snafu::Backtrace,
IntoHttp(#[from] ruma::api::error::IntoHttpError), },
#[error("{0}")]
Ldap(Cow<'static, str>), #[snafu(display(
#[error(transparent)] "There was a problem with the '{directive}' directive in your configuration: {message}"
Mxc(#[from] ruma::MxcUriError), ))]
#[error(transparent)] Config {
Mxid(#[from] ruma::IdParseError), directive: &'static str,
#[error("from {0}: {1}")] message: Cow<'static, str>,
Redaction(ruma::OwnedServerName, ruma::canonical_json::RedactionError), backtrace: snafu::Backtrace,
#[error("{0}: {1}")] },
Request(ruma::api::client::error::ErrorKind, Cow<'static, str>, http::StatusCode),
#[error(transparent)] #[snafu(display("{message}"))]
Ruma(#[from] ruma::api::client::error::Error), Conflict {
#[error(transparent)] message: Cow<'static, str>,
Signatures(#[from] ruma::signatures::Error), backtrace: snafu::Backtrace,
#[error(transparent)] },
StateRes(#[from] crate::state_res::Error),
#[error("uiaa")] #[snafu(display("Content disposition error: {source}"))]
Uiaa(ruma::api::client::uiaa::UiaaInfo), ContentDisposition {
source: ruma::http_headers::ContentDispositionParseError,
backtrace: snafu::Backtrace,
},
#[snafu(display("{message}"))]
Database {
message: Cow<'static, str>,
backtrace: snafu::Backtrace,
},
#[snafu(display("Feature '{feature}' is not available on this server."))]
FeatureDisabled {
feature: Cow<'static, str>,
},
#[snafu(display("Remote server {server} responded with: {error}"))]
Federation {
server: ruma::OwnedServerName,
error: ruma::api::client::error::Error,
backtrace: snafu::Backtrace,
},
#[snafu(display("{message} in {room_id}"))]
InconsistentRoomState {
message: &'static str,
room_id: ruma::OwnedRoomId,
backtrace: snafu::Backtrace,
},
#[snafu(display("HTTP conversion error: {source}"))]
IntoHttp {
source: ruma::api::error::IntoHttpError,
backtrace: snafu::Backtrace,
},
#[snafu(display("{message}"))]
Ldap {
message: Cow<'static, str>,
backtrace: snafu::Backtrace,
},
#[snafu(display("MXC URI error: {source}"))]
Mxc {
source: ruma::MxcUriError,
backtrace: snafu::Backtrace,
},
#[snafu(display("Matrix ID parse error: {source}"))]
Mxid {
source: ruma::IdParseError,
backtrace: snafu::Backtrace,
},
#[snafu(display("from {server}: {error}"))]
Redaction {
server: ruma::OwnedServerName,
error: ruma::canonical_json::RedactionError,
backtrace: snafu::Backtrace,
},
#[snafu(display("{kind}: {message}"))]
Request {
kind: ruma::api::client::error::ErrorKind,
message: Cow<'static, str>,
code: http::StatusCode,
backtrace: Option<snafu::Backtrace>,
},
#[snafu(display("Ruma error: {source}"))]
Ruma {
source: ruma::api::client::error::Error,
backtrace: snafu::Backtrace,
},
#[snafu(display("Signature error: {source}"))]
Signatures {
source: ruma::signatures::Error,
backtrace: snafu::Backtrace,
},
#[snafu(display("State resolution error: {source}"))]
#[snafu(context(false))]
StateRes {
source: crate::state_res::Error,
},
#[snafu(display("uiaa"))]
Uiaa {
info: ruma::api::client::uiaa::UiaaInfo,
},
// unique / untyped // unique / untyped
#[error("{0}")] #[snafu(display("{message}"))]
Err(Cow<'static, str>), Err {
message: Cow<'static, str>,
backtrace: snafu::Backtrace,
},
} }
impl Error { impl Error {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn from_errno() -> Self { Self::Io(std::io::Error::last_os_error()) } pub fn from_errno() -> Self { IoSnafu {}.into_error(std::io::Error::last_os_error()) }
//#[deprecated] //#[deprecated]
#[must_use]
pub fn bad_database(message: &'static str) -> Self { pub fn bad_database(message: &'static str) -> Self {
crate::err!(Database(error!("{message}"))) let message: Cow<'static, str> = message.into();
DatabaseSnafu { message }.build()
} }
/// Sanitizes public-facing errors that can leak sensitive information. /// Sanitizes public-facing errors that can leak sensitive information.
pub fn sanitized_message(&self) -> String { pub fn sanitized_message(&self) -> String {
match self { match self {
| Self::Database(..) => String::from("Database error occurred."), | Self::Database { .. } => String::from("Database error occurred."),
| Self::Io(..) => String::from("I/O error occurred."), | Self::Io { .. } => String::from("I/O error occurred."),
| _ => self.message(), | _ => self.message(),
} }
} }
@@ -158,8 +398,8 @@ impl Error {
/// Generate the error message string. /// Generate the error message string.
pub fn message(&self) -> String { pub fn message(&self) -> String {
match self { match self {
| Self::Federation(origin, error) => format!("Answer from {origin}: {error}"), | Self::Federation { server, error, .. } => format!("Answer from {server}: {error}"),
| Self::Ruma(error) => response::ruma_error_message(error), | Self::Ruma { source, .. } => response::ruma_error_message(source),
| _ => format!("{self}"), | _ => format!("{self}"),
} }
} }
@@ -170,10 +410,10 @@ impl Error {
use ruma::api::client::error::ErrorKind::{FeatureDisabled, Unknown}; use ruma::api::client::error::ErrorKind::{FeatureDisabled, Unknown};
match self { match self {
| Self::Federation(_, error) | Self::Ruma(error) => | Self::Federation { error, .. } => response::ruma_error_kind(error).clone(),
response::ruma_error_kind(error).clone(), | Self::Ruma { source, .. } => response::ruma_error_kind(source).clone(),
| Self::BadRequest(kind, ..) | Self::Request(kind, ..) => kind.clone(), | Self::BadRequest { kind, .. } | Self::Request { kind, .. } => kind.clone(),
| Self::FeatureDisabled(..) => FeatureDisabled, | Self::FeatureDisabled { .. } => FeatureDisabled,
| _ => Unknown, | _ => Unknown,
} }
} }
@@ -184,13 +424,15 @@ impl Error {
use http::StatusCode; use http::StatusCode;
match self { match self {
| Self::Federation(_, error) | Self::Ruma(error) => error.status_code, | Self::Federation { error, .. } => error.status_code,
| Self::Request(kind, _, code) => response::status_code(kind, *code), | Self::Ruma { source, .. } => source.status_code,
| Self::BadRequest(kind, ..) => response::bad_request_code(kind), | Self::Request { kind, code, .. } => response::status_code(kind, *code),
| Self::FeatureDisabled(..) => response::bad_request_code(&self.kind()), | Self::BadRequest { kind, .. } => response::bad_request_code(kind),
| Self::Reqwest(error) => error.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR), | Self::FeatureDisabled { .. } => response::bad_request_code(&self.kind()),
| Self::Conflict(_) => StatusCode::CONFLICT, | Self::Reqwest { source, .. } =>
| Self::Io(error) => response::io_error_code(error.kind()), source.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
| Self::Conflict { .. } => StatusCode::CONFLICT,
| Self::Io { source, .. } => response::io_error_code(source.kind()),
| _ => StatusCode::INTERNAL_SERVER_ERROR, | _ => StatusCode::INTERNAL_SERVER_ERROR,
} }
} }
@@ -203,16 +445,46 @@ impl Error {
pub fn is_not_found(&self) -> bool { self.status_code() == http::StatusCode::NOT_FOUND } pub fn is_not_found(&self) -> bool { self.status_code() == http::StatusCode::NOT_FOUND }
} }
impl std::fmt::Debug for Error { // Debug is already derived by Snafu
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message()) /// Macro to reduce boilerplate for From implementations using Snafu context
} macro_rules! impl_from_snafu {
($source_ty:ty => $context:ident) => {
impl From<$source_ty> for Error {
fn from(source: $source_ty) -> Self { $context.into_error(source) }
}
};
}
/// Macro for From impls that format messages into ErrSnafu or other
/// message-based contexts
macro_rules! impl_from_message {
($source_ty:ty => $context:ident, $msg:expr) => {
impl From<$source_ty> for Error {
fn from(source: $source_ty) -> Self {
let message: Cow<'static, str> = format!($msg, source).into();
$context { message }.build()
}
}
};
}
/// Macro for From impls with constant messages (no formatting)
macro_rules! impl_from_const_message {
($source_ty:ty => $context:ident, $msg:expr) => {
impl From<$source_ty> for Error {
fn from(_source: $source_ty) -> Self {
let message: Cow<'static, str> = $msg.into();
$context { message }.build()
}
}
};
} }
impl<T> From<PoisonError<T>> for Error { impl<T> From<PoisonError<T>> for Error {
#[cold] #[cold]
#[inline(never)] #[inline(never)]
fn from(e: PoisonError<T>) -> Self { Self::Poison(e.to_string().into()) } fn from(e: PoisonError<T>) -> Self { PoisonSnafu { message: e.to_string() }.build() }
} }
#[allow(clippy::fallible_impl_from)] #[allow(clippy::fallible_impl_from)]
@@ -224,6 +496,43 @@ impl From<Infallible> for Error {
} }
} }
// Implementations using the macro
impl_from_snafu!(std::io::Error => IoSnafu);
impl_from_snafu!(std::string::FromUtf8Error => FromUtf8Snafu);
impl_from_snafu!(regex::Error => RegexSnafu);
impl_from_snafu!(ruma::http_headers::ContentDispositionParseError => ContentDispositionSnafu);
impl_from_snafu!(ruma::api::error::IntoHttpError => IntoHttpSnafu);
impl_from_snafu!(ruma::JsTryFromIntError => JsTryFromIntSnafu);
impl_from_snafu!(ruma::CanonicalJsonError => CanonicalJsonSnafu);
impl_from_snafu!(axum::extract::rejection::PathRejection => PathSnafu);
impl_from_snafu!(clap::error::Error => ClapSnafu);
impl_from_snafu!(ruma::MxcUriError => MxcSnafu);
impl_from_snafu!(serde_saphyr::ser_error::Error => YamlSerSnafu);
impl_from_snafu!(toml::de::Error => TomlDeSnafu);
impl_from_snafu!(http::header::InvalidHeaderValue => HttpHeaderSnafu);
impl_from_snafu!(serde_json::Error => JsonSnafu);
// Custom implementations using message formatting
impl_from_const_message!(std::fmt::Error => ErrSnafu, "formatting error");
impl_from_message!(std::str::Utf8Error => ErrSnafu, "UTF-8 error: {}");
impl_from_message!(std::num::TryFromIntError => ArithmeticSnafu, "integer conversion error: {}");
impl_from_message!(tracing_subscriber::reload::Error => ErrSnafu, "tracing reload error: {}");
impl_from_message!(reqwest::Error => ErrSnafu, "HTTP client error: {}");
impl_from_message!(ruma::signatures::Error => ErrSnafu, "Signature error: {}");
impl_from_message!(ruma::IdParseError => ErrSnafu, "ID parse error: {}");
impl_from_message!(std::num::ParseIntError => ErrSnafu, "Integer parse error: {}");
impl_from_message!(std::array::TryFromSliceError => ErrSnafu, "Slice conversion error: {}");
impl_from_message!(tokio::task::JoinError => ErrSnafu, "Task join error: {}");
impl_from_message!(serde_saphyr::Error => ErrSnafu, "YAML error: {}");
// Generic implementation for CapacityError
impl<T> From<arrayvec::CapacityError<T>> for Error {
fn from(_source: arrayvec::CapacityError<T>) -> Self {
let message: Cow<'static, str> = "capacity error: buffer is full".into();
ErrSnafu { message }.build()
}
}
#[cold] #[cold]
#[inline(never)] #[inline(never)]
pub fn infallible(_e: &Infallible) { pub fn infallible(_e: &Infallible) {
+8 -5
View File
@@ -15,13 +15,16 @@ impl Error {
#[must_use] #[must_use]
#[inline] #[inline]
pub fn from_panic(e: Box<dyn Any + Send>) -> Self { Self::Panic(debug::panic_str(&e), e) } pub fn from_panic(e: Box<dyn Any + Send>) -> Self {
use super::PanicSnafu;
PanicSnafu { message: debug::panic_str(&e), panic: e }.build()
}
#[inline] #[inline]
pub fn into_panic(self) -> Box<dyn Any + Send + 'static> { pub fn into_panic(self) -> Box<dyn Any + Send + 'static> {
match self { match self {
| Self::Panic(_, e) | Self::PanicAny(e) => e, | Self::Panic { panic, .. } | Self::PanicAny { panic, .. } => panic,
| Self::JoinError(e) => e.into_panic(), | Self::JoinError { source, .. } => source.into_panic(),
| _ => Box::new(self), | _ => Box::new(self),
} }
} }
@@ -37,8 +40,8 @@ impl Error {
#[inline] #[inline]
pub fn is_panic(&self) -> bool { pub fn is_panic(&self) -> bool {
match &self { match &self {
| Self::Panic(..) | Self::PanicAny(..) => true, | Self::Panic { .. } | Self::PanicAny { .. } => true,
| Self::JoinError(e) => e.is_panic(), | Self::JoinError { source, .. } => source.is_panic(),
| _ => false, | _ => false,
} }
} }
+2 -2
View File
@@ -47,8 +47,8 @@ impl axum::response::IntoResponse for Error {
impl From<Error> for UiaaResponse { impl From<Error> for UiaaResponse {
#[inline] #[inline]
fn from(error: Error) -> Self { fn from(error: Error) -> Self {
if let Error::Uiaa(uiaainfo) = error { if let Error::Uiaa { info, .. } = error {
return Self::AuthResponse(uiaainfo); return Self::AuthResponse(info);
} }
let body = ErrorBody::Standard { let body = ErrorBody::Standard {
+8 -2
View File
@@ -5,9 +5,15 @@ use serde::{de, ser};
use crate::Error; use crate::Error;
impl de::Error for Error { impl de::Error for Error {
fn custom<T: Display + ToString>(msg: T) -> Self { Self::SerdeDe(msg.to_string().into()) } fn custom<T: Display + ToString>(msg: T) -> Self {
let message: std::borrow::Cow<'static, str> = msg.to_string().into();
super::SerdeDeSnafu { message }.build()
}
} }
impl ser::Error for Error { impl ser::Error for Error {
fn custom<T: Display + ToString>(msg: T) -> Self { Self::SerdeSer(msg.to_string().into()) } fn custom<T: Display + ToString>(msg: T) -> Self {
let message: std::borrow::Cow<'static, str> = msg.to_string().into();
super::SerdeSerSnafu { message }.build()
}
} }
+10 -3
View File
@@ -1,7 +1,7 @@
use ruma::{RoomVersionId, canonical_json::redact_content_in_place}; use ruma::{RoomVersionId, canonical_json::redact_content_in_place};
use serde_json::{Value as JsonValue, json, value::to_raw_value}; use serde_json::{Value as JsonValue, json, value::to_raw_value};
use crate::{Error, Result, err, implement}; use crate::{Result, err, implement};
#[implement(super::Pdu)] #[implement(super::Pdu)]
pub fn redact(&mut self, room_version_id: &RoomVersionId, reason: JsonValue) -> Result { pub fn redact(&mut self, room_version_id: &RoomVersionId, reason: JsonValue) -> Result {
@@ -10,8 +10,15 @@ pub fn redact(&mut self, room_version_id: &RoomVersionId, reason: JsonValue) ->
let mut content = serde_json::from_str(self.content.get()) let mut content = serde_json::from_str(self.content.get())
.map_err(|e| err!(Request(BadJson("Failed to deserialize content into type: {e}"))))?; .map_err(|e| err!(Request(BadJson("Failed to deserialize content into type: {e}"))))?;
redact_content_in_place(&mut content, room_version_id, self.kind.to_string()) redact_content_in_place(&mut content, room_version_id, self.kind.to_string()).map_err(
.map_err(|e| Error::Redaction(self.sender.server_name().to_owned(), e))?; |error| {
crate::error::RedactionSnafu {
server: self.sender.server_name().to_owned(),
error,
}
.build()
},
)?;
let reason = serde_json::to_value(reason).expect("Failed to preserialize reason"); let reason = serde_json::to_value(reason).expect("Failed to preserialize reason");
+7 -5
View File
@@ -27,7 +27,7 @@ use serde_json::{
use crate::{ use crate::{
matrix::{Event, Pdu, pdu::EventHash}, matrix::{Event, Pdu, pdu::EventHash},
state_res::{self as state_res, Error, Result, StateMap}, state_res::{self as state_res, Error, Result, StateMap, error::NotFoundSnafu},
}; };
static SERVER_TIMESTAMP: AtomicU64 = AtomicU64::new(0); static SERVER_TIMESTAMP: AtomicU64 = AtomicU64::new(0);
@@ -170,10 +170,12 @@ struct TestStore<E: Event>(HashMap<OwnedEventId, E>);
#[allow(unused)] #[allow(unused)]
impl<E: Event + Clone> TestStore<E> { impl<E: Event + Clone> TestStore<E> {
fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result<E> { fn get_event(&self, room_id: &RoomId, event_id: &EventId) -> Result<E> {
self.0 self.0.get(event_id).cloned().ok_or_else(|| {
.get(event_id) NotFoundSnafu {
.cloned() message: format!("{} not found", event_id),
.ok_or_else(|| Error::NotFound(format!("{} not found", event_id))) }
.build()
})
} }
/// Returns the events that correspond to the `event_ids` sorted in the same /// Returns the events that correspond to the `event_ids` sorted in the same
+27 -10
View File
@@ -1,23 +1,40 @@
use serde_json::Error as JsonError; use serde_json::Error as JsonError;
use thiserror::Error; use snafu::{IntoError, prelude::*};
/// Represents the various errors that arise when resolving state. /// Represents the various errors that arise when resolving state.
#[derive(Error, Debug)] #[derive(Debug, Snafu)]
#[snafu(visibility(pub))]
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
/// A deserialization error. /// A deserialization error.
#[error(transparent)] #[snafu(display("JSON error: {source}"))]
SerdeJson(#[from] JsonError), SerdeJson {
source: JsonError,
backtrace: snafu::Backtrace,
},
/// The given option or version is unsupported. /// The given option or version is unsupported.
#[error("Unsupported room version: {0}")] #[snafu(display("Unsupported room version: {version}"))]
Unsupported(String), Unsupported {
version: String,
backtrace: snafu::Backtrace,
},
/// The given event was not found. /// The given event was not found.
#[error("Not found error: {0}")] #[snafu(display("Not found error: {message}"))]
NotFound(String), NotFound {
message: String,
backtrace: snafu::Backtrace,
},
/// Invalid fields in the given PDU. /// Invalid fields in the given PDU.
#[error("Invalid PDU: {0}")] #[snafu(display("Invalid PDU: {message}"))]
InvalidPdu(String), InvalidPdu {
message: String,
backtrace: snafu::Backtrace,
},
}
impl From<serde_json::Error> for Error {
fn from(source: serde_json::Error) -> Self { SerdeJsonSnafu.into_error(source) }
} }
+4 -3
View File
@@ -24,6 +24,7 @@ use serde_json::{from_str as from_json_str, value::RawValue as RawJsonValue};
use super::{ use super::{
Error, Event, Result, StateEventType, StateKey, TimelineEventType, Error, Event, Result, StateEventType, StateKey, TimelineEventType,
error::InvalidPduSnafu,
power_levels::{ power_levels::{
deserialize_power_levels, deserialize_power_levels_content_fields, deserialize_power_levels, deserialize_power_levels_content_fields,
deserialize_power_levels_content_invite, deserialize_power_levels_content_redact, deserialize_power_levels_content_invite, deserialize_power_levels_content_redact,
@@ -383,8 +384,8 @@ where
return Ok(false); return Ok(false);
} }
let target_user = let target_user = <&UserId>::try_from(state_key)
<&UserId>::try_from(state_key).map_err(|e| Error::InvalidPdu(format!("{e}")))?; .map_err(|e| InvalidPduSnafu { message: format!("{e}") }.build())?;
let user_for_join_auth = content let user_for_join_auth = content
.join_authorised_via_users_server .join_authorised_via_users_server
@@ -461,7 +462,7 @@ where
?sender_membership_event_content, ?sender_membership_event_content,
"Sender membership event content missing membership field" "Sender membership event content missing membership field"
); );
return Err(Error::InvalidPdu("Missing membership field".to_owned())); return Err(InvalidPduSnafu { message: "Missing membership field" }.build());
}; };
let membership_state = membership_state.deserialize()?; let membership_state = membership_state.deserialize()?;
+42 -30
View File
@@ -29,18 +29,18 @@ use ruma::{
}; };
use serde_json::from_str as from_json_str; use serde_json::from_str as from_json_str;
pub(crate) use self::error::Error; pub(crate) use self::error::{Error, InvalidPduSnafu, NotFoundSnafu};
use self::power_levels::PowerLevelsContentFields; use self::power_levels::PowerLevelsContentFields;
pub use self::{ pub use self::{
event_auth::{auth_check, auth_types_for_event}, event_auth::{auth_check, auth_types_for_event},
room_version::RoomVersion, room_version::RoomVersion,
}; };
use super::{Event, StateKey};
use crate::{ use crate::{
debug, debug_error, err, debug, debug_error,
matrix::{Event, StateKey},
state_res::room_version::StateResolutionVersion, state_res::room_version::StateResolutionVersion,
trace, trace,
utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt, WidebandExt}, utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt},
warn, warn,
}; };
@@ -118,7 +118,10 @@ where
let csg = calculate_conflicted_subgraph(&conflicting, event_fetch) let csg = calculate_conflicted_subgraph(&conflicting, event_fetch)
.await .await
.ok_or_else(|| { .ok_or_else(|| {
Error::InvalidPdu("Failed to calculate conflicted subgraph".to_owned()) InvalidPduSnafu {
message: "Failed to calculate conflicted subgraph",
}
.build()
})?; })?;
debug!(count = csg.len(), "conflicted subgraph"); debug!(count = csg.len(), "conflicted subgraph");
trace!(set = ?csg, "conflicted subgraph"); trace!(set = ?csg, "conflicted subgraph");
@@ -149,10 +152,11 @@ where
let control_events: Vec<_> = all_conflicted let control_events: Vec<_> = all_conflicted
.iter() .iter()
.stream() .stream()
.wide_filter_map(async |id| { .broad_filter_map(async |id| {
is_power_event_id(id, &event_fetch) event_fetch(id.clone())
.await .await
.then_some(id.clone()) .filter(|event| is_power_event(&event))
.map(|_| id.clone())
}) })
.collect() .collect()
.await; .await;
@@ -314,7 +318,10 @@ where
trace!(event_id = event_id.as_str(), "fetching event for its auth events"); trace!(event_id = event_id.as_str(), "fetching event for its auth events");
let evt = fetch_event(event_id.clone()).await; let evt = fetch_event(event_id.clone()).await;
if evt.is_none() { if evt.is_none() {
err!("could not fetch event {} to calculate conflicted subgraph", event_id); tracing::error!(
"could not fetch event {} to calculate conflicted subgraph",
event_id
);
path.pop(); path.pop();
continue; continue;
} }
@@ -402,11 +409,11 @@ where
let fetcher = async |event_id: OwnedEventId| { let fetcher = async |event_id: OwnedEventId| {
let pl = *event_to_pl let pl = *event_to_pl
.get(&event_id) .get(&event_id)
.ok_or_else(|| Error::NotFound(String::new()))?; .ok_or_else(|| NotFoundSnafu { message: "" }.build())?;
let ev = fetch_event(event_id) let ev = fetch_event(event_id)
.await .await
.ok_or_else(|| Error::NotFound(String::new()))?; .ok_or_else(|| NotFoundSnafu { message: "" }.build())?;
Ok((pl, ev.origin_server_ts())) Ok((pl, ev.origin_server_ts()))
}; };
@@ -612,9 +619,12 @@ where
let events_to_check: Vec<_> = events_to_check let events_to_check: Vec<_> = events_to_check
.map(Result::Ok) .map(Result::Ok)
.broad_and_then(async |event_id| { .broad_and_then(async |event_id| {
fetch_event(event_id.to_owned()) fetch_event(event_id.to_owned()).await.ok_or_else(|| {
.await NotFoundSnafu {
.ok_or_else(|| Error::NotFound(format!("Failed to find {event_id}"))) message: format!("Failed to find {event_id}"),
}
.build()
})
}) })
.try_collect() .try_collect()
.boxed() .boxed()
@@ -653,7 +663,7 @@ where
trace!(event_id = event.event_id().as_str(), "checking event"); trace!(event_id = event.event_id().as_str(), "checking event");
let state_key = event let state_key = event
.state_key() .state_key()
.ok_or_else(|| Error::InvalidPdu("State event had no state key".to_owned()))?; .ok_or_else(|| InvalidPduSnafu { message: "State event had no state key" }.build())?;
let auth_types = auth_types_for_event( let auth_types = auth_types_for_event(
event.event_type(), event.event_type(),
@@ -669,13 +679,14 @@ where
trace!("room version uses hashed IDs, manually fetching create event"); trace!("room version uses hashed IDs, manually fetching create event");
let create_event_id_raw = event.room_id_or_hash().as_str().replace('!', "$"); let create_event_id_raw = event.room_id_or_hash().as_str().replace('!', "$");
let create_event_id = EventId::parse(&create_event_id_raw).map_err(|e| { let create_event_id = EventId::parse(&create_event_id_raw).map_err(|e| {
Error::InvalidPdu(format!( InvalidPduSnafu {
"Failed to parse create event ID from room ID/hash: {e}" message: format!("Failed to parse create event ID from room ID/hash: {e}"),
)) }
.build()
})?;
let create_event = fetch_event(create_event_id.into()).await.ok_or_else(|| {
NotFoundSnafu { message: "Failed to find create event" }.build()
})?; })?;
let create_event = fetch_event(create_event_id.into())
.await
.ok_or_else(|| Error::NotFound("Failed to find create event".into()))?;
auth_state.insert(create_event.event_type().with_state_key(""), create_event); auth_state.insert(create_event.event_type().with_state_key(""), create_event);
} }
for aid in event.auth_events() { for aid in event.auth_events() {
@@ -686,7 +697,7 @@ where
auth_state.insert( auth_state.insert(
ev.event_type() ev.event_type()
.with_state_key(ev.state_key().ok_or_else(|| { .with_state_key(ev.state_key().ok_or_else(|| {
Error::InvalidPdu("State event had no state key".to_owned()) InvalidPduSnafu { message: "State event had no state key" }.build()
})?), })?),
ev.clone(), ev.clone(),
); );
@@ -801,13 +812,13 @@ where
let event = fetch_event(p.clone()) let event = fetch_event(p.clone())
.await .await
.ok_or_else(|| Error::NotFound(format!("Failed to find {p}")))?; .ok_or_else(|| NotFoundSnafu { message: format!("Failed to find {p}") }.build())?;
pl = None; pl = None;
for aid in event.auth_events() { for aid in event.auth_events() {
let ev = fetch_event(aid.to_owned()) let ev = fetch_event(aid.to_owned()).await.ok_or_else(|| {
.await NotFoundSnafu { message: format!("Failed to find {aid}") }.build()
.ok_or_else(|| Error::NotFound(format!("Failed to find {aid}")))?; })?;
if is_type_and_key(&ev, &TimelineEventType::RoomPowerLevels, "") { if is_type_and_key(&ev, &TimelineEventType::RoomPowerLevels, "") {
pl = Some(aid.to_owned()); pl = Some(aid.to_owned());
@@ -869,9 +880,9 @@ where
event = None; event = None;
for aid in sort_ev.auth_events() { for aid in sort_ev.auth_events() {
let aev = fetch_event(aid.to_owned()) let aev = fetch_event(aid.to_owned()).await.ok_or_else(|| {
.await NotFoundSnafu { message: format!("Failed to find {aid}") }.build()
.ok_or_else(|| Error::NotFound(format!("Failed to find {aid}")))?; })?;
if is_type_and_key(&aev, &TimelineEventType::RoomPowerLevels, "") { if is_type_and_key(&aev, &TimelineEventType::RoomPowerLevels, "") {
event = Some(aev); event = Some(aev);
@@ -915,6 +926,7 @@ async fn add_event_and_auth_chain_to_graph<E, F, Fut>(
} }
} }
#[allow(dead_code)]
async fn is_power_event_id<E, F, Fut>(event_id: &EventId, fetch: &F) -> bool async fn is_power_event_id<E, F, Fut>(event_id: &EventId, fetch: &F) -> bool
where where
F: Fn(OwnedEventId) -> Fut + Sync, F: Fn(OwnedEventId) -> Fut + Sync,
@@ -1046,7 +1058,7 @@ mod tests {
// don't remove any events so we know it sorts them all correctly // don't remove any events so we know it sorts them all correctly
let mut events_to_sort = events.keys().cloned().collect::<Vec<_>>(); let mut events_to_sort = events.keys().cloned().collect::<Vec<_>>();
events_to_sort.shuffle(&mut rand::thread_rng()); events_to_sort.shuffle(&mut rand::rng());
let power_level = resolved_power let power_level = resolved_power
.get(&(StateEventType::RoomPowerLevels, "".into())) .get(&(StateEventType::RoomPowerLevels, "".into()))
+6 -2
View File
@@ -1,6 +1,6 @@
use ruma::RoomVersionId; use ruma::RoomVersionId;
use super::{Error, Result}; use super::{Result, error::UnsupportedSnafu};
#[derive(Debug)] #[derive(Debug)]
#[allow(clippy::exhaustive_enums)] #[allow(clippy::exhaustive_enums)]
@@ -163,7 +163,11 @@ impl RoomVersion {
| RoomVersionId::V10 => Self::V10, | RoomVersionId::V10 => Self::V10,
| RoomVersionId::V11 => Self::V11, | RoomVersionId::V11 => Self::V11,
| RoomVersionId::V12 => Self::V12, | RoomVersionId::V12 => Self::V12,
| ver => return Err(Error::Unsupported(format!("found version `{ver}`"))), | ver =>
return Err(UnsupportedSnafu {
version: format!("found version `{ver}`"),
}
.build()),
}) })
} }
} }
+2 -2
View File
@@ -22,7 +22,7 @@ use serde_json::{
value::{RawValue as RawJsonValue, to_raw_value as to_raw_json_value}, value::{RawValue as RawJsonValue, to_raw_value as to_raw_json_value},
}; };
use super::auth_types_for_event; use super::{auth_types_for_event, error::NotFoundSnafu};
use crate::{ use crate::{
Result, RoomVersion, info, Result, RoomVersion, info,
matrix::{Event, EventTypeExt, Pdu, StateMap, pdu::EventHash}, matrix::{Event, EventTypeExt, Pdu, StateMap, pdu::EventHash},
@@ -232,7 +232,7 @@ impl<E: Event + Clone> TestStore<E> {
self.0 self.0
.get(event_id) .get(event_id)
.cloned() .cloned()
.ok_or_else(|| super::Error::NotFound(format!("{event_id} not found"))) .ok_or_else(|| NotFoundSnafu { message: format!("{event_id} not found") }.build())
.map_err(Into::into) .map_err(Into::into)
} }
+2
View File
@@ -14,9 +14,11 @@ pub mod utils;
pub use ::arrayvec; pub use ::arrayvec;
pub use ::http; pub use ::http;
pub use ::paste;
pub use ::ruma; pub use ::ruma;
pub use ::smallstr; pub use ::smallstr;
pub use ::smallvec; pub use ::smallvec;
pub use ::snafu;
pub use ::toml; pub use ::toml;
pub use ::tracing; pub use ::tracing;
pub use config::Config; pub use config::Config;
+1 -1
View File
@@ -28,7 +28,7 @@ fn init_argon() -> Argon2<'static> {
} }
pub(super) fn password(password: &str) -> Result<String> { pub(super) fn password(password: &str) -> Result<String> {
let salt = SaltString::generate(rand::thread_rng()); let salt = SaltString::generate(rand_core::OsRng);
ARGON ARGON
.get_or_init(init_argon) .get_or_init(init_argon)
.hash_password(password.as_bytes(), &salt) .hash_password(password.as_bytes(), &salt)
+7 -10
View File
@@ -4,16 +4,16 @@ use std::{
}; };
use arrayvec::ArrayString; use arrayvec::ArrayString;
use rand::{Rng, seq::SliceRandom, thread_rng}; use rand::{RngExt, seq::SliceRandom};
pub fn shuffle<T>(vec: &mut [T]) { pub fn shuffle<T>(vec: &mut [T]) {
let mut rng = thread_rng(); let mut rng = rand::rng();
vec.shuffle(&mut rng); vec.shuffle(&mut rng);
} }
pub fn string(length: usize) -> String { pub fn string(length: usize) -> String {
thread_rng() rand::rng()
.sample_iter(&rand::distributions::Alphanumeric) .sample_iter(&rand::distr::Alphanumeric)
.take(length) .take(length)
.map(char::from) .map(char::from)
.collect() .collect()
@@ -22,8 +22,8 @@ pub fn string(length: usize) -> String {
#[inline] #[inline]
pub fn string_array<const LENGTH: usize>() -> ArrayString<LENGTH> { pub fn string_array<const LENGTH: usize>() -> ArrayString<LENGTH> {
let mut ret = ArrayString::<LENGTH>::new(); let mut ret = ArrayString::<LENGTH>::new();
thread_rng() rand::rng()
.sample_iter(&rand::distributions::Alphanumeric) .sample_iter(&rand::distr::Alphanumeric)
.take(LENGTH) .take(LENGTH)
.map(char::from) .map(char::from)
.for_each(|c| ret.push(c)); .for_each(|c| ret.push(c));
@@ -40,7 +40,4 @@ pub fn time_from_now_secs(range: Range<u64>) -> SystemTime {
} }
#[must_use] #[must_use]
pub fn secs(range: Range<u64>) -> Duration { pub fn secs(range: Range<u64>) -> Duration { Duration::from_secs(rand::random_range(range)) }
let mut rng = thread_rng();
Duration::from_secs(rng.gen_range(range))
}
+5 -1
View File
@@ -2,6 +2,8 @@
use std::{cell::Cell, fmt::Debug, path::PathBuf, sync::LazyLock}; use std::{cell::Cell, fmt::Debug, path::PathBuf, sync::LazyLock};
use snafu::IntoError;
use crate::{Result, is_equal_to}; use crate::{Result, is_equal_to};
type Id = usize; type Id = usize;
@@ -142,7 +144,9 @@ pub fn getcpu() -> Result<usize> {
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
#[inline] #[inline]
pub fn getcpu() -> Result<usize> { Err(crate::Error::Io(std::io::ErrorKind::Unsupported.into())) } pub fn getcpu() -> Result<usize> {
Err(crate::error::IoSnafu.into_error(std::io::ErrorKind::Unsupported.into()))
}
fn query_cores_available() -> impl Iterator<Item = Id> { fn query_cores_available() -> impl Iterator<Item = Id> {
core_affinity::get_core_ids() core_affinity::get_core_ids()
+12 -7
View File
@@ -255,7 +255,10 @@ impl<'a, 'de: 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
| "$serde_json::private::RawValue" => visitor.visit_map(self), | "$serde_json::private::RawValue" => visitor.visit_map(self),
| "Cbor" => visitor | "Cbor" => visitor
.visit_newtype_struct(&mut minicbor_serde::Deserializer::new(self.record_trail())) .visit_newtype_struct(&mut minicbor_serde::Deserializer::new(self.record_trail()))
.map_err(|e| Self::Error::SerdeDe(e.to_string().into())), .map_err(|e| {
let message: std::borrow::Cow<'static, str> = e.to_string().into();
conduwuit_core::error::SerdeDeSnafu { message }.build()
}),
| _ => visitor.visit_newtype_struct(self), | _ => visitor.visit_newtype_struct(self),
} }
@@ -313,9 +316,10 @@ impl<'a, 'de: 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
let end = self.pos.saturating_add(BYTES).min(self.buf.len()); let end = self.pos.saturating_add(BYTES).min(self.buf.len());
let bytes: ArrayVec<u8, BYTES> = self.buf[self.pos..end].try_into()?; let bytes: ArrayVec<u8, BYTES> = self.buf[self.pos..end].try_into()?;
let bytes = bytes let bytes = bytes.into_inner().map_err(|_| {
.into_inner() let message: std::borrow::Cow<'static, str> = "i64 buffer underflow".into();
.map_err(|_| Self::Error::SerdeDe("i64 buffer underflow".into()))?; conduwuit_core::error::SerdeDeSnafu { message }.build()
})?;
self.inc_pos(BYTES); self.inc_pos(BYTES);
visitor.visit_i64(i64::from_be_bytes(bytes)) visitor.visit_i64(i64::from_be_bytes(bytes))
@@ -345,9 +349,10 @@ impl<'a, 'de: 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
let end = self.pos.saturating_add(BYTES).min(self.buf.len()); let end = self.pos.saturating_add(BYTES).min(self.buf.len());
let bytes: ArrayVec<u8, BYTES> = self.buf[self.pos..end].try_into()?; let bytes: ArrayVec<u8, BYTES> = self.buf[self.pos..end].try_into()?;
let bytes = bytes let bytes = bytes.into_inner().map_err(|_| {
.into_inner() let message: std::borrow::Cow<'static, str> = "u64 buffer underflow".into();
.map_err(|_| Self::Error::SerdeDe("u64 buffer underflow".into()))?; conduwuit_core::error::SerdeDeSnafu { message }.build()
})?;
self.inc_pos(BYTES); self.inc_pos(BYTES);
visitor.visit_u64(u64::from_be_bytes(bytes)) visitor.visit_u64(u64::from_be_bytes(bytes))
-10
View File
@@ -33,8 +33,6 @@ pub struct Engine {
pub(crate) db: Db, pub(crate) db: Db,
pub(crate) pool: Arc<Pool>, pub(crate) pool: Arc<Pool>,
pub(crate) ctx: Arc<Context>, pub(crate) ctx: Arc<Context>,
pub(super) read_only: bool,
pub(super) secondary: bool,
pub(crate) checksums: bool, pub(crate) checksums: bool,
corks: AtomicU32, corks: AtomicU32,
} }
@@ -129,14 +127,6 @@ impl Engine {
sequence sequence
} }
#[inline]
#[must_use]
pub fn is_read_only(&self) -> bool { self.secondary || self.read_only }
#[inline]
#[must_use]
pub fn is_secondary(&self) -> bool { self.secondary }
} }
impl Drop for Engine { impl Drop for Engine {
+1 -2
View File
@@ -12,9 +12,8 @@ pub fn backup(&self) -> Result {
let mut engine = self.backup_engine()?; let mut engine = self.backup_engine()?;
let config = &self.ctx.server.config; let config = &self.ctx.server.config;
if config.database_backups_to_keep > 0 { if config.database_backups_to_keep > 0 {
let flush = !self.is_read_only();
engine engine
.create_new_backup_flush(&self.db, flush) .create_new_backup_flush(&self.db, true)
.map_err(map_err)?; .map_err(map_err)?;
let engine_info = engine.get_backup_info(); let engine_info = engine.get_backup_info();
+1 -10
View File
@@ -35,14 +35,7 @@ pub(crate) async fn open(ctx: Arc<Context>, desc: &[Descriptor]) -> Result<Arc<S
} }
debug!("Opening database..."); debug!("Opening database...");
let db = if config.rocksdb_read_only { let db = Db::open_cf_descriptors(&db_opts, path, cfds).or_else(or_else)?;
Db::open_cf_descriptors_read_only(&db_opts, path, cfds, false)
} else if config.rocksdb_secondary {
Db::open_cf_descriptors_as_secondary(&db_opts, path, path, cfds)
} else {
Db::open_cf_descriptors(&db_opts, path, cfds)
}
.or_else(or_else)?;
info!( info!(
columns = num_cfds, columns = num_cfds,
@@ -55,8 +48,6 @@ pub(crate) async fn open(ctx: Arc<Context>, desc: &[Descriptor]) -> Result<Arc<S
db, db,
pool: ctx.pool.clone(), pool: ctx.pool.clone(),
ctx: ctx.clone(), ctx: ctx.clone(),
read_only: config.rocksdb_read_only,
secondary: config.rocksdb_secondary,
checksums: config.rocksdb_checksums, checksums: config.rocksdb_checksums,
corks: AtomicU32::new(0), corks: AtomicU32::new(0),
})) }))
-8
View File
@@ -74,14 +74,6 @@ impl Database {
#[inline] #[inline]
pub fn keys(&self) -> impl Iterator<Item = &MapsKey> + Send + '_ { self.maps.keys() } pub fn keys(&self) -> impl Iterator<Item = &MapsKey> + Send + '_ { self.maps.keys() }
#[inline]
#[must_use]
pub fn is_read_only(&self) -> bool { self.db.is_read_only() }
#[inline]
#[must_use]
pub fn is_secondary(&self) -> bool { self.db.is_secondary() }
} }
impl Index<&str> for Database { impl Index<&str> for Database {
+4 -1
View File
@@ -199,7 +199,10 @@ impl<W: Write> ser::Serializer for &mut Serializer<'_, W> {
value value
.serialize(&mut Serializer::new(&mut Writer::new(&mut self.out))) .serialize(&mut Serializer::new(&mut Writer::new(&mut self.out)))
.map_err(|e| Self::Error::SerdeSer(e.to_string().into())) .map_err(|e| {
let message: std::borrow::Cow<'static, str> = e.to_string().into();
conduwuit_core::error::SerdeSerSnafu { message }.build()
})
}, },
| _ => unhandled!("Unrecognized serialization Newtype {name:?}"), | _ => unhandled!("Unrecognized serialization Newtype {name:?}"),
} }
+1 -9
View File
@@ -27,10 +27,6 @@ pub struct Args {
#[arg(long, short('O'))] #[arg(long, short('O'))]
pub option: Vec<String>, pub option: Vec<String>,
/// Run in a stricter read-only --maintenance mode.
#[arg(long)]
pub read_only: bool,
/// Run in maintenance mode while refusing connections. /// Run in maintenance mode while refusing connections.
#[arg(long)] #[arg(long)]
pub maintenance: bool, pub maintenance: bool,
@@ -143,11 +139,7 @@ pub(crate) fn parse() -> Args { Args::parse() }
/// Synthesize any command line options with configuration file options. /// Synthesize any command line options with configuration file options.
pub(crate) fn update(mut config: Figment, args: &Args) -> Result<Figment> { pub(crate) fn update(mut config: Figment, args: &Args) -> Result<Figment> {
if args.read_only { if args.maintenance {
config = config.join(("rocksdb_read_only", true));
}
if args.maintenance || args.read_only {
config = config.join(("startup_netburst", false)); config = config.join(("startup_netburst", false));
config = config.join(("listening", false)); config = config.join(("listening", false));
} }
+7 -2
View File
@@ -1,4 +1,4 @@
use std::sync::Arc; use std::{borrow::Cow, sync::Arc};
use axum::{Router, response::IntoResponse}; use axum::{Router, response::IntoResponse};
use conduwuit::Error; use conduwuit::Error;
@@ -18,5 +18,10 @@ pub(crate) fn build(services: &Arc<Services>) -> (Router, Guard) {
} }
async fn not_found(_uri: Uri) -> impl IntoResponse { async fn not_found(_uri: Uri) -> impl IntoResponse {
Error::Request(ErrorKind::Unrecognized, "Not Found".into(), StatusCode::NOT_FOUND) Error::Request {
kind: ErrorKind::Unrecognized,
message: Cow::Borrowed("Not Found"),
code: StatusCode::NOT_FOUND,
backtrace: None,
}
} }
+1 -3
View File
@@ -20,7 +20,6 @@ use std::{sync::Arc, time::Duration};
use async_trait::async_trait; use async_trait::async_trait;
use conduwuit::{Result, Server, debug, error, warn}; use conduwuit::{Result, Server, debug, error, warn};
use database::{Deserialized, Map}; use database::{Deserialized, Map};
use rand::Rng;
use ruma::events::{Mentions, room::message::RoomMessageEventContent}; use ruma::events::{Mentions, room::message::RoomMessageEventContent};
use serde::Deserialize; use serde::Deserialize;
use tokio::{ use tokio::{
@@ -100,8 +99,7 @@ impl crate::Service for Service {
} }
let first_check_jitter = { let first_check_jitter = {
let mut rng = rand::thread_rng(); let jitter_percent = rand::random_range(-50.0..=10.0);
let jitter_percent = rng.gen_range(-50.0..=10.0);
self.interval.mul_f64(1.0 + jitter_percent / 100.0) self.interval.mul_f64(1.0 + jitter_percent / 100.0)
}; };
+4 -4
View File
@@ -147,11 +147,11 @@ impl Service {
// same appservice) // same appservice)
if let Ok(existing) = self.find_from_token(&registration.as_token).await { if let Ok(existing) = self.find_from_token(&registration.as_token).await {
if existing.registration.id != registration.id { if existing.registration.id != registration.id {
return Err(err!(Request(InvalidParam( return Err!(Request(InvalidParam(
"Cannot register appservice: Token is already used by appservice '{}'. \ "Cannot register appservice: Token is already used by appservice '{}'. \
Please generate a different token.", Please generate a different token.",
existing.registration.id existing.registration.id
)))); )));
} }
} }
@@ -163,10 +163,10 @@ impl Service {
.await .await
.is_ok() .is_ok()
{ {
return Err(err!(Request(InvalidParam( return Err!(Request(InvalidParam(
"Cannot register appservice: The provided token is already in use by a user \ "Cannot register appservice: The provided token is already in use by a user \
device. Please generate a different token for the appservice." device. Please generate a different token for the appservice."
)))); )));
} }
self.db self.db
-4
View File
@@ -37,10 +37,6 @@ impl crate::Service for Service {
} }
async fn worker(self: Arc<Self>) -> Result { async fn worker(self: Arc<Self>) -> Result {
if self.services.globals.is_read_only() {
return Ok(());
}
if self.services.config.ldap.enable { if self.services.config.ldap.enable {
warn!("emergency password feature not available with LDAP enabled."); warn!("emergency password feature not available with LDAP enabled.");
return Ok(()); return Ok(());
+2 -5
View File
@@ -2,7 +2,7 @@ use std::{fmt::Debug, mem};
use bytes::Bytes; use bytes::Bytes;
use conduwuit::{ use conduwuit::{
Err, Error, Result, debug, debug::INFO_SPAN_LEVEL, debug_error, debug_warn, err, Err, Result, debug, debug::INFO_SPAN_LEVEL, debug_error, debug_warn, err,
error::inspect_debug_log, implement, trace, error::inspect_debug_log, implement, trace,
}; };
use http::{HeaderValue, header::AUTHORIZATION}; use http::{HeaderValue, header::AUTHORIZATION};
@@ -179,10 +179,7 @@ async fn into_http_response(
debug!("Got {status:?} for {method} {url}"); debug!("Got {status:?} for {method} {url}");
if !status.is_success() { if !status.is_success() {
return Err(Error::Federation( return Err!(Federation(dest.to_owned(), RumaError::from_http_response(http_response),));
dest.to_owned(),
RumaError::from_http_response(http_response),
));
} }
Ok(http_response) Ok(http_response)
-3
View File
@@ -156,7 +156,4 @@ impl Service {
pub fn server_is_ours(&self, server_name: &ServerName) -> bool { pub fn server_is_ours(&self, server_name: &ServerName) -> bool {
server_name == self.server_name() server_name == self.server_name()
} }
#[inline]
pub fn is_read_only(&self) -> bool { self.db.db.is_read_only() }
} }
+2 -2
View File
@@ -35,7 +35,7 @@ pub async fn fetch_remote_thumbnail(
.fetch_thumbnail_authenticated(mxc, user, server, timeout_ms, dim) .fetch_thumbnail_authenticated(mxc, user, server, timeout_ms, dim)
.await; .await;
if let Err(Error::Request(NotFound, ..)) = &result { if let Err(Error::Request { kind: NotFound, .. }) = &result {
return self return self
.fetch_thumbnail_unauthenticated(mxc, user, server, timeout_ms, dim) .fetch_thumbnail_unauthenticated(mxc, user, server, timeout_ms, dim)
.await; .await;
@@ -67,7 +67,7 @@ pub async fn fetch_remote_content(
); );
}); });
if let Err(Error::Request(Unrecognized, ..)) = &result { if let Err(Error::Request { kind: Unrecognized, .. }) = &result {
return self return self
.fetch_content_unauthenticated(mxc, user, server, timeout_ms) .fetch_content_unauthenticated(mxc, user, server, timeout_ms)
.await; .await;
+43 -26
View File
@@ -3,7 +3,7 @@ mod remote;
use std::sync::Arc; use std::sync::Arc;
use conduwuit::{ use conduwuit::{
Err, Event, Result, Server, err, Err, Event, Result, err,
utils::{ReadyExt, stream::TryIgnore}, utils::{ReadyExt, stream::TryIgnore},
}; };
use database::{Deserialized, Ignore, Interfix, Map}; use database::{Deserialized, Ignore, Interfix, Map};
@@ -30,12 +30,12 @@ struct Data {
} }
struct Services { struct Services {
server: Arc<Server>,
admin: Dep<admin::Service>, admin: Dep<admin::Service>,
appservice: Dep<appservice::Service>, appservice: Dep<appservice::Service>,
globals: Dep<globals::Service>, globals: Dep<globals::Service>,
sending: Dep<sending::Service>, sending: Dep<sending::Service>,
state_accessor: Dep<rooms::state_accessor::Service>, state_accessor: Dep<rooms::state_accessor::Service>,
state_cache: Dep<rooms::state_cache::Service>,
} }
impl crate::Service for Service { impl crate::Service for Service {
@@ -47,13 +47,13 @@ impl crate::Service for Service {
aliasid_alias: args.db["aliasid_alias"].clone(), aliasid_alias: args.db["aliasid_alias"].clone(),
}, },
services: Services { services: Services {
server: args.server.clone(),
admin: args.depend::<admin::Service>("admin"), admin: args.depend::<admin::Service>("admin"),
appservice: args.depend::<appservice::Service>("appservice"), appservice: args.depend::<appservice::Service>("appservice"),
globals: args.depend::<globals::Service>("globals"), globals: args.depend::<globals::Service>("globals"),
sending: args.depend::<sending::Service>("sending"), sending: args.depend::<sending::Service>("sending"),
state_accessor: args state_accessor: args
.depend::<rooms::state_accessor::Service>("rooms::state_accessor"), .depend::<rooms::state_accessor::Service>("rooms::state_accessor"),
state_cache: args.depend::<rooms::state_cache::Service>("rooms::state_cache"),
}, },
})) }))
} }
@@ -117,6 +117,9 @@ impl Service {
Ok(()) Ok(())
} }
/// Resolves the given room ID or alias, returning the resolved room ID.
/// Unlike resolve_with_servers (the underlying call), potential resident
/// servers are not returned
#[inline] #[inline]
pub async fn resolve(&self, room: &RoomOrAliasId) -> Result<OwnedRoomId> { pub async fn resolve(&self, room: &RoomOrAliasId) -> Result<OwnedRoomId> {
self.resolve_with_servers(room, None) self.resolve_with_servers(room, None)
@@ -124,6 +127,14 @@ impl Service {
.map(|(room_id, _)| room_id) .map(|(room_id, _)| room_id)
} }
/// Resolves the given room ID or alias, returning the resolved room ID, and
/// any servers that might be able to assist in fetching room data.
///
/// If the input is a room ID, this simply returns it and <servers>.
/// If the input is an alias, this attempts to resolve it locally, then via
/// appservices, and finally remotely if the alias is not local.
/// If the alias is successfully resolved, the room ID and an empty list of
/// servers is returned.
pub async fn resolve_with_servers( pub async fn resolve_with_servers(
&self, &self,
room: &RoomOrAliasId, room: &RoomOrAliasId,
@@ -134,28 +145,26 @@ impl Service {
Ok((room_id.to_owned(), servers.unwrap_or_default())) Ok((room_id.to_owned(), servers.unwrap_or_default()))
} else { } else {
let alias: &RoomAliasId = room.try_into().expect("valid RoomAliasId"); let alias: &RoomAliasId = room.try_into().expect("valid RoomAliasId");
self.resolve_alias(alias, servers).await self.resolve_alias(alias).await
} }
} }
/// Resolves the given room alias, returning the resolved room ID and any
/// servers that might be in the room.
#[tracing::instrument(skip(self), name = "resolve")] #[tracing::instrument(skip(self), name = "resolve")]
pub async fn resolve_alias( pub async fn resolve_alias(
&self, &self,
room_alias: &RoomAliasId, room_alias: &RoomAliasId,
servers: Option<Vec<OwnedServerName>>,
) -> Result<(OwnedRoomId, Vec<OwnedServerName>)> { ) -> Result<(OwnedRoomId, Vec<OwnedServerName>)> {
let server_name = room_alias.server_name(); let server_is_ours = self
let server_is_ours = self.services.globals.server_is_ours(server_name); .services
let servers_contains_ours = || { .globals
servers .server_is_ours(room_alias.server_name());
.as_ref()
.is_some_and(|servers| servers.contains(&self.services.server.name))
};
if !server_is_ours && !servers_contains_ours() { if !server_is_ours {
return self // TODO: The spec advises servers may cache remote room aliases temporarily.
.remote_resolve(room_alias, servers.unwrap_or_default()) // We might want to look at doing that.
.await; return self.remote_resolve(room_alias).await;
} }
let room_id = match self.resolve_local_alias(room_alias).await { let room_id = match self.resolve_local_alias(room_alias).await {
@@ -163,10 +172,18 @@ impl Service {
| Err(_) => self.resolve_appservice_alias(room_alias).await?, | Err(_) => self.resolve_appservice_alias(room_alias).await?,
}; };
room_id.map_or_else( if let Some(room_id) = room_id {
|| Err!(Request(NotFound("Room with alias not found."))), let servers: Vec<OwnedServerName> = self
|room_id| Ok((room_id, Vec::new())), .services
) .state_cache
.room_servers(&room_id)
.map(ToOwned::to_owned)
.collect()
.await;
return Ok((room_id, servers));
}
Err!(Request(NotFound("Alias does not exist.")))
} }
#[tracing::instrument(skip(self), level = "debug")] #[tracing::instrument(skip(self), level = "debug")]
@@ -206,12 +223,12 @@ impl Service {
// The creator of an alias can remove it // The creator of an alias can remove it
if self if self
.who_created_alias(alias).await .who_created_alias(alias).await
.is_ok_and(|user| user == user_id) .is_ok_and(|user| user == user_id)
// Server admins can remove any local alias // Server admins can remove any local alias
|| self.services.admin.user_is_admin(user_id).await || self.services.admin.user_is_admin(user_id).await
// Always allow the server service account to remove the alias, since there may not be an admin room // Always allow the server service account to remove the alias, since there may not be an admin room
|| server_user == user_id || server_user == user_id
{ {
return Ok(true); return Ok(true);
} }
+14 -47
View File
@@ -1,6 +1,4 @@
use std::iter::once; use conduwuit::{Result, debug, error, implement};
use conduwuit::{Result, debug, debug_error, err, implement};
use federation::query::get_room_information::v1::Response; use federation::query::get_room_information::v1::Response;
use ruma::{OwnedRoomId, OwnedServerName, RoomAliasId, ServerName, api::federation}; use ruma::{OwnedRoomId, OwnedServerName, RoomAliasId, ServerName, api::federation};
@@ -8,40 +6,21 @@ use ruma::{OwnedRoomId, OwnedServerName, RoomAliasId, ServerName, api::federatio
pub(super) async fn remote_resolve( pub(super) async fn remote_resolve(
&self, &self,
room_alias: &RoomAliasId, room_alias: &RoomAliasId,
servers: Vec<OwnedServerName>,
) -> Result<(OwnedRoomId, Vec<OwnedServerName>)> { ) -> Result<(OwnedRoomId, Vec<OwnedServerName>)> {
debug!(?room_alias, servers = ?servers, "resolve"); debug!("Asking {} to resolve {room_alias:?}", room_alias.server_name());
let servers = once(room_alias.server_name()) match self
.map(ToOwned::to_owned) .remote_request(room_alias, room_alias.server_name())
.chain(servers.into_iter()); .await
{
let mut resolved_servers = Vec::new(); | Err(e) => {
let mut resolved_room_id: Option<OwnedRoomId> = None; error!("Unable to resolve remote room alias {}: {e}", room_alias);
for server in servers { Err(e)
match self.remote_request(room_alias, &server).await { },
| Err(e) => debug_error!("Failed to query for {room_alias:?} from {server}: {e}"), | Ok(Response { room_id, servers }) => {
| Ok(Response { room_id, servers }) => { debug!("Remote resolved {room_alias:?} to {room_id:?} with servers {servers:?}");
debug!( Ok((room_id, servers))
"Server {server} answered with {room_id:?} for {room_alias:?} servers: \ },
{servers:?}"
);
resolved_room_id.get_or_insert(room_id);
add_server(&mut resolved_servers, server);
if !servers.is_empty() {
add_servers(&mut resolved_servers, servers);
break;
}
},
}
} }
resolved_room_id
.map(|room_id| (room_id, resolved_servers))
.ok_or_else(|| {
err!(Request(NotFound("No servers could assist in resolving the room alias")))
})
} }
#[implement(super::Service)] #[implement(super::Service)]
@@ -59,15 +38,3 @@ async fn remote_request(
.send_federation_request(server, request) .send_federation_request(server, request)
.await .await
} }
fn add_servers(servers: &mut Vec<OwnedServerName>, new: Vec<OwnedServerName>) {
for server in new {
add_server(servers, server);
}
}
fn add_server(servers: &mut Vec<OwnedServerName>, server: OwnedServerName) {
if !servers.contains(&server) {
servers.push(server);
}
}
@@ -112,7 +112,14 @@ where
{ {
let event_fetch = |event_id| self.event_fetch(event_id); let event_fetch = |event_id| self.event_fetch(event_id);
let event_exists = |event_id| self.event_exists(event_id); let event_exists = |event_id| self.event_exists(event_id);
state_res::resolve(room_version, state_sets, auth_chain_sets, &event_fetch, &event_exists) Ok(
.map_err(|e| err!(error!("State resolution failed: {e:?}"))) state_res::resolve(
.await room_version,
state_sets,
auth_chain_sets,
&event_fetch,
&event_exists,
)
.await?,
)
} }
+2 -2
View File
@@ -3,7 +3,7 @@ use std::{
str::FromStr, str::FromStr,
}; };
use conduwuit::{Error, Result}; use conduwuit::{Err, Error, Result};
use ruma::{UInt, api::client::error::ErrorKind}; use ruma::{UInt, api::client::error::ErrorKind};
use crate::rooms::short::ShortRoomId; use crate::rooms::short::ShortRoomId;
@@ -57,7 +57,7 @@ impl FromStr for PaginationToken {
if let Some(token) = pag_tok() { if let Some(token) = pag_tok() {
Ok(token) Ok(token)
} else { } else {
Err(Error::BadRequest(ErrorKind::InvalidParam, "invalid token")) Err!(BadRequest(ErrorKind::InvalidParam, "invalid token"))
} }
} }
} }
+8 -1
View File
@@ -139,7 +139,12 @@ pub async fn backfill_if_required(&self, room_id: &RoomId, from: PduCount) -> Re
}) })
.boxed(); .boxed();
let mut federated_room = false;
while let Some(ref backfill_server) = servers.next().await { while let Some(ref backfill_server) = servers.next().await {
if !self.services.globals.server_is_ours(backfill_server) {
federated_room = true;
}
info!("Asking {backfill_server} for backfill in {room_id}"); info!("Asking {backfill_server} for backfill in {room_id}");
let response = self let response = self
.services .services
@@ -168,7 +173,9 @@ pub async fn backfill_if_required(&self, room_id: &RoomId, from: PduCount) -> Re
} }
} }
warn!("No servers could backfill, but backfill was needed in room {room_id}"); if federated_room {
warn!("No servers could backfill, but backfill was needed in room {room_id}");
}
Ok(()) Ok(())
} }
+4 -5
View File
@@ -75,10 +75,7 @@ pub async fn create_hash_and_sign_event(
let content: RoomCreateEventContent = serde_json::from_str(content.get())?; let content: RoomCreateEventContent = serde_json::from_str(content.get())?;
Ok(content.room_version) Ok(content.room_version)
} else { } else {
Err(Error::InconsistentRoomState( Err!(InconsistentRoomState("non-create event for room of unknown version", room_id))
"non-create event for room of unknown version",
room_id,
))
} }
} }
let PduBuilder { let PduBuilder {
@@ -275,7 +272,9 @@ pub async fn create_hash_and_sign_event(
.hash_and_sign_event(&mut pdu_json, &room_version_id) .hash_and_sign_event(&mut pdu_json, &room_version_id)
{ {
return match e { return match e {
| Error::Signatures(ruma::signatures::Error::PduSize) => { | Error::Signatures { source, .. }
if matches!(source, ruma::signatures::Error::PduSize) =>
{
Err!(Request(TooLarge("Message/PDU is too long (exceeds 65535 bytes)"))) Err!(Request(TooLarge("Message/PDU is too long (exceeds 65535 bytes)")))
}, },
| _ => Err!(Request(Unknown(warn!("Signing event failed: {e}")))), | _ => Err!(Request(Unknown(warn!("Signing event failed: {e}")))),
+7 -5
View File
@@ -385,11 +385,13 @@ fn num_senders(args: &crate::Args<'_>) -> usize {
const MIN_SENDERS: usize = 1; const MIN_SENDERS: usize = 1;
// Limit the number of senders to the number of workers threads or number of // Limit the number of senders to the number of workers threads or number of
// cores, conservatively. // cores, conservatively.
let max_senders = args let mut max_senders = args.server.metrics.num_workers();
.server
.metrics // Work around some platforms not returning the number of cores.
.num_workers() let num_cores = available_parallelism();
.min(available_parallelism()); if num_cores > 0 {
max_senders = max_senders.min(num_cores);
}
// If the user doesn't override the default 0, this is intended to then default // If the user doesn't override the default 0, this is intended to then default
// to 1 for now as multiple senders is experimental. // to 1 for now as multiple senders is experimental.
+2 -2
View File
@@ -139,7 +139,7 @@ impl Services {
// reset dormant online/away statuses to offline, and set the server user as // reset dormant online/away statuses to offline, and set the server user as
// online // online
if self.server.config.allow_local_presence && !self.db.is_read_only() { if self.server.config.allow_local_presence {
self.presence.unset_all_presence().await; self.presence.unset_all_presence().await;
_ = self _ = self
.presence .presence
@@ -156,7 +156,7 @@ impl Services {
info!("Shutting down services..."); info!("Shutting down services...");
// set the server user as offline // set the server user as offline
if self.server.config.allow_local_presence && !self.db.is_read_only() { if self.server.config.allow_local_presence {
_ = self _ = self
.presence .presence
.ping_presence(&self.globals.server_user, &ruma::presence::PresenceState::Offline) .ping_presence(&self.globals.server_user, &ruma::presence::PresenceState::Offline)
+4 -4
View File
@@ -1,7 +1,7 @@
use std::{collections::BTreeMap, sync::Arc}; use std::{collections::BTreeMap, sync::Arc};
use conduwuit::{ use conduwuit::{
Err, Error, Result, SyncRwLock, err, error, implement, utils, Err, Result, SyncRwLock, err, error, implement, utils,
utils::{hash, string::EMPTY}, utils::{hash, string::EMPTY},
}; };
use database::{Deserialized, Json, Map}; use database::{Deserialized, Json, Map};
@@ -117,7 +117,7 @@ pub async fn try_auth(
} else if let Some(username) = user { } else if let Some(username) = user {
username username
} else { } else {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::Unrecognized, ErrorKind::Unrecognized,
"Identifier type not recognized.", "Identifier type not recognized.",
)); ));
@@ -125,7 +125,7 @@ pub async fn try_auth(
#[cfg(not(feature = "element_hacks"))] #[cfg(not(feature = "element_hacks"))]
let Some(UserIdentifier::UserIdOrLocalpart(username)) = identifier else { let Some(UserIdentifier::UserIdOrLocalpart(username)) = identifier else {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::Unrecognized, ErrorKind::Unrecognized,
"Identifier type not recognized.", "Identifier type not recognized.",
)); ));
@@ -135,7 +135,7 @@ pub async fn try_auth(
username.clone(), username.clone(),
self.services.globals.server_name(), self.services.globals.server_name(),
) )
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "User ID is invalid."))?; .map_err(|_| err!(BadRequest(ErrorKind::InvalidParam, "User ID is invalid.")))?;
// Check if the access token being used matches the credentials used for UIAA // Check if the access token being used matches the credentials used for UIAA
if user_id.localpart() != user_id_from_username.localpart() { if user_id.localpart() != user_id_from_username.localpart() {
+12 -6
View File
@@ -184,6 +184,12 @@ impl Service {
password: Option<&str>, password: Option<&str>,
origin: Option<&str>, origin: Option<&str>,
) -> Result<()> { ) -> Result<()> {
if !self.services.globals.user_is_local(user_id)
&& (password.is_some() || origin.is_some())
{
return Err!("Cannot create a nonlocal user with a set password or origin");
}
self.db self.db
.userid_origin .userid_origin
.insert(user_id, origin.unwrap_or("password")); .insert(user_id, origin.unwrap_or("password"));
@@ -755,13 +761,13 @@ impl Service {
.keys .keys
.into_values(); .into_values();
let self_signing_key_id = self_signing_key_ids.next().ok_or(Error::BadRequest( let self_signing_key_id = self_signing_key_ids.next().ok_or(err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Self signing key contained no key.", "Self signing key contained no key.",
))?; )))?;
if self_signing_key_ids.next().is_some() { if self_signing_key_ids.next().is_some() {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Self signing key contained more than one key.", "Self signing key contained more than one key.",
)); ));
@@ -1433,13 +1439,13 @@ pub fn parse_master_key(
let master_key = master_key let master_key = master_key
.deserialize() .deserialize()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid master key"))?; .map_err(|_| err!(BadRequest(ErrorKind::InvalidParam, "Invalid master key")))?;
let mut master_key_ids = master_key.keys.values(); let mut master_key_ids = master_key.keys.values();
let master_key_id = master_key_ids let master_key_id = master_key_ids
.next() .next()
.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Master key contained no key."))?; .ok_or(err!(BadRequest(ErrorKind::InvalidParam, "Master key contained no key.")))?;
if master_key_ids.next().is_some() { if master_key_ids.next().is_some() {
return Err(Error::BadRequest( return Err!(BadRequest(
ErrorKind::InvalidParam, ErrorKind::InvalidParam,
"Master key contained more than one key.", "Master key contained more than one key.",
)); ));
+1 -1
View File
@@ -25,7 +25,7 @@ axum.workspace = true
futures.workspace = true futures.workspace = true
tracing.workspace = true tracing.workspace = true
rand.workspace = true rand.workspace = true
thiserror.workspace = true snafu.workspace = true
[lints] [lints]
workspace = true workspace = true
+12 -4
View File
@@ -8,6 +8,7 @@ use axum::{
}; };
use conduwuit_build_metadata::{GIT_REMOTE_COMMIT_URL, GIT_REMOTE_WEB_URL, version_tag}; use conduwuit_build_metadata::{GIT_REMOTE_COMMIT_URL, GIT_REMOTE_WEB_URL, version_tag};
use conduwuit_service::state; use conduwuit_service::state;
use snafu::{IntoError, prelude::*};
pub fn build() -> Router<state::State> { pub fn build() -> Router<state::State> {
Router::<state::State>::new() Router::<state::State>::new()
@@ -48,10 +49,17 @@ async fn logo_handler() -> impl IntoResponse {
) )
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, Snafu)]
enum WebError { enum WebError {
#[error("Failed to render template: {0}")] #[snafu(display("Failed to render template: {source}"))]
Render(#[from] askama::Error), Render {
source: askama::Error,
backtrace: snafu::Backtrace,
},
}
impl From<askama::Error> for WebError {
fn from(source: askama::Error) -> Self { RenderSnafu.into_error(source) }
} }
impl IntoResponse for WebError { impl IntoResponse for WebError {
@@ -66,7 +74,7 @@ impl IntoResponse for WebError {
let nonce = rand::random::<u64>().to_string(); let nonce = rand::random::<u64>().to_string();
let status = match &self { let status = match &self {
| Self::Render(_) => StatusCode::INTERNAL_SERVER_ERROR, | Self::Render { .. } => StatusCode::INTERNAL_SERVER_ERROR,
}; };
let tmpl = Error { nonce: &nonce, err: self }; let tmpl = Error { nonce: &nonce, err: self };
if let Ok(body) = tmpl.render() { if let Ok(body) = tmpl.render() {
+2 -2
View File
@@ -12,8 +12,8 @@ Server Error
</h1> </h1>
{%- match err -%} {%- match err -%}
{% when WebError::Render(err) -%} {% when WebError::Render { source, .. } -%}
<pre>{{ err }}</pre> <pre>{{ source }}</pre>
{% else -%} <p>An error occurred</p> {% else -%} <p>An error occurred</p>
{%- endmatch -%} {%- endmatch -%}