mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 19146166c0 | |||
| f47027006f | |||
| b7a8f71e14 | |||
| c7378d15ab | |||
| 7beeab270e | |||
| 6a812b7776 | |||
| b1f4bbe89e | |||
| 6701f88bf9 | |||
| 62b9e8227b | |||
| 7369b58d91 | |||
| f6df44b13f | |||
| f243b383cb | |||
| e0b7d03018 | |||
| 184ae2ebb9 | |||
| 0ea0d09b97 | |||
| 6763952ce4 | |||
| e2da8301df | |||
| 296a4b92d6 | |||
| 00c054d356 | |||
| 2558ec0c2a | |||
| 56bc3c184e | |||
| 5c1b90b463 | |||
| 0dbb774559 | |||
| 16e0566c84 | |||
| 489b6e4ecb | |||
| e71f75a58c | |||
| 082ed5b70c | |||
| 76fe8c4cdc | |||
| c4a9f7a6d1 | |||
| a047199fb4 | |||
| 411c9da743 | |||
| fb54f2058c | |||
| 358273226c | |||
| fd9bbb08ed | |||
| 53184cd2fc | |||
| 25f7d80a8c | |||
| 02fa0ba0b8 | |||
| 572b228f40 | |||
| b0a61e38da | |||
| 401dff20eb | |||
| f2a50e8f62 | |||
| 36e80b0af4 | |||
| c9a4c546e2 | |||
| da8b60b4ce | |||
| 89afaa94ac | |||
| 2b5563cee3 | |||
| 6cb9d50383 | |||
| 77c0f6e0c6 | |||
| c85e710760 | |||
| 59346fc766 | |||
| 9c5e735888 | |||
| fe74e82318 | |||
| cb79a3b9d7 | |||
| ebc8df1c4d | |||
| b667a963cf | |||
| 5a6b909b37 | |||
| dba9cf0ad2 | |||
| 287ddd9bc5 | |||
| 79a278b9e8 | |||
| 6c5d658ef2 | |||
| 70c43abca8 | |||
| 6a9b47c52e | |||
| c042de96f8 | |||
| 7a6acd1c82 | |||
| d260c4fcc2 | |||
| fa15de9764 | |||
| e6c7a4ae60 | |||
| 5bed4ad81d | |||
| 587abe9d14 | |||
| c499042a76 | |||
| 86e450a835 | |||
| 4c796029bb | |||
| fc3615c46b | |||
| 7375f7a68e |
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
github: [JadedBlueEyes, nexy7574]
|
github: [JadedBlueEyes, nexy7574, gingershaped]
|
||||||
custom:
|
custom:
|
||||||
- https://ko-fi.com/nexy7574
|
- https://ko-fi.com/nexy7574
|
||||||
- https://ko-fi.com/JadedBlueEyes
|
- https://ko-fi.com/JadedBlueEyes
|
||||||
|
|||||||
@@ -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.41.0
|
rev: v1.43.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: typos
|
- id: typos
|
||||||
- id: typos
|
- id: typos
|
||||||
@@ -31,7 +31,7 @@ repos:
|
|||||||
stages: [commit-msg]
|
stages: [commit-msg]
|
||||||
|
|
||||||
- repo: https://github.com/crate-ci/committed
|
- repo: https://github.com/crate-ci/committed
|
||||||
rev: v1.1.9
|
rev: v1.1.10
|
||||||
hooks:
|
hooks:
|
||||||
- id: committed
|
- id: committed
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -6,14 +6,13 @@ extend-exclude = ["*.csr", "*.lock", "pnpm-lock.yaml"]
|
|||||||
extend-ignore-re = [
|
extend-ignore-re = [
|
||||||
"(?Rm)^.*(#|//|<!--)\\s*spellchecker:disable-line(\\s*-->)$", # Ignore a line by making it trail with a `spellchecker:disable-line` comment
|
"(?Rm)^.*(#|//|<!--)\\s*spellchecker:disable-line(\\s*-->)$", # Ignore a line by making it trail with a `spellchecker:disable-line` comment
|
||||||
"^[0-9a-f]{7,}$", # Commit hashes
|
"^[0-9a-f]{7,}$", # Commit hashes
|
||||||
|
"4BA7",
|
||||||
# some heuristics for base64 strings
|
# some heuristics for base64 strings
|
||||||
"[A-Za-z0-9+=]{72,}",
|
"[A-Za-z0-9+=]{72,}",
|
||||||
"([A-Za-z0-9+=]|\\\\\\s\\*){72,}",
|
"([A-Za-z0-9+=]|\\\\\\s\\*){72,}",
|
||||||
"[0-9+][A-Za-z0-9+]{30,}[a-z0-9+]",
|
"[0-9+][A-Za-z0-9+]{30,}[a-z0-9+]",
|
||||||
"\\$[A-Z0-9+][A-Za-z0-9+]{6,}[a-z0-9+]",
|
"\\$[A-Z0-9+][A-Za-z0-9+]{6,}[a-z0-9+]",
|
||||||
"\\b[a-z0-9+/=][A-Za-z0-9+/=]{7,}[a-z0-9+/=][A-Z]\\b",
|
"\\b[a-z0-9+/=][A-Za-z0-9+/=]{7,}[a-z0-9+/=][A-Z]\\b",
|
||||||
|
|
||||||
# In the renovate config
|
# In the renovate config
|
||||||
".ontainer"
|
".ontainer"
|
||||||
]
|
]
|
||||||
|
|||||||
+77
-16
@@ -1,37 +1,98 @@
|
|||||||
|
# Continuwuity v0.5.4 (2026-02-08)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- The announcement checker will now announce errors it encounters in the first run to the admin room, plus a few other
|
||||||
|
misc improvements. Contributed by @Jade ([#1288](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1288))
|
||||||
|
- Drastically improved the performance and reliability of account deactivations. Contributed by @nex ([#1314](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1314))
|
||||||
|
- Refuse to process requests for and events in rooms that we no longer have any local users in (reduces state resets
|
||||||
|
and improves performance). Contributed by @nex ([#1316](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1316))
|
||||||
|
- Added server-specific admin API routes to ban and unban rooms, for use with moderation bots. Contributed by @nex
|
||||||
|
([#1301](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1301))
|
||||||
|
|
||||||
|
## Bugfixes
|
||||||
|
|
||||||
|
- Fix the generated configuration containing uncommented optional sections. Contributed by @Jade ([#1290](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1290))
|
||||||
|
- Fixed specification non-compliance when handling remote media errors. Contributed by @nex ([#1298](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1298))
|
||||||
|
- UIAA requests which check for out-of-band success (sent by matrix-js-sdk) will no longer create unhelpful errors in
|
||||||
|
the logs. Contributed by @ginger ([#1305](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1305))
|
||||||
|
- Use exists instead of contains to save writing to a buffer in `src/service/users/mod.rs`: `is_login_disabled`.
|
||||||
|
Contributed
|
||||||
|
by @aprilgrimoire. ([#1340](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1340))
|
||||||
|
- Fixed backtraces being swallowed during panics. Contributed by @jade ([#1337](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1337))
|
||||||
|
- Fixed a potential vulnerability that could allow an evil remote server to return malicious events during the room join
|
||||||
|
and knock process. Contributed by @nex, reported by violet & [mat](https://matdoes.dev).
|
||||||
|
- Fixed a race condition that could result in outlier PDUs being incorrectly marked as visible to a remote server.
|
||||||
|
Contributed by @nex, reported by violet & [mat](https://matdoes.dev).
|
||||||
|
- ACLs are no longer case-sensitive. Contributed by @nex, reported by [vel](matrix:u/vel:nhjkl.com?action=chat).
|
||||||
|
|
||||||
|
## Docs
|
||||||
|
|
||||||
|
- Fixed Fedora install instructions. Contributed by @julian45 ([#1342](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1342))
|
||||||
|
|
||||||
|
# Continuwuity 0.5.3 (2026-01-12)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Improve the display of nested configuration with the `!admin server show-config` command. Contributed by @Jade ([#1279](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1279))
|
||||||
|
|
||||||
|
## Bugfixes
|
||||||
|
|
||||||
|
- Fixed `M_BAD_JSON` error when sending invites to other servers or when providing joins. Contributed by @nex ([#1286](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1286))
|
||||||
|
|
||||||
|
## Docs
|
||||||
|
|
||||||
|
- Improve admin command documentation generation. Contributed by @ginger ([#1280](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1280))
|
||||||
|
|
||||||
|
## Misc
|
||||||
|
|
||||||
|
- Improve timeout-related code for federation and URL previews. Contributed by @Jade ([#1278](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1278))
|
||||||
|
|
||||||
# Continuwuity 0.5.2 (2026-01-09)
|
# Continuwuity 0.5.2 (2026-01-09)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Added support for issuing additional registration tokens, stored in the database, which supplement the existing registration token hardcoded in the config file. These tokens may optionally expire after a certain number of uses or after a certain amount of time has passed. Additionally, the `registration_token_file` configuration option is superseded by this feature and **has been removed**. Use the new `!admin token` command family to manage registration tokens. Contributed by @ginger (#783).
|
- Added support for issuing additional registration tokens, stored in the database, which supplement the existing
|
||||||
- Implemented a configuration defined admin list independent of the admin room. Contributed by @Terryiscool160. (#1253)
|
registration token hardcoded in the config file. These tokens may optionally expire after a certain number of uses or
|
||||||
- Added support for invite and join anti-spam via Draupnir and Meowlnir, similar to that of synapse-http-antispam. Contributed by @nex. (#1263)
|
after a certain amount of time has passed. Additionally, the `registration_token_file` configuration option is
|
||||||
- Implemented account locking functionality, to complement user suspension. Contributed by @nex. (#1266)
|
superseded by this feature and **has been removed**. Use the new `!admin token` command family to manage registration
|
||||||
- Added admin command to forcefully log out all of a user's existing sessions. Contributed by @nex. (#1271)
|
tokens. Contributed by @ginger (#783).
|
||||||
- Implemented toggling the ability for an account to log in without mutating any of its data. Contributed by @nex. (#1272)
|
- Implemented a configuration defined admin list independent of the admin room. Contributed by @Terryiscool160. ([#1253](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1253))
|
||||||
- Add support for custom room create event timestamps, to allow generating custom prefixes in hashed room IDs. Contributed by @nex. (#1277)
|
- Added support for invite and join anti-spam via Draupnir and Meowlnir, similar to that of synapse-http-antispam.
|
||||||
- Certain potentially dangerous admin commands are now restricted to only be usable in the admin room and server console. Contributed by @ginger.
|
Contributed by @nex. ([#1263](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1263))
|
||||||
|
- Implemented account locking functionality, to complement user suspension. Contributed by @nex. ([#1266](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1266))
|
||||||
|
- Added admin command to forcefully log out all of a user's existing sessions. Contributed by @nex. ([#1271](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1271))
|
||||||
|
- Implemented toggling the ability for an account to log in without mutating any of its data. Contributed by @nex. (
|
||||||
|
[#1272](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1272))
|
||||||
|
- Add support for custom room create event timestamps, to allow generating custom prefixes in hashed room IDs.
|
||||||
|
Contributed by @nex. ([#1277](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1277))
|
||||||
|
- Certain potentially dangerous admin commands are now restricted to only be usable in the admin room and server
|
||||||
|
console. Contributed by @ginger.
|
||||||
|
|
||||||
## Bugfixes
|
## Bugfixes
|
||||||
|
|
||||||
- Fixed unreliable room summary fetching and improved error messages. Contributed by @nex. (#1257)
|
- Fixed unreliable room summary fetching and improved error messages. Contributed by @nex. ([#1257](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1257))
|
||||||
- Client requested timeout parameter is now applied to e2ee key lookups and claims. Related federation requests are now also concurrent. Contributed by @nex. (#1261)
|
- Client requested timeout parameter is now applied to e2ee key lookups and claims. Related federation requests are now
|
||||||
- Fixed the whoami endpoint returning HTTP 404 instead of HTTP 403, which confused some appservices. Contributed by @nex. (#1276)
|
also concurrent. Contributed by @nex. ([#1261](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1261))
|
||||||
|
- Fixed the whoami endpoint returning HTTP 404 instead of HTTP 403, which confused some appservices. Contributed by
|
||||||
|
@nex. ([#1276](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1276))
|
||||||
|
|
||||||
## Misc
|
## Misc
|
||||||
|
|
||||||
- The `console` feature is now enabled by default, allowing the server console to be used for running admin commands directly. To automatically open the console on startup, set the `admin_console_automatic` config option to `true`. Contributed by @ginger.
|
- The `console` feature is now enabled by default, allowing the server console to be used for running admin commands
|
||||||
|
directly. To automatically open the console on startup, set the `admin_console_automatic` config option to `true`.
|
||||||
|
Contributed by @ginger.
|
||||||
- We now (finally) document our container image mirrors. Contributed by @Jade
|
- We now (finally) document our container image mirrors. Contributed by @Jade
|
||||||
|
|
||||||
|
|
||||||
# Continuwuity 0.5.0 (2025-12-30)
|
# Continuwuity 0.5.0 (2025-12-30)
|
||||||
|
|
||||||
**This release contains a CRITICAL vulnerability patch, and you must update as soon as possible**
|
**This release contains a CRITICAL vulnerability patch, and you must update as soon as possible**
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Enabled the OTLP exporter in default builds, and allow configuring the exporter protocol. (@Jade). (#1251)
|
- Enabled the OTLP exporter in default builds, and allow configuring the exporter protocol. (@Jade). ([#1251](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1251))
|
||||||
|
|
||||||
## Bug Fixes
|
## Bug Fixes
|
||||||
|
|
||||||
- Don't allow admin room upgrades, as this can break the admin room (@timedout) (#1245)
|
- Don't allow admin room upgrades, as this can break the admin room (@timedout) ([#1245](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1245))
|
||||||
- Fix invalid creators in power levels during upgrade to v12 (@timedout) (#1245)
|
- Fix invalid creators in power levels during upgrade to v12 (@timedout) ([#1245](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1245))
|
||||||
|
|||||||
Generated
+869
-488
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -12,7 +12,7 @@ license = "Apache-2.0"
|
|||||||
# See also `rust-toolchain.toml`
|
# See also `rust-toolchain.toml`
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
|
repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
|
||||||
version = "0.5.2"
|
version = "0.5.4"
|
||||||
|
|
||||||
[workspace.metadata.crane]
|
[workspace.metadata.crane]
|
||||||
name = "conduwuit"
|
name = "conduwuit"
|
||||||
@@ -158,7 +158,7 @@ features = ["raw_value"]
|
|||||||
|
|
||||||
# Used for appservice registration files
|
# Used for appservice registration files
|
||||||
[workspace.dependencies.serde-saphyr]
|
[workspace.dependencies.serde-saphyr]
|
||||||
version = "0.0.10"
|
version = "0.0.17"
|
||||||
|
|
||||||
# Used to load forbidden room/user regex from config
|
# Used to load forbidden room/user regex from config
|
||||||
[workspace.dependencies.serde_regex]
|
[workspace.dependencies.serde_regex]
|
||||||
@@ -342,7 +342,7 @@ 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 = "f9e74cb206cfa45cf5f17d39282253b43a15fcd5"
|
rev = "458d52bdc7f9a07c497be94a1420ebd3d87d7b2b"
|
||||||
features = [
|
features = [
|
||||||
"compat",
|
"compat",
|
||||||
"rand",
|
"rand",
|
||||||
|
|||||||
@@ -57,9 +57,10 @@ Continuwuity aims to:
|
|||||||
|
|
||||||
### Can I try it out?
|
### Can I try it out?
|
||||||
|
|
||||||
Check out the [documentation](https://continuwuity.org) for installation instructions.
|
Check out the [documentation](https://continuwuity.org) for installation instructions, or join one of these vetted public homeservers running Continuwuity to get a feel for things!
|
||||||
|
|
||||||
There are currently no open registration Continuwuity instances available.
|
- https://continuwuity.rocks -- A public demo server operated by the Continuwuity Team.
|
||||||
|
- https://federated.nexus -- Federated Nexus is a community resource hosting multiple FOSS (especially federated) services, including Matrix and Forgejo.
|
||||||
|
|
||||||
### What are we working on?
|
### What are we working on?
|
||||||
|
|
||||||
|
|||||||
+6
-34
@@ -2,11 +2,7 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# Path to Complement's source code
|
# The root path where complement is available.
|
||||||
#
|
|
||||||
# The `COMPLEMENT_SRC` environment variable is set in the Nix dev shell, which
|
|
||||||
# points to a store path containing the Complement source code. It's likely you
|
|
||||||
# want to just pass that as the first argument to use it here.
|
|
||||||
COMPLEMENT_SRC="${COMPLEMENT_SRC:-$1}"
|
COMPLEMENT_SRC="${COMPLEMENT_SRC:-$1}"
|
||||||
|
|
||||||
# A `.jsonl` file to write test logs to
|
# A `.jsonl` file to write test logs to
|
||||||
@@ -15,7 +11,10 @@ LOG_FILE="${2:-complement_test_logs.jsonl}"
|
|||||||
# A `.jsonl` file to write test results to
|
# A `.jsonl` file to write test results to
|
||||||
RESULTS_FILE="${3:-complement_test_results.jsonl}"
|
RESULTS_FILE="${3:-complement_test_results.jsonl}"
|
||||||
|
|
||||||
COMPLEMENT_BASE_IMAGE="${COMPLEMENT_BASE_IMAGE:-complement-conduwuit:main}"
|
# The base docker image to use for complement tests
|
||||||
|
# You can build the default with `docker build -t continuwuity:complement -f ./docker/complement.Dockerfile .`
|
||||||
|
# after running `cargo build`. Only the debug binary is used.
|
||||||
|
COMPLEMENT_BASE_IMAGE="${COMPLEMENT_BASE_IMAGE:-continuwuity:complement}"
|
||||||
|
|
||||||
# Complement tests that are skipped due to flakiness/reliability issues or we don't implement such features and won't for a long time
|
# Complement tests that are skipped due to flakiness/reliability issues or we don't implement such features and won't for a long time
|
||||||
SKIPPED_COMPLEMENT_TESTS='TestPartialStateJoin.*|TestRoomDeleteAlias/Parallel/Regular_users_can_add_and_delete_aliases_when_m.*|TestRoomDeleteAlias/Parallel/Can_delete_canonical_alias|TestUnbanViaInvite.*|TestRoomState/Parallel/GET_/publicRooms_lists.*"|TestRoomDeleteAlias/Parallel/Users_with_sufficient_power-level_can_delete_other.*'
|
SKIPPED_COMPLEMENT_TESTS='TestPartialStateJoin.*|TestRoomDeleteAlias/Parallel/Regular_users_can_add_and_delete_aliases_when_m.*|TestRoomDeleteAlias/Parallel/Can_delete_canonical_alias|TestUnbanViaInvite.*|TestRoomState/Parallel/GET_/publicRooms_lists.*"|TestRoomDeleteAlias/Parallel/Users_with_sufficient_power-level_can_delete_other.*'
|
||||||
@@ -34,25 +33,6 @@ toplevel="$(git rev-parse --show-toplevel)"
|
|||||||
|
|
||||||
pushd "$toplevel" > /dev/null
|
pushd "$toplevel" > /dev/null
|
||||||
|
|
||||||
if [ ! -f "complement_oci_image.tar.gz" ]; then
|
|
||||||
echo "building complement conduwuit image"
|
|
||||||
|
|
||||||
# if using macOS, use linux-complement
|
|
||||||
#bin/nix-build-and-cache just .#linux-complement
|
|
||||||
bin/nix-build-and-cache just .#complement
|
|
||||||
#nix build -L .#complement
|
|
||||||
|
|
||||||
echo "complement conduwuit image tar.gz built at \"result\""
|
|
||||||
|
|
||||||
echo "loading into docker"
|
|
||||||
docker load < result
|
|
||||||
popd > /dev/null
|
|
||||||
else
|
|
||||||
echo "skipping building a complement conduwuit image as complement_oci_image.tar.gz was already found, loading this"
|
|
||||||
|
|
||||||
docker load < complement_oci_image.tar.gz
|
|
||||||
popd > /dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "running go test with:"
|
echo "running go test with:"
|
||||||
@@ -72,24 +52,16 @@ env \
|
|||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
||||||
# Post-process the results into an easy-to-compare format, sorted by Test name for reproducible results
|
# Post-process the results into an easy-to-compare format, sorted by Test name for reproducible results
|
||||||
cat "$LOG_FILE" | jq -s -c 'sort_by(.Test)[]' | jq -c '
|
jq -s -c 'sort_by(.Test)[]' < "$LOG_FILE" | jq -c '
|
||||||
select(
|
select(
|
||||||
(.Action == "pass" or .Action == "fail" or .Action == "skip")
|
(.Action == "pass" or .Action == "fail" or .Action == "skip")
|
||||||
and .Test != null
|
and .Test != null
|
||||||
) | {Action: .Action, Test: .Test}
|
) | {Action: .Action, Test: .Test}
|
||||||
' > "$RESULTS_FILE"
|
' > "$RESULTS_FILE"
|
||||||
|
|
||||||
#if command -v gotestfmt &> /dev/null; then
|
|
||||||
# echo "using gotestfmt on $LOG_FILE"
|
|
||||||
# grep '{"Time":' "$LOG_FILE" | gotestfmt > "complement_test_logs_gotestfmt.log"
|
|
||||||
#fi
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo ""
|
echo ""
|
||||||
echo "complement logs saved at $LOG_FILE"
|
echo "complement logs saved at $LOG_FILE"
|
||||||
echo "complement results saved at $RESULTS_FILE"
|
echo "complement results saved at $RESULTS_FILE"
|
||||||
#if command -v gotestfmt &> /dev/null; then
|
|
||||||
# echo "complement logs in gotestfmt pretty format outputted at complement_test_logs_gotestfmt.log (use an editor/terminal/pager that interprets ANSI colours and UTF-8 emojis)"
|
|
||||||
#fi
|
|
||||||
echo ""
|
echo ""
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -xe
|
||||||
|
# If we have no $SERVER_NAME set, abort
|
||||||
|
if [ -z "$SERVER_NAME" ]; then
|
||||||
|
echo "SERVER_NAME is not set, aborting"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If /complement/ca/ca.crt or /complement/ca/ca.key are missing, abort
|
||||||
|
if [ ! -f /complement/ca/ca.crt ] || [ ! -f /complement/ca/ca.key ]; then
|
||||||
|
echo "/complement/ca/ca.crt or /complement/ca/ca.key is missing, aborting"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add the root cert to the local trust store
|
||||||
|
echo 'Installing Complement CA certificate to local trust store'
|
||||||
|
cp /complement/ca/ca.crt /usr/local/share/ca-certificates/complement-ca.crt
|
||||||
|
update-ca-certificates
|
||||||
|
|
||||||
|
# Sign a certificate for our $SERVER_NAME
|
||||||
|
echo "Generating and signing certificate for $SERVER_NAME"
|
||||||
|
openssl genrsa -out "/$SERVER_NAME.key" 2048
|
||||||
|
|
||||||
|
echo "Generating CSR for $SERVER_NAME"
|
||||||
|
openssl req -new -sha256 \
|
||||||
|
-key "/$SERVER_NAME.key" \
|
||||||
|
-out "/$SERVER_NAME.csr" \
|
||||||
|
-subj "/C=US/ST=CA/O=Continuwuity, Inc./CN=$SERVER_NAME"\
|
||||||
|
-addext "subjectAltName=DNS:$SERVER_NAME"
|
||||||
|
openssl req -in "$SERVER_NAME.csr" -noout -text
|
||||||
|
|
||||||
|
echo "Signing certificate for $SERVER_NAME with Complement CA"
|
||||||
|
cat <<EOF > ./cert.ext
|
||||||
|
authorityKeyIdentifier=keyid,issuer
|
||||||
|
basicConstraints = CA:FALSE
|
||||||
|
keyUsage = digitalSignature, keyEncipherment, dataEncipherment, nonRepudiation
|
||||||
|
extendedKeyUsage = serverAuth
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
[alt_names]
|
||||||
|
DNS.1 = *.docker.internal
|
||||||
|
DNS.2 = hs1
|
||||||
|
DNS.3 = hs2
|
||||||
|
DNS.4 = hs3
|
||||||
|
DNS.5 = hs4
|
||||||
|
DNS.6 = $SERVER_NAME
|
||||||
|
IP.1 = 127.0.0.1
|
||||||
|
EOF
|
||||||
|
openssl x509 \
|
||||||
|
-req \
|
||||||
|
-in "/$SERVER_NAME.csr" \
|
||||||
|
-CA /complement/ca/ca.crt \
|
||||||
|
-CAkey /complement/ca/ca.key \
|
||||||
|
-CAcreateserial \
|
||||||
|
-out "/$SERVER_NAME.crt" \
|
||||||
|
-days 1 \
|
||||||
|
-sha256 \
|
||||||
|
-extfile ./cert.ext
|
||||||
|
|
||||||
|
# Tell continuwuity where to find the certs
|
||||||
|
export CONTINUWUITY_TLS__KEY="/$SERVER_NAME.key"
|
||||||
|
export CONTINUWUITY_TLS__CERTS="/$SERVER_NAME.crt"
|
||||||
|
# And who it is
|
||||||
|
export CONTINUWUITY_SERVER_NAME="$SERVER_NAME"
|
||||||
|
|
||||||
|
echo "Starting Continuwuity with SERVER_NAME=$SERVER_NAME"
|
||||||
|
# Start continuwuity
|
||||||
|
/usr/local/bin/conduwuit --config /etc/continuwuity/config.toml
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# ============================================= #
|
||||||
|
# Complement pre-filled configuration file #
|
||||||
|
#
|
||||||
|
# DANGER: THIS FILE FORCES INSECURE VALUES. #
|
||||||
|
# DO NOT USE OUTSIDE THE TEST SUITE ENV! #
|
||||||
|
# ============================================= #
|
||||||
|
[global]
|
||||||
|
address = "0.0.0.0"
|
||||||
|
allow_device_name_federation = true
|
||||||
|
allow_guest_registration = true
|
||||||
|
allow_public_room_directory_over_federation = true
|
||||||
|
allow_public_room_directory_without_auth = true
|
||||||
|
allow_registration = true
|
||||||
|
database_path = "/database"
|
||||||
|
log = "trace,h2=debug,hyper=debug"
|
||||||
|
port = [8008, 8448]
|
||||||
|
trusted_servers = []
|
||||||
|
only_query_trusted_key_servers = false
|
||||||
|
query_trusted_key_servers_first = false
|
||||||
|
query_trusted_key_servers_first_on_join = false
|
||||||
|
yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true
|
||||||
|
ip_range_denylist = []
|
||||||
|
url_preview_domain_contains_allowlist = ["*"]
|
||||||
|
url_preview_domain_explicit_denylist = ["*"]
|
||||||
|
media_compat_file_link = false
|
||||||
|
media_startup_check = true
|
||||||
|
prune_missing_media = true
|
||||||
|
log_colors = true
|
||||||
|
admin_room_notices = false
|
||||||
|
allow_check_for_updates = false
|
||||||
|
intentionally_unknown_config_option_for_testing = true
|
||||||
|
rocksdb_log_level = "info"
|
||||||
|
rocksdb_max_log_files = 1
|
||||||
|
rocksdb_recovery_mode = 0
|
||||||
|
rocksdb_paranoid_file_checks = true
|
||||||
|
log_guest_registrations = false
|
||||||
|
allow_legacy_media = true
|
||||||
|
startup_netburst = true
|
||||||
|
startup_netburst_keep = -1
|
||||||
|
allow_invalid_tls_certificates_yes_i_know_what_the_fuck_i_am_doing_with_this_and_i_know_this_is_insecure = true
|
||||||
|
dns_timeout = 60
|
||||||
|
dns_attempts = 20
|
||||||
|
request_conn_timeout = 60
|
||||||
|
request_timeout = 120
|
||||||
|
well_known_conn_timeout = 60
|
||||||
|
well_known_timeout = 60
|
||||||
|
federation_idle_timeout = 300
|
||||||
|
sender_timeout = 300
|
||||||
|
sender_idle_timeout = 300
|
||||||
|
sender_retry_backoff_limit = 300
|
||||||
|
|
||||||
|
[global.tls]
|
||||||
|
dual_protocol = true
|
||||||
@@ -1759,10 +1759,6 @@
|
|||||||
#
|
#
|
||||||
#config_reload_signal = true
|
#config_reload_signal = true
|
||||||
|
|
||||||
# This item is undocumented. Please contribute documentation for it.
|
|
||||||
#
|
|
||||||
#ldap = false
|
|
||||||
|
|
||||||
[global.tls]
|
[global.tls]
|
||||||
|
|
||||||
# Path to a valid TLS certificate file.
|
# Path to a valid TLS certificate file.
|
||||||
@@ -1930,7 +1926,9 @@
|
|||||||
#
|
#
|
||||||
#admin_filter = ""
|
#admin_filter = ""
|
||||||
|
|
||||||
[global.antispam.meowlnir]
|
#[global.antispam]
|
||||||
|
|
||||||
|
#[global.antispam.meowlnir]
|
||||||
|
|
||||||
# The base URL on which to contact Meowlnir (before /_meowlnir/antispam).
|
# The base URL on which to contact Meowlnir (before /_meowlnir/antispam).
|
||||||
#
|
#
|
||||||
@@ -1955,7 +1953,7 @@
|
|||||||
#
|
#
|
||||||
#check_all_joins = false
|
#check_all_joins = false
|
||||||
|
|
||||||
[global.antispam.draupnir]
|
#[global.antispam.draupnir]
|
||||||
|
|
||||||
# The base URL on which to contact Draupnir (before /api/).
|
# The base URL on which to contact Draupnir (before /api/).
|
||||||
#
|
#
|
||||||
|
|||||||
+1
-1
@@ -48,7 +48,7 @@ EOF
|
|||||||
|
|
||||||
# Developer tool versions
|
# Developer tool versions
|
||||||
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
|
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
|
||||||
ENV BINSTALL_VERSION=1.16.6
|
ENV BINSTALL_VERSION=1.17.4
|
||||||
# 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
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
FROM ubuntu:latest
|
||||||
|
EXPOSE 8008
|
||||||
|
EXPOSE 8448
|
||||||
|
RUN apt-get update && apt-get install -y ca-certificates liburing2 && rm -rf /var/lib/apt/lists/*
|
||||||
|
RUN mkdir -p /etc/continuwuity /var/lib/continuwuity
|
||||||
|
COPY docker/complement-entrypoint.sh /usr/local/bin/complement-entrypoint.sh
|
||||||
|
COPY docker/complement.config.toml /etc/continuwuity/config.toml
|
||||||
|
COPY target/debug/conduwuit /usr/local/bin/conduwuit
|
||||||
|
RUN chmod +x /usr/local/bin/conduwuit /usr/local/bin/complement-entrypoint.sh
|
||||||
|
#HEALTHCHECK --interval=30s --timeout=5s CMD curl --fail http://localhost:8008/_continuwuity/server_version || exit 1
|
||||||
|
ENTRYPOINT ["/usr/local/bin/complement-entrypoint.sh"]
|
||||||
@@ -18,7 +18,7 @@ RUN --mount=type=cache,target=/etc/apk/cache apk add \
|
|||||||
|
|
||||||
# Developer tool versions
|
# Developer tool versions
|
||||||
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
|
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
|
||||||
ENV BINSTALL_VERSION=1.16.6
|
ENV BINSTALL_VERSION=1.17.4
|
||||||
# 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
|
||||||
|
|||||||
+9
-3
@@ -34,6 +34,14 @@
|
|||||||
"name": "troubleshooting",
|
"name": "troubleshooting",
|
||||||
"label": "Troubleshooting"
|
"label": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
"security",
|
||||||
|
{
|
||||||
|
"type": "dir-section-header",
|
||||||
|
"name": "community",
|
||||||
|
"label": "Community",
|
||||||
|
"collapsible": true,
|
||||||
|
"collapsed": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "divider"
|
"type": "divider"
|
||||||
},
|
},
|
||||||
@@ -63,7 +71,5 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "divider"
|
"type": "divider"
|
||||||
},
|
}
|
||||||
"community",
|
|
||||||
"security"
|
|
||||||
]
|
]
|
||||||
|
|||||||
+10
-5
@@ -19,16 +19,21 @@
|
|||||||
{
|
{
|
||||||
"text": "Admin Command Reference",
|
"text": "Admin Command Reference",
|
||||||
"link": "/reference/admin/"
|
"link": "/reference/admin/"
|
||||||
},
|
|
||||||
{
|
|
||||||
"text": "Server Reference",
|
|
||||||
"link": "/reference/server"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Community",
|
"text": "Community",
|
||||||
"link": "/community"
|
"items": [
|
||||||
|
{
|
||||||
|
"text": "Community Guidelines",
|
||||||
|
"link": "/community/guidelines"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Become a Partnered Homeserver!",
|
||||||
|
"link": "/community/ops-guidelines"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Security",
|
"text": "Security",
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "file",
|
||||||
|
"name": "guidelines",
|
||||||
|
"label": "Community Guidelines"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "file",
|
||||||
|
"name": "ops-guidelines",
|
||||||
|
"label": "Partnered Homeserver Guidelines"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Partnered Homeserver Operator Requirements
|
||||||
|
> _So you want to be an officially sanctioned public Continuwuity homeserver operator?_
|
||||||
|
|
||||||
|
Thank you for your interest in the project! There's a few things we need from you first to make sure your homeserver meets our quality standards and that you are prepared to handle the additional workload introduced by operating a public chat service.
|
||||||
|
|
||||||
|
## Stuff you must have
|
||||||
|
if you don't do these things we will tell you to go away
|
||||||
|
|
||||||
|
- Your homeserver must be running an up-to-date version of Continuwuity
|
||||||
|
- You must have a CAPTCHA, external registration system, or apply-to-join system that provides one-time-use invite codes (we do not accept fully open nor static token registration)
|
||||||
|
- Your homeserver must have support details listed in [`/.well-known/matrix/support`](https://spec.matrix.org/v1.17/client-server-api/#getwell-knownmatrixsupport)
|
||||||
|
- Your rules and guidelines must align with [the project's own code of conduct](guidelines).
|
||||||
|
- You must be reasonably responsive (i.e. don't leave us hanging for a week if we alert you to an issue on your server)
|
||||||
|
- Your homeserver's community rooms (if any) must be protected by a moderation bot subscribed to policy lists like the Community Moderation Effort (you can get one from https://asgard.chat if you don't want to run your own)
|
||||||
|
|
||||||
|
## Stuff we encourage you to have
|
||||||
|
not strictly required but we will consider your request more strongly if you have it
|
||||||
|
|
||||||
|
- You should have automated moderation tooling that can automatically suspend abusive users on your homeserver who are added to policy lists
|
||||||
|
- You should have multiple server administrators (increased bus factor)
|
||||||
|
- You should have a terms of service and privacy policy prominently available
|
||||||
|
|
||||||
|
## Stuff you get
|
||||||
|
|
||||||
|
- Prominent listing in our README!
|
||||||
|
- A gold star sticker
|
||||||
|
- Access to a low noise room for more direct communication with maintainers and collaboration with fellow operators
|
||||||
|
- Read-only access to the continuwuity internal ban list
|
||||||
|
- Early notice of upcoming releases
|
||||||
|
|
||||||
|
## Sound good?
|
||||||
|
To get started, ping a team member in [our main chatroom](https://matrix.to/#/#continuwuity:continuwuity.org) and ask to be added to the list.
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
# RPM Installation Guide
|
# RPM Installation Guide
|
||||||
|
|
||||||
Continuwuity is available as RPM packages for Fedora, RHEL, and compatible distributions.
|
Continuwuity is available as RPM packages for Fedora and compatible distributions.
|
||||||
|
We do not currently have infrastructure to build RPMs for RHEL and compatible distributions, but this is a work in progress.
|
||||||
|
|
||||||
The RPM packaging files are maintained in the `fedora/` directory:
|
The RPM packaging files are maintained in the `fedora/` directory:
|
||||||
- `continuwuity.spec.rpkg` - RPM spec file using rpkg macros for building from git
|
- `continuwuity.spec.rpkg` - RPM spec file using rpkg macros for building from git
|
||||||
- `continuwuity.service` - Systemd service file for the server
|
- `continuwuity.service` - Systemd service file for the server
|
||||||
- `RPM-GPG-KEY-continuwuity.asc` - GPG public key for verifying signed packages
|
- `RPM-GPG-KEY-continuwuity.asc` - GPG public key for verifying signed packages
|
||||||
|
|
||||||
RPM packages built by CI are signed with our GPG key (Ed25519, ID: `5E0FF73F411AAFCA`).
|
RPM packages built by CI are signed with our GPG key (RSA, ID: `6595 E8DB 9191 D39A 46D6 A514 4BA7 F590 DF0B AA1D`). # spellchecker:disable-line
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Import the signing key
|
# Import the signing key
|
||||||
sudo rpm --import https://forgejo.ellis.link/continuwuation/continuwuity/raw/branch/main/fedora/RPM-GPG-KEY-continuwuity.asc
|
sudo rpm --import https://forgejo.ellis.link/api/packages/continuwuation/rpm/repository.key
|
||||||
|
|
||||||
# Verify a downloaded package
|
# Verify a downloaded package
|
||||||
rpm --checksig continuwuity-*.rpm
|
rpm --checksig continuwuity-*.rpm
|
||||||
@@ -23,7 +24,7 @@ rpm --checksig continuwuity-*.rpm
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Add the repository and install
|
# Add the repository and install
|
||||||
sudo dnf config-manager addrepo --from-repofile=https://forgejo.ellis.link/api/packages/continuwuation/rpm/stable/continuwuation.repo
|
sudo dnf config-manager addrepo --from-repofile=https://forgejo.ellis.link/api/packages/continuwuation/rpm/stable.repo
|
||||||
sudo dnf install continuwuity
|
sudo dnf install continuwuity
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ sudo dnf install continuwuity
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Add the dev repository and install
|
# Add the dev repository and install
|
||||||
sudo dnf config-manager addrepo --from-repofile=https://forgejo.ellis.link/api/packages/continuwuation/rpm/dev/continuwuation.repo
|
sudo dnf config-manager addrepo --from-repofile=https://forgejo.ellis.link/api/packages/continuwuation/rpm/dev.repo
|
||||||
sudo dnf install continuwuity
|
sudo dnf install continuwuity
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -39,23 +40,10 @@ sudo dnf install continuwuity
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Branch names are sanitized (slashes become hyphens, lowercase only)
|
# Branch names are sanitized (slashes become hyphens, lowercase only)
|
||||||
sudo dnf config-manager addrepo --from-repofile=https://forgejo.ellis.link/api/packages/continuwuation/rpm/tom-new-feature/continuwuation.repo
|
sudo dnf config-manager addrepo --from-repofile=https://forgejo.ellis.link/api/packages/continuwuation/rpm/tom-new-feature.repo
|
||||||
sudo dnf install continuwuity
|
sudo dnf install continuwuity
|
||||||
```
|
```
|
||||||
|
|
||||||
**Direct installation** without adding repository
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Latest stable release
|
|
||||||
sudo dnf install https://forgejo.ellis.link/api/packages/continuwuation/rpm/stable/continuwuity
|
|
||||||
|
|
||||||
# Latest development build
|
|
||||||
sudo dnf install https://forgejo.ellis.link/api/packages/continuwuation/rpm/dev/continuwuity
|
|
||||||
|
|
||||||
# Specific feature branch
|
|
||||||
sudo dnf install https://forgejo.ellis.link/api/packages/continuwuation/rpm/branch-name/continuwuity
|
|
||||||
```
|
|
||||||
|
|
||||||
**Manual repository configuration** (alternative method)
|
**Manual repository configuration** (alternative method)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -65,7 +53,7 @@ name=Continuwuity - Matrix homeserver
|
|||||||
baseurl=https://forgejo.ellis.link/api/packages/continuwuation/rpm/stable
|
baseurl=https://forgejo.ellis.link/api/packages/continuwuation/rpm/stable
|
||||||
enabled=1
|
enabled=1
|
||||||
gpgcheck=1
|
gpgcheck=1
|
||||||
gpgkey=https://forgejo.ellis.link/continuwuation/continuwuity/raw/branch/main/fedora/RPM-GPG-KEY-continuwuity.asc
|
gpgkey=https://forgejo.ellis.link/api/packages/continuwuation/rpm/repository.key
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
sudo dnf install continuwuity
|
sudo dnf install continuwuity
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
"message": "Welcome to Continuwuity! Important announcements about the project will appear here."
|
"message": "Welcome to Continuwuity! Important announcements about the project will appear here."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 7,
|
"id": 8,
|
||||||
"mention_room": true,
|
"mention_room": false,
|
||||||
"date": "2025-12-30",
|
"date": "2026-01-12",
|
||||||
"message": "Continuwuity v0.5.1 has been released. **The release contains a fix for the critical vulnerability [GHSA-m5p2-vccg-8c9v](https://github.com/continuwuity/continuwuity/security/advisories/GHSA-m5p2-vccg-8c9v) (embargoed) affecting all Conduit-derived servers. Update as soon as possible.**\n\nThis has been *actively exploited* to attempt account takeover and forge events bricking the Continuwuity rooms. The new space is accessible at [Continuwuity (room list)](https://matrix.to/#/!8cR4g-i9ucof69E4JHNg9LbPVkGprHb3SzcrGBDDJgk?via=continuwuity.org&via=starstruck.systems&via=gingershaped.computer)\n"
|
"message": "Hey everyone!\n\nJust letting you know we've released [v0.5.3](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.3) - this one is a bit of a hotfix for an issue with inviting and allowing others to join rooms.\n\nIf you appreceate the round-the-clock work we've been doing to keep your servers secure over this holiday period, we'd really appreciate your support - you can sponsor individuals on our team using the 'sponsor' button at the top of [our GitHub repository](https://github.com/continuwuity/continuwuity). If you can't do that, even a star helps - spreading the word and advocating for our project helps keep it going.\n\nHave a lovely rest of your year \\\n[Jade \\(she/her\\)](https://matrix.to/#/%40jade%3Aellis.link) \n🩵"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,10 +118,6 @@ Print detailed tokio runtime metrics accumulated since last command invocation
|
|||||||
|
|
||||||
Print the current time
|
Print the current time
|
||||||
|
|
||||||
## `!admin debug list-dependencies`
|
|
||||||
|
|
||||||
List dependencies
|
|
||||||
|
|
||||||
## `!admin debug database-stats`
|
## `!admin debug database-stats`
|
||||||
|
|
||||||
Get database statistics
|
Get database statistics
|
||||||
|
|||||||
@@ -16,10 +16,6 @@ Show configuration values
|
|||||||
|
|
||||||
Reload configuration values
|
Reload configuration values
|
||||||
|
|
||||||
## `!admin server list-features`
|
|
||||||
|
|
||||||
List the features built into the server
|
|
||||||
|
|
||||||
## `!admin server memory-usage`
|
## `!admin server memory-usage`
|
||||||
|
|
||||||
Print database memory usage statistics
|
Print database memory usage statistics
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ much possible corruption is restored
|
|||||||
|
|
||||||
## Debugging
|
## Debugging
|
||||||
|
|
||||||
Note that users should not really be debugging things. If you find yourself
|
Note that users should not really need to debug things. If you find yourself
|
||||||
debugging and find the issue, please let us know and/or how we can fix it.
|
debugging and find the issue, please let us know and/or how we can fix it.
|
||||||
Various debug commands can be found in `!admin debug`.
|
Various debug commands can be found in `!admin debug`.
|
||||||
|
|
||||||
@@ -178,6 +178,31 @@ server performance on either side as that endpoint is completely unauthenticated
|
|||||||
and simply fetches a string on a static JSON endpoint. It is very low cost both
|
and simply fetches a string on a static JSON endpoint. It is very low cost both
|
||||||
bandwidth and computationally.
|
bandwidth and computationally.
|
||||||
|
|
||||||
|
### Enabling backtraces for errors
|
||||||
|
|
||||||
|
Continuwuity can capture backtraces (stack traces) for errors to help diagnose
|
||||||
|
issues. Backtraces show the exact sequence of function calls that led to an
|
||||||
|
error, which is invaluable for debugging.
|
||||||
|
|
||||||
|
To enable backtraces, set the `RUST_BACKTRACE` environment variable before starting Continuwuity:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# For both panics and errors
|
||||||
|
RUST_BACKTRACE=1 ./conduwuit
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
For systemd deployments, add this to your service file:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Service]
|
||||||
|
Environment="RUST_BACKTRACE=1"
|
||||||
|
```
|
||||||
|
|
||||||
|
Backtrace capture has a performance cost. Avoid leaving it on.
|
||||||
|
You can also enable it only for panics by setting
|
||||||
|
`RUST_BACKTRACE=1` and `RUST_LIB_BACKTRACE=0`.
|
||||||
|
|
||||||
### Allocator memory stats
|
### Allocator memory stats
|
||||||
|
|
||||||
When using jemalloc with jemallocator's `stats` feature (`--enable-stats`), you
|
When using jemalloc with jemallocator's `stats` feature (`--enable-stats`), you
|
||||||
|
|||||||
Generated
+252
-1613
File diff suppressed because it is too large
Load Diff
+3
-4
@@ -22,10 +22,9 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rspress/core": "^2.0.0-rc.1",
|
"@rspress/core": "^2.0.0",
|
||||||
"@rspress/plugin-client-redirects": "^2.0.0-alpha.12",
|
"@rspress/plugin-client-redirects": "^2.0.0",
|
||||||
"@rspress/plugin-preview": "^2.0.0-beta.35",
|
"@rspress/plugin-sitemap": "^2.0.0",
|
||||||
"@rspress/plugin-sitemap": "^2.0.0-beta.23",
|
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,8 @@
|
|||||||
tag-message = "chore: Release v{{version}}"
|
tag-message = "chore: Release v{{version}}"
|
||||||
|
tag-prefix = ""
|
||||||
|
shared-version = true
|
||||||
|
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
sign-commit = true
|
||||||
|
sign-tag = true
|
||||||
|
|||||||
+4
-2
@@ -1,5 +1,4 @@
|
|||||||
import { defineConfig } from '@rspress/core';
|
import { defineConfig } from '@rspress/core';
|
||||||
import { pluginPreview } from '@rspress/plugin-preview';
|
|
||||||
import { pluginSitemap } from '@rspress/plugin-sitemap';
|
import { pluginSitemap } from '@rspress/plugin-sitemap';
|
||||||
import { pluginClientRedirects } from '@rspress/plugin-client-redirects';
|
import { pluginClientRedirects } from '@rspress/plugin-client-redirects';
|
||||||
|
|
||||||
@@ -41,7 +40,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
plugins: [pluginPreview(), pluginSitemap({
|
plugins: [pluginSitemap({
|
||||||
siteUrl: 'https://continuwuity.org', // TODO: Set automatically in build pipeline
|
siteUrl: 'https://continuwuity.org', // TODO: Set automatically in build pipeline
|
||||||
}),
|
}),
|
||||||
pluginClientRedirects({
|
pluginClientRedirects({
|
||||||
@@ -54,6 +53,9 @@ export default defineConfig({
|
|||||||
}, {
|
}, {
|
||||||
from: '/server_reference',
|
from: '/server_reference',
|
||||||
to: '/reference/server'
|
to: '/reference/server'
|
||||||
|
}, {
|
||||||
|
from: '/community$',
|
||||||
|
to: '/community/guidelines'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})],
|
})],
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ serde-saphyr.workspace = true
|
|||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
tracing-subscriber.workspace = true
|
tracing-subscriber.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
ctor.workspace = true
|
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -819,32 +819,6 @@ pub(super) async fn time(&self) -> Result {
|
|||||||
self.write_str(&now).await
|
self.write_str(&now).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[admin_command]
|
|
||||||
pub(super) async fn list_dependencies(&self, names: bool) -> Result {
|
|
||||||
if names {
|
|
||||||
let out = info::cargo::dependencies_names().join(" ");
|
|
||||||
return self.write_str(&out).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut out = String::new();
|
|
||||||
let deps = info::cargo::dependencies();
|
|
||||||
writeln!(out, "| name | version | features |")?;
|
|
||||||
writeln!(out, "| ---- | ------- | -------- |")?;
|
|
||||||
for (name, dep) in deps {
|
|
||||||
let version = dep.try_req().unwrap_or("*");
|
|
||||||
let feats = dep.req_features();
|
|
||||||
let feats = if !feats.is_empty() {
|
|
||||||
feats.join(" ")
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
writeln!(out, "| {name} | {version} | {feats} |")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.write_str(&out).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[admin_command]
|
#[admin_command]
|
||||||
pub(super) async fn database_stats(
|
pub(super) async fn database_stats(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -206,12 +206,6 @@ pub enum DebugCommand {
|
|||||||
/// Print the current time
|
/// Print the current time
|
||||||
Time,
|
Time,
|
||||||
|
|
||||||
/// List dependencies
|
|
||||||
ListDependencies {
|
|
||||||
#[arg(short, long)]
|
|
||||||
names: bool,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Get database statistics
|
/// Get database statistics
|
||||||
DatabaseStats {
|
DatabaseStats {
|
||||||
property: Option<String>,
|
property: Option<String>,
|
||||||
|
|||||||
@@ -30,11 +30,8 @@ pub(crate) use crate::{context::Context, utils::get_room_info};
|
|||||||
|
|
||||||
pub(crate) const PAGE_SIZE: usize = 100;
|
pub(crate) const PAGE_SIZE: usize = 100;
|
||||||
|
|
||||||
use ctor::{ctor, dtor};
|
|
||||||
|
|
||||||
conduwuit::mod_ctor! {}
|
conduwuit::mod_ctor! {}
|
||||||
conduwuit::mod_dtor! {}
|
conduwuit::mod_dtor! {}
|
||||||
conduwuit::rustc_flags_capture! {}
|
|
||||||
|
|
||||||
pub use crate::admin::AdminCommand;
|
pub use crate::admin::AdminCommand;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::{fmt::Write, path::PathBuf, sync::Arc};
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Result, info,
|
Err, Result,
|
||||||
utils::{stream::IterStream, time},
|
utils::{stream::IterStream, time},
|
||||||
warn,
|
warn,
|
||||||
};
|
};
|
||||||
@@ -59,34 +59,6 @@ pub(super) async fn reload_config(&self, path: Option<PathBuf>) -> Result {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[admin_command]
|
|
||||||
pub(super) async fn list_features(&self, available: bool, enabled: bool, comma: bool) -> Result {
|
|
||||||
let delim = if comma { "," } else { " " };
|
|
||||||
if enabled && !available {
|
|
||||||
let features = info::rustc::features().join(delim);
|
|
||||||
let out = format!("`\n{features}\n`");
|
|
||||||
return self.write_str(&out).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
if available && !enabled {
|
|
||||||
let features = info::cargo::features().join(delim);
|
|
||||||
let out = format!("`\n{features}\n`");
|
|
||||||
return self.write_str(&out).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut features = String::new();
|
|
||||||
let enabled = info::rustc::features();
|
|
||||||
let available = info::cargo::features();
|
|
||||||
for feature in available {
|
|
||||||
let active = enabled.contains(&feature.as_str());
|
|
||||||
let emoji = if active { "✅" } else { "❌" };
|
|
||||||
let remark = if active { "[enabled]" } else { "" };
|
|
||||||
writeln!(features, "{emoji} {feature} {remark}")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.write_str(&features).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[admin_command]
|
#[admin_command]
|
||||||
pub(super) async fn memory_usage(&self) -> Result {
|
pub(super) async fn memory_usage(&self) -> Result {
|
||||||
let services_usage = self.services.memory_usage().await?;
|
let services_usage = self.services.memory_usage().await?;
|
||||||
|
|||||||
@@ -21,18 +21,6 @@ pub enum ServerCommand {
|
|||||||
path: Option<PathBuf>,
|
path: Option<PathBuf>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// List the features built into the server
|
|
||||||
ListFeatures {
|
|
||||||
#[arg(short, long)]
|
|
||||||
available: bool,
|
|
||||||
|
|
||||||
#[arg(short, long)]
|
|
||||||
enabled: bool,
|
|
||||||
|
|
||||||
#[arg(short, long)]
|
|
||||||
comma: bool,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Print database memory usage statistics
|
/// Print database memory usage statistics
|
||||||
MemoryUsage,
|
MemoryUsage,
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,7 @@ use std::{
|
|||||||
fmt::Write as _,
|
fmt::Write as _,
|
||||||
};
|
};
|
||||||
|
|
||||||
use api::client::{
|
use api::client::{full_user_deactivate, join_room_by_id_helper, leave_room, remote_leave_room};
|
||||||
full_user_deactivate, join_room_by_id_helper, leave_all_rooms, leave_room, remote_leave_room,
|
|
||||||
update_avatar_url, update_displayname,
|
|
||||||
};
|
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Result, debug, debug_warn, error, info, is_equal_to,
|
Err, Result, debug, debug_warn, error, info, is_equal_to,
|
||||||
matrix::{Event, pdu::PduBuilder},
|
matrix::{Event, pdu::PduBuilder},
|
||||||
@@ -227,9 +224,6 @@ pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) ->
|
|||||||
full_user_deactivate(self.services, &user_id, &all_joined_rooms)
|
full_user_deactivate(self.services, &user_id, &all_joined_rooms)
|
||||||
.boxed()
|
.boxed()
|
||||||
.await?;
|
.await?;
|
||||||
update_displayname(self.services, &user_id, None, &all_joined_rooms).await;
|
|
||||||
update_avatar_url(self.services, &user_id, None, None, &all_joined_rooms).await;
|
|
||||||
leave_all_rooms(self.services, &user_id).await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.write_str(&format!("User {user_id} has been deactivated"))
|
self.write_str(&format!("User {user_id} has been deactivated"))
|
||||||
@@ -406,10 +400,6 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
|
|||||||
full_user_deactivate(self.services, &user_id, &all_joined_rooms)
|
full_user_deactivate(self.services, &user_id, &all_joined_rooms)
|
||||||
.boxed()
|
.boxed()
|
||||||
.await?;
|
.await?;
|
||||||
update_displayname(self.services, &user_id, None, &all_joined_rooms).await;
|
|
||||||
update_avatar_url(self.services, &user_id, None, None, &all_joined_rooms)
|
|
||||||
.await;
|
|
||||||
leave_all_rooms(self.services, &user_id).await;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,6 @@ serde.workspace = true
|
|||||||
sha1.workspace = true
|
sha1.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
ctor.workspace = true
|
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
pub mod rooms;
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
use axum::extract::State;
|
||||||
|
use conduwuit::{Err, Result, info, utils::ReadyExt, warn};
|
||||||
|
use futures::{FutureExt, StreamExt};
|
||||||
|
use ruma::{
|
||||||
|
OwnedRoomAliasId, continuwuity_admin_api::rooms,
|
||||||
|
events::room::message::RoomMessageEventContent,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{Ruma, client::leave_room};
|
||||||
|
|
||||||
|
/// # `PUT /_continuwuity/admin/rooms/{roomID}/ban`
|
||||||
|
///
|
||||||
|
/// Bans or unbans a room.
|
||||||
|
pub(crate) async fn ban_room(
|
||||||
|
State(services): State<crate::State>,
|
||||||
|
body: Ruma<rooms::ban::v1::Request>,
|
||||||
|
) -> Result<rooms::ban::v1::Response> {
|
||||||
|
let sender_user = body.sender_user();
|
||||||
|
if !services.users.is_admin(sender_user).await {
|
||||||
|
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if body.banned {
|
||||||
|
// Don't ban again if already banned
|
||||||
|
if services.rooms.metadata.is_banned(&body.room_id).await {
|
||||||
|
return Err!(Request(InvalidParam("Room is already banned")));
|
||||||
|
}
|
||||||
|
info!(%sender_user, "Banning room {}", body.room_id);
|
||||||
|
|
||||||
|
services
|
||||||
|
.admin
|
||||||
|
.notice(&format!("{sender_user} banned {} (ban in progress)", body.room_id))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let mut users = services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.room_members(&body.room_id)
|
||||||
|
.map(ToOwned::to_owned)
|
||||||
|
.ready_filter(|user| services.globals.user_is_local(user))
|
||||||
|
.boxed();
|
||||||
|
let mut evicted = Vec::new();
|
||||||
|
let mut failed_evicted = Vec::new();
|
||||||
|
|
||||||
|
while let Some(ref user_id) = users.next().await {
|
||||||
|
info!("Evicting user {} from room {}", user_id, body.room_id);
|
||||||
|
match leave_room(&services, user_id, &body.room_id, None)
|
||||||
|
.boxed()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
| Ok(()) => {
|
||||||
|
services.rooms.state_cache.forget(&body.room_id, user_id);
|
||||||
|
evicted.push(user_id.clone());
|
||||||
|
},
|
||||||
|
| Err(e) => {
|
||||||
|
warn!("Failed to evict user {} from room {}: {}", user_id, body.room_id, e);
|
||||||
|
failed_evicted.push(user_id.clone());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let aliases: Vec<OwnedRoomAliasId> = services
|
||||||
|
.rooms
|
||||||
|
.alias
|
||||||
|
.local_aliases_for_room(&body.room_id)
|
||||||
|
.map(ToOwned::to_owned)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.await;
|
||||||
|
for alias in &aliases {
|
||||||
|
info!("Removing alias {} for banned room {}", alias, body.room_id);
|
||||||
|
services
|
||||||
|
.rooms
|
||||||
|
.alias
|
||||||
|
.remove_alias(alias, &services.globals.server_user)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
services.rooms.directory.set_not_public(&body.room_id); // remove from the room directory
|
||||||
|
services.rooms.metadata.ban_room(&body.room_id, true); // prevent further joins
|
||||||
|
services.rooms.metadata.disable_room(&body.room_id, true); // disable federation
|
||||||
|
|
||||||
|
services
|
||||||
|
.admin
|
||||||
|
.notice(&format!(
|
||||||
|
"Finished banning {}: Removed {} users ({} failed) and {} aliases",
|
||||||
|
body.room_id,
|
||||||
|
evicted.len(),
|
||||||
|
failed_evicted.len(),
|
||||||
|
aliases.len()
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
if !evicted.is_empty() || !failed_evicted.is_empty() || !aliases.is_empty() {
|
||||||
|
let msg = services
|
||||||
|
.admin
|
||||||
|
.text_or_file(RoomMessageEventContent::text_markdown(format!(
|
||||||
|
"Removed users:\n{}\n\nFailed to remove users:\n{}\n\nRemoved aliases: {}",
|
||||||
|
evicted
|
||||||
|
.iter()
|
||||||
|
.map(|u| u.as_str())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n"),
|
||||||
|
failed_evicted
|
||||||
|
.iter()
|
||||||
|
.map(|u| u.as_str())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n"),
|
||||||
|
aliases
|
||||||
|
.iter()
|
||||||
|
.map(|a| a.as_str())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", "),
|
||||||
|
)))
|
||||||
|
.await;
|
||||||
|
services.admin.send_message(msg).await.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(rooms::ban::v1::Response::new(evicted, failed_evicted, aliases))
|
||||||
|
} else {
|
||||||
|
// Don't unban if not banned
|
||||||
|
if !services.rooms.metadata.is_banned(&body.room_id).await {
|
||||||
|
return Err!(Request(InvalidParam("Room is not banned")));
|
||||||
|
}
|
||||||
|
info!(%sender_user, "Unbanning room {}", body.room_id);
|
||||||
|
services.rooms.metadata.disable_room(&body.room_id, false);
|
||||||
|
services.rooms.metadata.ban_room(&body.room_id, false);
|
||||||
|
services
|
||||||
|
.admin
|
||||||
|
.notice(&format!("{sender_user} unbanned {}", body.room_id))
|
||||||
|
.await;
|
||||||
|
Ok(rooms::ban::v1::Response::new(Vec::new(), Vec::new(), Vec::new()))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
use axum::extract::State;
|
||||||
|
use conduwuit::{Err, Result};
|
||||||
|
use futures::StreamExt;
|
||||||
|
use ruma::{OwnedRoomId, continuwuity_admin_api::rooms};
|
||||||
|
|
||||||
|
use crate::Ruma;
|
||||||
|
|
||||||
|
/// # `GET /_continuwuity/admin/rooms/list`
|
||||||
|
///
|
||||||
|
/// Lists all rooms known to this server, excluding banned ones.
|
||||||
|
pub(crate) async fn list_rooms(
|
||||||
|
State(services): State<crate::State>,
|
||||||
|
body: Ruma<rooms::list::v1::Request>,
|
||||||
|
) -> Result<rooms::list::v1::Response> {
|
||||||
|
let sender_user = body.sender_user();
|
||||||
|
if !services.users.is_admin(sender_user).await {
|
||||||
|
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rooms: Vec<OwnedRoomId> = services
|
||||||
|
.rooms
|
||||||
|
.metadata
|
||||||
|
.iter_ids()
|
||||||
|
.filter_map(|room_id| async move {
|
||||||
|
if !services.rooms.metadata.is_banned(room_id).await {
|
||||||
|
Some(room_id.to_owned())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
.await;
|
||||||
|
rooms.sort();
|
||||||
|
Ok(rooms::list::v1::Response::new(rooms))
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod ban;
|
||||||
|
pub mod list;
|
||||||
+29
-29
@@ -26,6 +26,7 @@ use ruma::{
|
|||||||
events::{
|
events::{
|
||||||
GlobalAccountDataEventType, StateEventType,
|
GlobalAccountDataEventType, StateEventType,
|
||||||
room::{
|
room::{
|
||||||
|
member::{MembershipState, RoomMemberEventContent},
|
||||||
message::RoomMessageEventContent,
|
message::RoomMessageEventContent,
|
||||||
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
||||||
},
|
},
|
||||||
@@ -815,9 +816,6 @@ pub(crate) async fn deactivate_route(
|
|||||||
.collect()
|
.collect()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
super::update_displayname(&services, sender_user, None, &all_joined_rooms).await;
|
|
||||||
super::update_avatar_url(&services, sender_user, None, None, &all_joined_rooms).await;
|
|
||||||
|
|
||||||
full_user_deactivate(&services, sender_user, &all_joined_rooms)
|
full_user_deactivate(&services, sender_user, &all_joined_rooms)
|
||||||
.boxed()
|
.boxed()
|
||||||
.await?;
|
.await?;
|
||||||
@@ -907,9 +905,6 @@ pub async fn full_user_deactivate(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
services.users.deactivate_account(user_id).await.ok();
|
services.users.deactivate_account(user_id).await.ok();
|
||||||
|
|
||||||
super::update_displayname(services, user_id, None, all_joined_rooms).await;
|
|
||||||
super::update_avatar_url(services, user_id, None, None, all_joined_rooms).await;
|
|
||||||
|
|
||||||
services
|
services
|
||||||
.users
|
.users
|
||||||
.all_profile_keys(user_id)
|
.all_profile_keys(user_id)
|
||||||
@@ -918,9 +913,11 @@ pub async fn full_user_deactivate(
|
|||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
for room_id in all_joined_rooms {
|
// TODO: Rescind all user invites
|
||||||
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
|
||||||
|
|
||||||
|
let mut pdu_queue: Vec<(PduBuilder, &OwnedRoomId)> = Vec::new();
|
||||||
|
|
||||||
|
for room_id in all_joined_rooms {
|
||||||
let room_power_levels = services
|
let room_power_levels = services
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
@@ -948,30 +945,33 @@ pub async fn full_user_deactivate(
|
|||||||
if user_can_demote_self {
|
if user_can_demote_self {
|
||||||
let mut power_levels_content = room_power_levels.unwrap_or_default();
|
let mut power_levels_content = room_power_levels.unwrap_or_default();
|
||||||
power_levels_content.users.remove(user_id);
|
power_levels_content.users.remove(user_id);
|
||||||
|
let pl_evt = PduBuilder::state(String::new(), &power_levels_content);
|
||||||
// ignore errors so deactivation doesn't fail
|
pdu_queue.push((pl_evt, room_id));
|
||||||
match services
|
|
||||||
.rooms
|
|
||||||
.timeline
|
|
||||||
.build_and_append_pdu(
|
|
||||||
PduBuilder::state(String::new(), &power_levels_content),
|
|
||||||
user_id,
|
|
||||||
Some(room_id),
|
|
||||||
&state_lock,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
| Err(e) => {
|
|
||||||
warn!(%room_id, %user_id, "Failed to demote user's own power level: {e}");
|
|
||||||
},
|
|
||||||
| _ => {
|
|
||||||
info!("Demoted {user_id} in {room_id} as part of account deactivation");
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Leave the room
|
||||||
|
pdu_queue.push((
|
||||||
|
PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
|
||||||
|
avatar_url: None,
|
||||||
|
blurhash: None,
|
||||||
|
membership: MembershipState::Leave,
|
||||||
|
displayname: None,
|
||||||
|
join_authorized_via_users_server: None,
|
||||||
|
reason: None,
|
||||||
|
is_direct: None,
|
||||||
|
third_party_invite: None,
|
||||||
|
redact_events: None,
|
||||||
|
}),
|
||||||
|
room_id,
|
||||||
|
));
|
||||||
|
|
||||||
|
// TODO: Redact all messages sent by the user in the room
|
||||||
}
|
}
|
||||||
|
|
||||||
super::leave_all_rooms(services, user_id).boxed().await;
|
super::update_all_rooms(services, pdu_queue, user_id).await;
|
||||||
|
for room_id in all_joined_rooms {
|
||||||
|
services.rooms.state_cache.forget(room_id, user_id);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,8 +91,11 @@ pub(crate) async fn upload_keys_route(
|
|||||||
.users
|
.users
|
||||||
.get_device_keys(sender_user, sender_device)
|
.get_device_keys(sender_user, sender_device)
|
||||||
.await
|
.await
|
||||||
|
.and_then(|keys| keys.deserialize().map_err(Into::into))
|
||||||
{
|
{
|
||||||
if existing_keys.json().get() == device_keys.json().get() {
|
// NOTE: also serves as a workaround for a nheko bug which omits cross-signing
|
||||||
|
// NOTE: signatures when re-uploading the same DeviceKeys.
|
||||||
|
if existing_keys.keys == deser_device_keys.keys {
|
||||||
debug!(
|
debug!(
|
||||||
%sender_user,
|
%sender_user,
|
||||||
%sender_device,
|
%sender_device,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use conduwuit::{
|
|||||||
utils::{
|
utils::{
|
||||||
self, shuffle,
|
self, shuffle,
|
||||||
stream::{IterStream, ReadyExt},
|
stream::{IterStream, ReadyExt},
|
||||||
|
to_canonical_object,
|
||||||
},
|
},
|
||||||
warn,
|
warn,
|
||||||
};
|
};
|
||||||
@@ -1012,40 +1013,55 @@ async fn make_join_request(
|
|||||||
trace!("make_join response: {:?}", make_join_response);
|
trace!("make_join response: {:?}", make_join_response);
|
||||||
make_join_counter = make_join_counter.saturating_add(1);
|
make_join_counter = make_join_counter.saturating_add(1);
|
||||||
|
|
||||||
if let Err(ref e) = make_join_response {
|
match make_join_response {
|
||||||
if matches!(
|
| Ok(response) => {
|
||||||
e.kind(),
|
info!("Received make_join response from {remote_server}");
|
||||||
ErrorKind::IncompatibleRoomVersion { .. } | ErrorKind::UnsupportedRoomVersion
|
if let Err(e) = validate_remote_member_event_stub(
|
||||||
) {
|
&MembershipState::Join,
|
||||||
incompatible_room_version_count =
|
sender_user,
|
||||||
incompatible_room_version_count.saturating_add(1);
|
room_id,
|
||||||
}
|
&to_canonical_object(&response.event)?,
|
||||||
|
) {
|
||||||
|
warn!("make_join response from {remote_server} failed validation: {e}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
make_join_response_and_server = Ok((response, remote_server.clone()));
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
| Err(e) => {
|
||||||
|
info!("make_join request to {remote_server} failed: {e}");
|
||||||
|
if matches!(
|
||||||
|
e.kind(),
|
||||||
|
ErrorKind::IncompatibleRoomVersion { .. } | ErrorKind::UnsupportedRoomVersion
|
||||||
|
) {
|
||||||
|
incompatible_room_version_count =
|
||||||
|
incompatible_room_version_count.saturating_add(1);
|
||||||
|
}
|
||||||
|
|
||||||
if incompatible_room_version_count > 15 {
|
if incompatible_room_version_count > 15 {
|
||||||
info!(
|
info!(
|
||||||
"15 servers have responded with M_INCOMPATIBLE_ROOM_VERSION or \
|
"15 servers have responded with M_INCOMPATIBLE_ROOM_VERSION or \
|
||||||
M_UNSUPPORTED_ROOM_VERSION, assuming that conduwuit does not support the \
|
M_UNSUPPORTED_ROOM_VERSION, assuming that conduwuit does not support \
|
||||||
room version {room_id}: {e}"
|
the room version {room_id}: {e}"
|
||||||
);
|
);
|
||||||
make_join_response_and_server =
|
make_join_response_and_server =
|
||||||
Err!(BadServerResponse("Room version is not supported by Conduwuit"));
|
Err!(BadServerResponse("Room version is not supported by Conduwuit"));
|
||||||
return make_join_response_and_server;
|
return make_join_response_and_server;
|
||||||
}
|
}
|
||||||
|
|
||||||
if make_join_counter > 40 {
|
if make_join_counter > 40 {
|
||||||
warn!(
|
warn!(
|
||||||
"40 servers failed to provide valid make_join response, assuming no server \
|
"40 servers failed to provide valid make_join response, assuming no \
|
||||||
can assist in joining."
|
server can assist in joining."
|
||||||
);
|
);
|
||||||
make_join_response_and_server =
|
make_join_response_and_server =
|
||||||
Err!(BadServerResponse("No server available to assist in joining."));
|
Err!(BadServerResponse("No server available to assist in joining."));
|
||||||
|
|
||||||
return make_join_response_and_server;
|
return make_join_response_and_server;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
make_join_response_and_server = make_join_response.map(|r| (r, remote_server.clone()));
|
|
||||||
|
|
||||||
if make_join_response_and_server.is_ok() {
|
if make_join_response_and_server.is_ok() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use conduwuit::{
|
|||||||
},
|
},
|
||||||
result::FlatOk,
|
result::FlatOk,
|
||||||
trace,
|
trace,
|
||||||
utils::{self, shuffle, stream::IterStream},
|
utils::{self, shuffle, stream::IterStream, to_canonical_object},
|
||||||
warn,
|
warn,
|
||||||
};
|
};
|
||||||
use futures::{FutureExt, StreamExt};
|
use futures::{FutureExt, StreamExt};
|
||||||
@@ -741,6 +741,17 @@ async fn make_knock_request(
|
|||||||
|
|
||||||
trace!("make_knock response: {make_knock_response:?}");
|
trace!("make_knock response: {make_knock_response:?}");
|
||||||
make_knock_counter = make_knock_counter.saturating_add(1);
|
make_knock_counter = make_knock_counter.saturating_add(1);
|
||||||
|
if let Ok(r) = &make_knock_response {
|
||||||
|
if let Err(e) = validate_remote_member_event_stub(
|
||||||
|
&MembershipState::Knock,
|
||||||
|
sender_user,
|
||||||
|
room_id,
|
||||||
|
&to_canonical_object(&r.event)?,
|
||||||
|
) {
|
||||||
|
warn!("make_knock response from {remote_server} failed validation: {e}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
make_knock_response_and_server = make_knock_response.map(|r| (r, remote_server.clone()));
|
make_knock_response_and_server = make_knock_response.map(|r| (r, remote_server.clone()));
|
||||||
|
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ pub(crate) fn validate_remote_member_event_stub(
|
|||||||
};
|
};
|
||||||
if event_membership != &membership.as_str() {
|
if event_membership != &membership.as_str() {
|
||||||
return Err!(BadServerResponse(
|
return Err!(BadServerResponse(
|
||||||
"Remote server returned member event with incorrect room_id"
|
"Remote server returned member event with incorrect membership type"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -371,11 +371,3 @@ pub(crate) async fn is_ignored_invite(
|
|||||||
.invite_filter_level(&sender_user, recipient_user)
|
.invite_filter_level(&sender_user, recipient_user)
|
||||||
.await == FilterLevel::Ignore
|
.await == FilterLevel::Ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(debug_assertions, ctor::ctor)]
|
|
||||||
fn _is_sorted() {
|
|
||||||
debug_assert!(
|
|
||||||
IGNORED_MESSAGE_TYPES.is_sorted(),
|
|
||||||
"IGNORED_MESSAGE_TYPES must be sorted by the developer"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
+3
-2
@@ -1,12 +1,13 @@
|
|||||||
#![type_length_limit = "16384"] //TODO: reduce me
|
#![type_length_limit = "16384"] //TODO: reduce me
|
||||||
#![allow(clippy::toplevel_ref_arg)]
|
#![allow(clippy::toplevel_ref_arg)]
|
||||||
|
|
||||||
|
extern crate conduwuit_core as conduwuit;
|
||||||
|
extern crate conduwuit_service as service;
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod router;
|
pub mod router;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|
||||||
extern crate conduwuit_core as conduwuit;
|
pub mod admin;
|
||||||
extern crate conduwuit_service as service;
|
|
||||||
|
|
||||||
pub(crate) use self::router::{Ruma, RumaResponse, State};
|
pub(crate) use self::router::{Ruma, RumaResponse, State};
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -17,7 +17,7 @@ use http::{Uri, uri};
|
|||||||
|
|
||||||
use self::handler::RouterExt;
|
use self::handler::RouterExt;
|
||||||
pub(super) use self::{args::Args as Ruma, response::RumaResponse};
|
pub(super) use self::{args::Args as Ruma, response::RumaResponse};
|
||||||
use crate::{client, server};
|
use crate::{admin, client, server};
|
||||||
|
|
||||||
pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||||
let config = &server.config;
|
let config = &server.config;
|
||||||
@@ -187,7 +187,9 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
|||||||
.route("/_conduwuit/server_version", get(client::conduwuit_server_version))
|
.route("/_conduwuit/server_version", get(client::conduwuit_server_version))
|
||||||
.route("/_continuwuity/server_version", get(client::conduwuit_server_version))
|
.route("/_continuwuity/server_version", get(client::conduwuit_server_version))
|
||||||
.ruma_route(&client::room_initial_sync_route)
|
.ruma_route(&client::room_initial_sync_route)
|
||||||
.route("/client/server.json", get(client::syncv3_client_server_json));
|
.route("/client/server.json", get(client::syncv3_client_server_json))
|
||||||
|
.ruma_route(&admin::rooms::ban::ban_room)
|
||||||
|
.ruma_route(&admin::rooms::list::list_rooms);
|
||||||
|
|
||||||
if config.allow_federation {
|
if config.allow_federation {
|
||||||
router = router
|
router = router
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::cmp;
|
|||||||
|
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Event, PduCount, Result,
|
Err, Event, PduCount, Result, info,
|
||||||
result::LogErr,
|
result::LogErr,
|
||||||
utils::{IterStream, ReadyExt, stream::TryTools},
|
utils::{IterStream, ReadyExt, stream::TryTools},
|
||||||
};
|
};
|
||||||
@@ -34,6 +34,18 @@ pub(crate) async fn get_backfill_route(
|
|||||||
}
|
}
|
||||||
.check()
|
.check()
|
||||||
.await?;
|
.await?;
|
||||||
|
if !services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.server_in_room(services.globals.server_name(), &body.room_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
info!(
|
||||||
|
origin = body.origin().as_str(),
|
||||||
|
"Refusing to serve backfill for room we aren't participating in"
|
||||||
|
);
|
||||||
|
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||||
|
}
|
||||||
|
|
||||||
let limit = body
|
let limit = body
|
||||||
.limit
|
.limit
|
||||||
|
|||||||
+14
-1
@@ -1,5 +1,5 @@
|
|||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduwuit::{Result, err};
|
use conduwuit::{Err, Result, err, info};
|
||||||
use ruma::{MilliSecondsSinceUnixEpoch, RoomId, api::federation::event::get_event};
|
use ruma::{MilliSecondsSinceUnixEpoch, RoomId, api::federation::event::get_event};
|
||||||
|
|
||||||
use super::AccessCheck;
|
use super::AccessCheck;
|
||||||
@@ -38,6 +38,19 @@ pub(crate) async fn get_event_route(
|
|||||||
.check()
|
.check()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
if !services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.server_in_room(services.globals.server_name(), room_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
info!(
|
||||||
|
origin = body.origin().as_str(),
|
||||||
|
"Refusing to serve state for room we aren't participating in"
|
||||||
|
);
|
||||||
|
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||||
|
}
|
||||||
|
|
||||||
Ok(get_event::v1::Response {
|
Ok(get_event::v1::Response {
|
||||||
origin: services.globals.server_name().to_owned(),
|
origin: services.globals.server_name().to_owned(),
|
||||||
origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
|
origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::{borrow::Borrow, iter::once};
|
use std::{borrow::Borrow, iter::once};
|
||||||
|
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduwuit::{Error, Result, utils::stream::ReadyExt};
|
use conduwuit::{Err, Error, Result, info, utils::stream::ReadyExt};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
RoomId,
|
RoomId,
|
||||||
@@ -29,6 +29,19 @@ pub(crate) async fn get_event_authorization_route(
|
|||||||
.check()
|
.check()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
if !services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.server_in_room(services.globals.server_name(), &body.room_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
info!(
|
||||||
|
origin = body.origin().as_str(),
|
||||||
|
"Refusing to serve state for room we aren't participating in"
|
||||||
|
);
|
||||||
|
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||||
|
}
|
||||||
|
|
||||||
let event = services
|
let event = services
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduwuit::{Result, debug, debug_error, utils::to_canonical_object};
|
use conduwuit::{Err, Result, debug, debug_error, info, utils::to_canonical_object};
|
||||||
use ruma::api::federation::event::get_missing_events;
|
use ruma::api::federation::event::get_missing_events;
|
||||||
|
|
||||||
use super::AccessCheck;
|
use super::AccessCheck;
|
||||||
@@ -26,6 +26,19 @@ pub(crate) async fn get_missing_events_route(
|
|||||||
.check()
|
.check()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
if !services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.server_in_room(services.globals.server_name(), &body.room_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
info!(
|
||||||
|
origin = body.origin().as_str(),
|
||||||
|
"Refusing to serve state for room we aren't participating in"
|
||||||
|
);
|
||||||
|
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||||
|
}
|
||||||
|
|
||||||
let limit = body
|
let limit = body
|
||||||
.limit
|
.limit
|
||||||
.try_into()
|
.try_into()
|
||||||
@@ -66,12 +79,12 @@ pub(crate) async fn get_missing_events_route(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i = i.saturating_add(1);
|
||||||
let Ok(event) = to_canonical_object(&pdu) else {
|
let Ok(event) = to_canonical_object(&pdu) else {
|
||||||
debug_error!(
|
debug_error!(
|
||||||
body.origin = body.origin.as_ref().map(tracing::field::display),
|
body.origin = body.origin.as_ref().map(tracing::field::display),
|
||||||
"Failed to convert PDU in database to canonical JSON: {pdu:?}"
|
"Failed to convert PDU in database to canonical JSON: {pdu:?}"
|
||||||
);
|
);
|
||||||
i = i.saturating_add(1);
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Result,
|
Err, Result, info,
|
||||||
utils::stream::{BroadbandExt, IterStream},
|
utils::stream::{BroadbandExt, IterStream},
|
||||||
};
|
};
|
||||||
use conduwuit_service::rooms::spaces::{
|
use conduwuit_service::rooms::spaces::{
|
||||||
@@ -23,6 +23,19 @@ pub(crate) async fn get_hierarchy_route(
|
|||||||
return Err!(Request(NotFound("Room does not exist.")));
|
return Err!(Request(NotFound("Room does not exist.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.server_in_room(services.globals.server_name(), &body.room_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
info!(
|
||||||
|
origin = body.origin().as_str(),
|
||||||
|
"Refusing to serve state for room we aren't participating in"
|
||||||
|
);
|
||||||
|
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||||
|
}
|
||||||
|
|
||||||
let room_id = &body.room_id;
|
let room_id = &body.room_id;
|
||||||
let suggested_only = body.suggested_only;
|
let suggested_only = body.suggested_only;
|
||||||
let ref identifier = Identifier::ServerName(body.origin());
|
let ref identifier = Identifier::ServerName(body.origin());
|
||||||
|
|||||||
@@ -30,6 +30,18 @@ pub(crate) async fn create_join_event_template_route(
|
|||||||
if !services.rooms.metadata.exists(&body.room_id).await {
|
if !services.rooms.metadata.exists(&body.room_id).await {
|
||||||
return Err!(Request(NotFound("Room is unknown to this server.")));
|
return Err!(Request(NotFound("Room is unknown to this server.")));
|
||||||
}
|
}
|
||||||
|
if !services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.server_in_room(services.globals.server_name(), &body.room_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
info!(
|
||||||
|
origin = body.origin().as_str(),
|
||||||
|
"Refusing to serve make_join for room we aren't participating in"
|
||||||
|
);
|
||||||
|
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||||
|
}
|
||||||
|
|
||||||
if body.user_id.server_name() != body.origin() {
|
if body.user_id.server_name() != body.origin() {
|
||||||
return Err!(Request(BadJson("Not allowed to join on behalf of another server/user.")));
|
return Err!(Request(BadJson("Not allowed to join on behalf of another server/user.")));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use RoomVersionId::*;
|
use RoomVersionId::*;
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduwuit::{Err, Error, Result, debug_warn, matrix::pdu::PduBuilder, warn};
|
use conduwuit::{Err, Error, 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},
|
||||||
@@ -20,6 +20,18 @@ pub(crate) async fn create_knock_event_template_route(
|
|||||||
if !services.rooms.metadata.exists(&body.room_id).await {
|
if !services.rooms.metadata.exists(&body.room_id).await {
|
||||||
return Err!(Request(NotFound("Room is unknown to this server.")));
|
return Err!(Request(NotFound("Room is unknown to this server.")));
|
||||||
}
|
}
|
||||||
|
if !services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.server_in_room(services.globals.server_name(), &body.room_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
info!(
|
||||||
|
origin = body.origin().as_str(),
|
||||||
|
"Refusing to serve make_knock for room we aren't participating in"
|
||||||
|
);
|
||||||
|
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||||
|
}
|
||||||
|
|
||||||
if body.user_id.server_name() != body.origin() {
|
if body.user_id.server_name() != body.origin() {
|
||||||
return Err!(Request(BadJson("Not allowed to knock on behalf of another server/user.")));
|
return Err!(Request(BadJson("Not allowed to knock on behalf of another server/user.")));
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduwuit::{Err, Result, matrix::pdu::PduBuilder};
|
use conduwuit::{Err, Result, info, matrix::pdu::PduBuilder};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::federation::membership::prepare_leave_event,
|
api::federation::membership::prepare_leave_event,
|
||||||
events::room::member::{MembershipState, RoomMemberEventContent},
|
events::room::member::{MembershipState, RoomMemberEventContent},
|
||||||
@@ -20,6 +20,19 @@ pub(crate) async fn create_leave_event_template_route(
|
|||||||
return Err!(Request(NotFound("Room is unknown to this server.")));
|
return Err!(Request(NotFound("Room is unknown to this server.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.server_in_room(services.globals.server_name(), &body.room_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
info!(
|
||||||
|
origin = body.origin().as_str(),
|
||||||
|
"Refusing to serve make_leave for room we aren't participating in"
|
||||||
|
);
|
||||||
|
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||||
|
}
|
||||||
|
|
||||||
if body.user_id.server_name() != body.origin() {
|
if body.user_id.server_name() != body.origin() {
|
||||||
return Err!(Request(Forbidden(
|
return Err!(Request(Forbidden(
|
||||||
"Not allowed to leave on behalf of another server/user."
|
"Not allowed to leave on behalf of another server/user."
|
||||||
|
|||||||
+14
-12
@@ -13,8 +13,7 @@ use conduwuit::{
|
|||||||
use conduwuit_service::Services;
|
use conduwuit_service::Services;
|
||||||
use futures::{FutureExt, StreamExt, TryStreamExt};
|
use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId,
|
CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, ServerName,
|
||||||
ServerName,
|
|
||||||
api::federation::membership::create_join_event,
|
api::federation::membership::create_join_event,
|
||||||
events::{
|
events::{
|
||||||
StateEventType,
|
StateEventType,
|
||||||
@@ -37,6 +36,18 @@ async fn create_join_event(
|
|||||||
if !services.rooms.metadata.exists(room_id).await {
|
if !services.rooms.metadata.exists(room_id).await {
|
||||||
return Err!(Request(NotFound("Room is unknown to this server.")));
|
return Err!(Request(NotFound("Room is unknown to this server.")));
|
||||||
}
|
}
|
||||||
|
if !services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.server_in_room(services.globals.server_name(), room_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
info!(
|
||||||
|
origin = origin.as_str(),
|
||||||
|
"Refusing to serve send_join for room we aren't participating in"
|
||||||
|
);
|
||||||
|
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||||
|
}
|
||||||
|
|
||||||
// ACL check origin server
|
// ACL check origin server
|
||||||
services
|
services
|
||||||
@@ -178,15 +189,6 @@ async fn create_join_event(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let origin: OwnedServerName = serde_json::from_value(
|
|
||||||
value
|
|
||||||
.get("origin")
|
|
||||||
.ok_or_else(|| err!(Request(BadJson("Event does not have an origin server name."))))?
|
|
||||||
.clone()
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
.map_err(|e| err!(Request(BadJson("Event has an invalid origin server name: {e}"))))?;
|
|
||||||
|
|
||||||
trace!("Signing send_join event");
|
trace!("Signing send_join event");
|
||||||
services
|
services
|
||||||
.server_keys
|
.server_keys
|
||||||
@@ -204,7 +206,7 @@ async fn create_join_event(
|
|||||||
let pdu_id = services
|
let pdu_id = services
|
||||||
.rooms
|
.rooms
|
||||||
.event_handler
|
.event_handler
|
||||||
.handle_incoming_pdu(&origin, room_id, &event_id, value.clone(), true)
|
.handle_incoming_pdu(sender.server_name(), room_id, &event_id, value.clone(), true)
|
||||||
.boxed()
|
.boxed()
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?;
|
.ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Result, err,
|
Err, Result, err, info,
|
||||||
matrix::{event::gen_event_id_canonical_json, pdu::PduEvent},
|
matrix::{event::gen_event_id_canonical_json, pdu::PduEvent},
|
||||||
warn,
|
warn,
|
||||||
};
|
};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
OwnedServerName, OwnedUserId,
|
OwnedUserId,
|
||||||
RoomVersionId::*,
|
RoomVersionId::*,
|
||||||
api::federation::knock::send_knock,
|
api::federation::knock::send_knock,
|
||||||
events::{
|
events::{
|
||||||
@@ -54,6 +54,19 @@ pub(crate) async fn create_knock_event_v1_route(
|
|||||||
return Err!(Request(NotFound("Room is unknown to this server.")));
|
return Err!(Request(NotFound("Room is unknown to this server.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.server_in_room(services.globals.server_name(), &body.room_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
info!(
|
||||||
|
origin = body.origin().as_str(),
|
||||||
|
"Refusing to serve send_knock for room we aren't participating in"
|
||||||
|
);
|
||||||
|
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||||
|
}
|
||||||
|
|
||||||
// ACL check origin server
|
// ACL check origin server
|
||||||
services
|
services
|
||||||
.rooms
|
.rooms
|
||||||
@@ -136,15 +149,6 @@ pub(crate) async fn create_knock_event_v1_route(
|
|||||||
return Err!(Request(InvalidParam("state_key does not match sender user of event.")));
|
return Err!(Request(InvalidParam("state_key does not match sender user of event.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
let origin: OwnedServerName = serde_json::from_value(
|
|
||||||
value
|
|
||||||
.get("origin")
|
|
||||||
.ok_or_else(|| err!(Request(BadJson("Event does not have an origin server name."))))?
|
|
||||||
.clone()
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
.map_err(|e| err!(Request(BadJson("Event has an invalid origin server name: {e}"))))?;
|
|
||||||
|
|
||||||
let mut event: JsonObject = serde_json::from_str(body.pdu.get())
|
let mut event: JsonObject = serde_json::from_str(body.pdu.get())
|
||||||
.map_err(|e| err!(Request(InvalidParam("Invalid knock event PDU: {e}"))))?;
|
.map_err(|e| err!(Request(InvalidParam("Invalid knock event PDU: {e}"))))?;
|
||||||
|
|
||||||
@@ -163,7 +167,7 @@ pub(crate) async fn create_knock_event_v1_route(
|
|||||||
let pdu_id = services
|
let pdu_id = services
|
||||||
.rooms
|
.rooms
|
||||||
.event_handler
|
.event_handler
|
||||||
.handle_incoming_pdu(&origin, &body.room_id, &event_id, value.clone(), true)
|
.handle_incoming_pdu(sender.server_name(), &body.room_id, &event_id, value.clone(), true)
|
||||||
.boxed()
|
.boxed()
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?;
|
.ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#![allow(deprecated)]
|
#![allow(deprecated)]
|
||||||
|
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduwuit::{Err, Result, err, matrix::event::gen_event_id_canonical_json};
|
use conduwuit::{Err, Result, err, info, matrix::event::gen_event_id_canonical_json};
|
||||||
use conduwuit_service::Services;
|
use conduwuit_service::Services;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
@@ -50,6 +50,19 @@ async fn create_leave_event(
|
|||||||
return Err!(Request(NotFound("Room is unknown to this server.")));
|
return Err!(Request(NotFound("Room is unknown to this server.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.server_in_room(services.globals.server_name(), room_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
info!(
|
||||||
|
origin = origin.as_str(),
|
||||||
|
"Refusing to serve backfill for room we aren't participating in"
|
||||||
|
);
|
||||||
|
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||||
|
}
|
||||||
|
|
||||||
// ACL check origin
|
// ACL check origin
|
||||||
services
|
services
|
||||||
.rooms
|
.rooms
|
||||||
|
|||||||
+14
-1
@@ -1,7 +1,7 @@
|
|||||||
use std::{borrow::Borrow, iter::once};
|
use std::{borrow::Borrow, iter::once};
|
||||||
|
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduwuit::{Result, at, err, utils::IterStream};
|
use conduwuit::{Err, Result, at, err, info, utils::IterStream};
|
||||||
use futures::{FutureExt, StreamExt, TryStreamExt};
|
use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||||
use ruma::{OwnedEventId, api::federation::event::get_room_state};
|
use ruma::{OwnedEventId, api::federation::event::get_room_state};
|
||||||
|
|
||||||
@@ -24,6 +24,19 @@ pub(crate) async fn get_room_state_route(
|
|||||||
.check()
|
.check()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
if !services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.server_in_room(services.globals.server_name(), &body.room_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
info!(
|
||||||
|
origin = body.origin().as_str(),
|
||||||
|
"Refusing to serve state for room we aren't participating in"
|
||||||
|
);
|
||||||
|
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||||
|
}
|
||||||
|
|
||||||
let shortstatehash = services
|
let shortstatehash = services
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::{borrow::Borrow, iter::once};
|
use std::{borrow::Borrow, iter::once};
|
||||||
|
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduwuit::{Result, at, err};
|
use conduwuit::{Err, Result, at, err, info};
|
||||||
use futures::{StreamExt, TryStreamExt};
|
use futures::{StreamExt, TryStreamExt};
|
||||||
use ruma::{OwnedEventId, api::federation::event::get_room_state_ids};
|
use ruma::{OwnedEventId, api::federation::event::get_room_state_ids};
|
||||||
|
|
||||||
@@ -25,6 +25,19 @@ pub(crate) async fn get_room_state_ids_route(
|
|||||||
.check()
|
.check()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
if !services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.server_in_room(services.globals.server_name(), &body.room_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
info!(
|
||||||
|
origin = body.origin().as_str(),
|
||||||
|
"Refusing to serve state for room we aren't participating in"
|
||||||
|
);
|
||||||
|
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||||
|
}
|
||||||
|
|
||||||
let shortstatehash = services
|
let shortstatehash = services
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
|
|||||||
+4
-13
@@ -1,6 +1,6 @@
|
|||||||
use conduwuit::{Err, Result, implement, is_false};
|
use conduwuit::{Err, Result, implement, is_false};
|
||||||
use conduwuit_service::Services;
|
use conduwuit_service::Services;
|
||||||
use futures::{FutureExt, StreamExt, future::OptionFuture, join};
|
use futures::{FutureExt, future::OptionFuture, join};
|
||||||
use ruma::{EventId, RoomId, ServerName};
|
use ruma::{EventId, RoomId, ServerName};
|
||||||
|
|
||||||
pub(super) struct AccessCheck<'a> {
|
pub(super) struct AccessCheck<'a> {
|
||||||
@@ -31,15 +31,6 @@ pub(super) async fn check(&self) -> Result {
|
|||||||
.state_cache
|
.state_cache
|
||||||
.server_in_room(self.origin, self.room_id);
|
.server_in_room(self.origin, self.room_id);
|
||||||
|
|
||||||
// if any user on our homeserver is trying to knock this room, we'll need to
|
|
||||||
// acknowledge bans or leaves
|
|
||||||
let user_is_knocking = self
|
|
||||||
.services
|
|
||||||
.rooms
|
|
||||||
.state_cache
|
|
||||||
.room_members_knocked(self.room_id)
|
|
||||||
.count();
|
|
||||||
|
|
||||||
let server_can_see: OptionFuture<_> = self
|
let server_can_see: OptionFuture<_> = self
|
||||||
.event_id
|
.event_id
|
||||||
.map(|event_id| {
|
.map(|event_id| {
|
||||||
@@ -51,14 +42,14 @@ pub(super) async fn check(&self) -> Result {
|
|||||||
})
|
})
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
let (world_readable, server_in_room, server_can_see, acl_check, user_is_knocking) =
|
let (world_readable, server_in_room, server_can_see, acl_check) =
|
||||||
join!(world_readable, server_in_room, server_can_see, acl_check, user_is_knocking);
|
join!(world_readable, server_in_room, server_can_see, acl_check);
|
||||||
|
|
||||||
if !acl_check {
|
if !acl_check {
|
||||||
return Err!(Request(Forbidden("Server access denied.")));
|
return Err!(Request(Forbidden("Server access denied.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !world_readable && !server_in_room && user_is_knocking == 0 {
|
if !world_readable && !server_in_room {
|
||||||
return Err!(Request(Forbidden("Server is not in room.")));
|
return Err!(Request(Forbidden("Server is not in room.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ type Key = ArrayVec<usize, KEY_SEGS>;
|
|||||||
const NAME_MAX: usize = 128;
|
const NAME_MAX: usize = 128;
|
||||||
const KEY_SEGS: usize = 8;
|
const KEY_SEGS: usize = 8;
|
||||||
|
|
||||||
#[crate::ctor]
|
#[ctor::ctor]
|
||||||
fn _static_initialization() {
|
fn _static_initialization() {
|
||||||
acq_epoch().expect("pre-initialization of jemalloc failed");
|
acq_epoch().expect("pre-initialization of jemalloc failed");
|
||||||
acq_epoch().expect("pre-initialization of jemalloc failed");
|
acq_epoch().expect("pre-initialization of jemalloc failed");
|
||||||
|
|||||||
+19
-8
@@ -53,8 +53,7 @@ use crate::{Result, err, error::Error, utils::sys};
|
|||||||
### For more information, see:
|
### For more information, see:
|
||||||
### https://continuwuity.org/configuration.html
|
### https://continuwuity.org/configuration.html
|
||||||
"#,
|
"#,
|
||||||
ignore = "config_paths catchall well_known tls blurhashing \
|
ignore = "config_paths catchall"
|
||||||
allow_invalid_tls_certificates_yes_i_know_what_the_fuck_i_am_doing_with_this_and_i_know_this_is_insecure antispam"
|
|
||||||
)]
|
)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
// Paths to config file(s). Not supposed to be set manually in the config file,
|
// Paths to config file(s). Not supposed to be set manually in the config file,
|
||||||
@@ -105,7 +104,7 @@ pub struct Config {
|
|||||||
#[serde(default = "default_port")]
|
#[serde(default = "default_port")]
|
||||||
port: ListeningPort,
|
port: ListeningPort,
|
||||||
|
|
||||||
// external structure; separate section
|
/// display: nested
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub tls: TlsConfig,
|
pub tls: TlsConfig,
|
||||||
|
|
||||||
@@ -724,7 +723,7 @@ pub struct Config {
|
|||||||
#[serde(default = "default_default_room_version")]
|
#[serde(default = "default_default_room_version")]
|
||||||
pub default_room_version: RoomVersionId,
|
pub default_room_version: RoomVersionId,
|
||||||
|
|
||||||
// external structure; separate section
|
/// display: nested
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub well_known: WellKnownConfig,
|
pub well_known: WellKnownConfig,
|
||||||
|
|
||||||
@@ -2030,19 +2029,22 @@ pub struct Config {
|
|||||||
/// etc. This is a hidden argument that should NOT be used in production as
|
/// etc. This is a hidden argument that should NOT be used in production as
|
||||||
/// it is highly insecure and I will personally yell at you if I catch you
|
/// it is highly insecure and I will personally yell at you if I catch you
|
||||||
/// using this.
|
/// using this.
|
||||||
|
///
|
||||||
|
/// display: hidden
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub allow_invalid_tls_certificates_yes_i_know_what_the_fuck_i_am_doing_with_this_and_i_know_this_is_insecure:
|
pub allow_invalid_tls_certificates_yes_i_know_what_the_fuck_i_am_doing_with_this_and_i_know_this_is_insecure:
|
||||||
bool,
|
bool,
|
||||||
|
|
||||||
// external structure; separate section
|
/// display: nested
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub ldap: LdapConfig,
|
pub ldap: LdapConfig,
|
||||||
|
|
||||||
/// Configuration for antispam support
|
/// Configuration for antispam support
|
||||||
|
/// display: nested
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub antispam: Option<Antispam>,
|
pub antispam: Option<Antispam>,
|
||||||
|
|
||||||
// external structure; separate section
|
/// display: nested
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub blurhashing: BlurhashConfig,
|
pub blurhashing: BlurhashConfig,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
@@ -2259,15 +2261,23 @@ struct ListeningAddr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[config_example_generator(
|
||||||
|
filename = "conduwuit-example.toml",
|
||||||
|
section = "global.antispam",
|
||||||
|
optional = "true"
|
||||||
|
)]
|
||||||
pub struct Antispam {
|
pub struct Antispam {
|
||||||
|
/// display: nested
|
||||||
pub meowlnir: Option<MeowlnirConfig>,
|
pub meowlnir: Option<MeowlnirConfig>,
|
||||||
|
/// display: nested
|
||||||
pub draupnir: Option<DraupnirConfig>,
|
pub draupnir: Option<DraupnirConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[config_example_generator(
|
#[config_example_generator(
|
||||||
filename = "conduwuit-example.toml",
|
filename = "conduwuit-example.toml",
|
||||||
section = "global.antispam.meowlnir"
|
section = "global.antispam.meowlnir",
|
||||||
|
optional = "true"
|
||||||
)]
|
)]
|
||||||
pub struct MeowlnirConfig {
|
pub struct MeowlnirConfig {
|
||||||
/// The base URL on which to contact Meowlnir (before /_meowlnir/antispam).
|
/// The base URL on which to contact Meowlnir (before /_meowlnir/antispam).
|
||||||
@@ -2296,7 +2306,8 @@ pub struct MeowlnirConfig {
|
|||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[config_example_generator(
|
#[config_example_generator(
|
||||||
filename = "conduwuit-example.toml",
|
filename = "conduwuit-example.toml",
|
||||||
section = "global.antispam.draupnir"
|
section = "global.antispam.draupnir",
|
||||||
|
optional = "true"
|
||||||
)]
|
)]
|
||||||
pub struct DraupnirConfig {
|
pub struct DraupnirConfig {
|
||||||
/// The base URL on which to contact Draupnir (before /api/).
|
/// The base URL on which to contact Draupnir (before /api/).
|
||||||
|
|||||||
+1
-1
@@ -62,7 +62,7 @@ pub const INFO_SPAN_LEVEL: Level = if cfg!(debug_assertions) {
|
|||||||
pub static DEBUGGER: LazyLock<bool> =
|
pub static DEBUGGER: LazyLock<bool> =
|
||||||
LazyLock::new(|| env::var("_").unwrap_or_default().ends_with("gdb"));
|
LazyLock::new(|| env::var("_").unwrap_or_default().ends_with("gdb"));
|
||||||
|
|
||||||
#[cfg_attr(debug_assertions, crate::ctor)]
|
#[cfg_attr(debug_assertions, ctor::ctor)]
|
||||||
#[cfg_attr(not(debug_assertions), allow(dead_code))]
|
#[cfg_attr(not(debug_assertions), allow(dead_code))]
|
||||||
fn set_panic_trap() {
|
fn set_panic_trap() {
|
||||||
if !*DEBUGGER {
|
if !*DEBUGGER {
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ macro_rules! err {
|
|||||||
macro_rules! err_log {
|
macro_rules! err_log {
|
||||||
($out:ident, $level:ident, $($fields:tt)+) => {{
|
($out:ident, $level:ident, $($fields:tt)+) => {{
|
||||||
use $crate::tracing::{
|
use $crate::tracing::{
|
||||||
callsite, callsite2, metadata, valueset, Callsite,
|
callsite, callsite2, metadata, valueset_all, Callsite,
|
||||||
Level,
|
Level,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ macro_rules! err_log {
|
|||||||
fields: $($fields)+,
|
fields: $($fields)+,
|
||||||
};
|
};
|
||||||
|
|
||||||
($crate::error::visit)(&mut $out, LEVEL, &__CALLSITE, &mut valueset!(__CALLSITE.metadata().fields(), $($fields)+));
|
($crate::error::visit)(&mut $out, LEVEL, &__CALLSITE, &mut valueset_all!(__CALLSITE.metadata().fields(), $($fields)+));
|
||||||
($out).into()
|
($out).into()
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,25 @@ use crate::error;
|
|||||||
|
|
||||||
impl axum::response::IntoResponse for Error {
|
impl axum::response::IntoResponse for Error {
|
||||||
fn into_response(self) -> axum::response::Response {
|
fn into_response(self) -> axum::response::Response {
|
||||||
|
let status = self.status_code();
|
||||||
|
if status.is_server_error() {
|
||||||
|
error!(
|
||||||
|
error = %self,
|
||||||
|
error_debug = ?self,
|
||||||
|
kind = ?self.kind(),
|
||||||
|
status = %status,
|
||||||
|
"Server error"
|
||||||
|
);
|
||||||
|
} else if status.is_client_error() {
|
||||||
|
use crate::debug_error;
|
||||||
|
debug_error!(
|
||||||
|
error = %self,
|
||||||
|
kind = ?self.kind(),
|
||||||
|
status = %status,
|
||||||
|
"Client error"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let response: UiaaResponse = self.into();
|
let response: UiaaResponse = self.into();
|
||||||
response
|
response
|
||||||
.try_into_http_response::<BytesMut>()
|
.try_into_http_response::<BytesMut>()
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
//! Information about the build related to Cargo. This is a frontend interface
|
|
||||||
//! informed by proc-macros that capture raw information at build time which is
|
|
||||||
//! further processed at runtime either during static initialization or as
|
|
||||||
//! necessary.
|
|
||||||
|
|
||||||
use std::sync::OnceLock;
|
|
||||||
|
|
||||||
use cargo_toml::{DepsSet, Manifest};
|
|
||||||
use conduwuit_macros::cargo_manifest;
|
|
||||||
|
|
||||||
use crate::Result;
|
|
||||||
|
|
||||||
// Raw captures of the cargo manifest for each crate. This is provided by a
|
|
||||||
// proc-macro at build time since the source directory and the cargo toml's may
|
|
||||||
// not be present during execution.
|
|
||||||
|
|
||||||
#[cargo_manifest]
|
|
||||||
const WORKSPACE_MANIFEST: &'static str = ();
|
|
||||||
#[cargo_manifest(crate = "macros")]
|
|
||||||
const MACROS_MANIFEST: &'static str = ();
|
|
||||||
#[cargo_manifest(crate = "core")]
|
|
||||||
const CORE_MANIFEST: &'static str = ();
|
|
||||||
#[cargo_manifest(crate = "database")]
|
|
||||||
const DATABASE_MANIFEST: &'static str = ();
|
|
||||||
#[cargo_manifest(crate = "service")]
|
|
||||||
const SERVICE_MANIFEST: &'static str = ();
|
|
||||||
#[cargo_manifest(crate = "admin")]
|
|
||||||
const ADMIN_MANIFEST: &'static str = ();
|
|
||||||
#[cargo_manifest(crate = "router")]
|
|
||||||
const ROUTER_MANIFEST: &'static str = ();
|
|
||||||
#[cargo_manifest(crate = "main")]
|
|
||||||
const MAIN_MANIFEST: &'static str = ();
|
|
||||||
|
|
||||||
/// Processed list of features across all project crates. This is generated from
|
|
||||||
/// the data in the MANIFEST strings and contains all possible project features.
|
|
||||||
/// For *enabled* features see the info::rustc module instead.
|
|
||||||
static FEATURES: OnceLock<Vec<String>> = OnceLock::new();
|
|
||||||
|
|
||||||
/// Processed list of dependencies. This is generated from the data captured in
|
|
||||||
/// the MANIFEST.
|
|
||||||
static DEPENDENCIES: OnceLock<DepsSet> = OnceLock::new();
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn dependencies_names() -> Vec<&'static str> {
|
|
||||||
dependencies().keys().map(String::as_str).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dependencies() -> &'static DepsSet {
|
|
||||||
DEPENDENCIES.get_or_init(|| {
|
|
||||||
init_dependencies().unwrap_or_else(|e| panic!("Failed to initialize dependencies: {e}"))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List of all possible features for the project. For *enabled* features in
|
|
||||||
/// this build see the companion function in info::rustc.
|
|
||||||
pub fn features() -> &'static Vec<String> {
|
|
||||||
FEATURES.get_or_init(|| {
|
|
||||||
init_features().unwrap_or_else(|e| panic!("Failed initialize features: {e}"))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_features() -> Result<Vec<String>> {
|
|
||||||
let mut features = Vec::new();
|
|
||||||
append_features(&mut features, WORKSPACE_MANIFEST)?;
|
|
||||||
append_features(&mut features, MACROS_MANIFEST)?;
|
|
||||||
append_features(&mut features, CORE_MANIFEST)?;
|
|
||||||
append_features(&mut features, DATABASE_MANIFEST)?;
|
|
||||||
append_features(&mut features, SERVICE_MANIFEST)?;
|
|
||||||
append_features(&mut features, ADMIN_MANIFEST)?;
|
|
||||||
append_features(&mut features, ROUTER_MANIFEST)?;
|
|
||||||
append_features(&mut features, MAIN_MANIFEST)?;
|
|
||||||
features.sort();
|
|
||||||
features.dedup();
|
|
||||||
|
|
||||||
Ok(features)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn append_features(features: &mut Vec<String>, manifest: &str) -> Result<()> {
|
|
||||||
let manifest = Manifest::from_str(manifest)?;
|
|
||||||
features.extend(manifest.features.keys().cloned());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_dependencies() -> Result<DepsSet> {
|
|
||||||
let manifest = Manifest::from_str(WORKSPACE_MANIFEST)?;
|
|
||||||
let deps_set = manifest
|
|
||||||
.workspace
|
|
||||||
.as_ref()
|
|
||||||
.expect("manifest has workspace section")
|
|
||||||
.dependencies
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
Ok(deps_set)
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,5 @@
|
|||||||
//! Information about the project. This module contains version, build, system,
|
|
||||||
//! etc information which can be queried by admins or used by developers.
|
|
||||||
|
|
||||||
pub mod cargo;
|
|
||||||
pub mod room_version;
|
pub mod room_version;
|
||||||
pub mod rustc;
|
|
||||||
pub mod version;
|
pub mod version;
|
||||||
|
|
||||||
pub use conduwuit_macros::rustc_flags_capture;
|
|
||||||
|
|
||||||
pub const MODULE_ROOT: &str = const_str::split!(std::module_path!(), "::")[0];
|
pub const MODULE_ROOT: &str = const_str::split!(std::module_path!(), "::")[0];
|
||||||
pub const CRATE_PREFIX: &str = const_str::split!(MODULE_ROOT, '_')[0];
|
pub const CRATE_PREFIX: &str = const_str::split!(MODULE_ROOT, '_')[0];
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
//! Information about the build related to rustc. This is a frontend interface
|
|
||||||
//! informed by proc-macros at build time. Since the project is split into
|
|
||||||
//! several crates, lower-level information is supplied from each crate during
|
|
||||||
//! static initialization.
|
|
||||||
|
|
||||||
use std::{collections::BTreeMap, sync::OnceLock};
|
|
||||||
|
|
||||||
use crate::utils::exchange;
|
|
||||||
|
|
||||||
/// Raw capture of rustc flags used to build each crate in the project. Informed
|
|
||||||
/// by rustc_flags_capture macro (one in each crate's mod.rs). This is
|
|
||||||
/// done during static initialization which is why it's mutex-protected and pub.
|
|
||||||
/// Should not be written to by anything other than our macro.
|
|
||||||
///
|
|
||||||
/// We specifically use a std mutex here because parking_lot cannot be used
|
|
||||||
/// after thread local storage is destroyed on MacOS.
|
|
||||||
pub static FLAGS: std::sync::Mutex<BTreeMap<&str, &[&str]>> =
|
|
||||||
std::sync::Mutex::new(BTreeMap::new());
|
|
||||||
|
|
||||||
/// Processed list of enabled features across all project crates. This is
|
|
||||||
/// generated from the data in FLAGS.
|
|
||||||
static FEATURES: OnceLock<Vec<&'static str>> = OnceLock::new();
|
|
||||||
|
|
||||||
/// List of features enabled for the project.
|
|
||||||
pub fn features() -> &'static Vec<&'static str> { FEATURES.get_or_init(init_features) }
|
|
||||||
|
|
||||||
fn init_features() -> Vec<&'static str> {
|
|
||||||
let mut features = Vec::new();
|
|
||||||
FLAGS
|
|
||||||
.lock()
|
|
||||||
.expect("locked")
|
|
||||||
.iter()
|
|
||||||
.for_each(|(_, flags)| append_features(&mut features, flags));
|
|
||||||
|
|
||||||
features.sort_unstable();
|
|
||||||
features.dedup();
|
|
||||||
features
|
|
||||||
}
|
|
||||||
|
|
||||||
fn append_features(features: &mut Vec<&'static str>, flags: &[&'static str]) {
|
|
||||||
let mut next_is_cfg = false;
|
|
||||||
for flag in flags {
|
|
||||||
let is_cfg = *flag == "--cfg";
|
|
||||||
let is_feature = flag.starts_with("feature=");
|
|
||||||
if exchange(&mut next_is_cfg, is_cfg) && is_feature {
|
|
||||||
if let Some(feature) = flag
|
|
||||||
.split_once('=')
|
|
||||||
.map(|(_, feature)| feature.trim_matches('"'))
|
|
||||||
{
|
|
||||||
features.push(feature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-4
@@ -22,7 +22,7 @@ pub use ::tracing;
|
|||||||
pub use config::Config;
|
pub use config::Config;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
pub use info::{
|
pub use info::{
|
||||||
rustc_flags_capture, version,
|
version,
|
||||||
version::{name, version},
|
version::{name, version},
|
||||||
};
|
};
|
||||||
pub use matrix::{
|
pub use matrix::{
|
||||||
@@ -30,12 +30,10 @@ pub use matrix::{
|
|||||||
};
|
};
|
||||||
pub use parking_lot::{Mutex as SyncMutex, RwLock as SyncRwLock};
|
pub use parking_lot::{Mutex as SyncMutex, RwLock as SyncRwLock};
|
||||||
pub use server::Server;
|
pub use server::Server;
|
||||||
pub use utils::{ctor, dtor, implement, result, result::Result};
|
pub use utils::{implement, result, result::Result};
|
||||||
|
|
||||||
pub use crate as conduwuit_core;
|
pub use crate as conduwuit_core;
|
||||||
|
|
||||||
rustc_flags_capture! {}
|
|
||||||
|
|
||||||
#[cfg(any(not(conduwuit_mods), not(feature = "conduwuit_mods")))]
|
#[cfg(any(not(conduwuit_mods), not(feature = "conduwuit_mods")))]
|
||||||
pub mod mods {
|
pub mod mods {
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ pub mod time;
|
|||||||
pub mod with_lock;
|
pub mod with_lock;
|
||||||
|
|
||||||
pub use ::conduwuit_macros::implement;
|
pub use ::conduwuit_macros::implement;
|
||||||
pub use ::ctor::{ctor, dtor};
|
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
arrayvec::ArrayVecExt,
|
arrayvec::ArrayVecExt,
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ serde.workspace = true
|
|||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
ctor.workspace = true
|
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -3,11 +3,8 @@
|
|||||||
extern crate conduwuit_core as conduwuit;
|
extern crate conduwuit_core as conduwuit;
|
||||||
extern crate rust_rocksdb as rocksdb;
|
extern crate rust_rocksdb as rocksdb;
|
||||||
|
|
||||||
use ctor::{ctor, dtor};
|
|
||||||
|
|
||||||
conduwuit::mod_ctor! {}
|
conduwuit::mod_ctor! {}
|
||||||
conduwuit::mod_dtor! {}
|
conduwuit::mod_dtor! {}
|
||||||
conduwuit::rustc_flags_capture! {}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod benches;
|
mod benches;
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
use std::{fs::read_to_string, path::PathBuf};
|
|
||||||
|
|
||||||
use proc_macro::{Span, TokenStream};
|
|
||||||
use quote::quote;
|
|
||||||
use syn::{Error, ItemConst, Meta};
|
|
||||||
|
|
||||||
use crate::{Result, utils};
|
|
||||||
|
|
||||||
pub(super) fn manifest(item: ItemConst, args: &[Meta]) -> Result<TokenStream> {
|
|
||||||
let member = utils::get_named_string(args, "crate");
|
|
||||||
let path = manifest_path(member.as_deref())?;
|
|
||||||
let manifest = read_to_string(&path).unwrap_or_default();
|
|
||||||
let val = manifest.as_str();
|
|
||||||
let name = item.ident;
|
|
||||||
let ret = quote! {
|
|
||||||
const #name: &'static str = #val;
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(ret.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::option_env_unwrap)]
|
|
||||||
fn manifest_path(member: Option<&str>) -> Result<PathBuf> {
|
|
||||||
let Some(path) = option_env!("CARGO_MANIFEST_DIR") else {
|
|
||||||
return Err(Error::new(
|
|
||||||
Span::call_site().into(),
|
|
||||||
"missing CARGO_MANIFEST_DIR in environment",
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut path: PathBuf = path.into();
|
|
||||||
|
|
||||||
// conduwuit/src/macros/ -> conduwuit/src/
|
|
||||||
path.pop();
|
|
||||||
|
|
||||||
if let Some(member) = member {
|
|
||||||
// conduwuit/$member/Cargo.toml
|
|
||||||
path.push(member);
|
|
||||||
} else {
|
|
||||||
// conduwuit/src/ -> conduwuit/
|
|
||||||
path.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
path.push("Cargo.toml");
|
|
||||||
|
|
||||||
Ok(path)
|
|
||||||
}
|
|
||||||
+76
-36
@@ -73,11 +73,19 @@ fn generate_example(input: &ItemStruct, args: &[Meta], write: bool) -> Result<To
|
|||||||
.expect("written to config file");
|
.expect("written to config file");
|
||||||
}
|
}
|
||||||
|
|
||||||
file.write_fmt(format_args!("\n[{section}]\n"))
|
let optional = settings.get("optional").is_some_and(|v| v == "true");
|
||||||
|
let section_header = if optional {
|
||||||
|
format!("\n#[{section}]\n")
|
||||||
|
} else {
|
||||||
|
format!("\n[{section}]\n")
|
||||||
|
};
|
||||||
|
file.write_fmt(format_args!("{section_header}"))
|
||||||
.expect("written to config file");
|
.expect("written to config file");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut summary: Vec<TokenStream2> = Vec::new();
|
let mut summary: Vec<TokenStream2> = Vec::new();
|
||||||
|
let mut nested_displays: Vec<TokenStream2> = Vec::new();
|
||||||
|
|
||||||
if let Fields::Named(FieldsNamed { named, .. }) = &input.fields {
|
if let Fields::Named(FieldsNamed { named, .. }) = &input.fields {
|
||||||
for field in named {
|
for field in named {
|
||||||
let Some(ident) = &field.ident else {
|
let Some(ident) = &field.ident else {
|
||||||
@@ -92,35 +100,6 @@ fn generate_example(input: &ItemStruct, args: &[Meta], write: bool) -> Result<To
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let doc = get_doc_comment(field)
|
|
||||||
.unwrap_or_else(|| undocumented.into())
|
|
||||||
.trim_end()
|
|
||||||
.to_owned();
|
|
||||||
|
|
||||||
let doc = if doc.ends_with('#') {
|
|
||||||
format!("{doc}\n")
|
|
||||||
} else {
|
|
||||||
format!("{doc}\n#\n")
|
|
||||||
};
|
|
||||||
|
|
||||||
let default = get_doc_comment_line(field, "default")
|
|
||||||
.or_else(|| get_default(field))
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let default = if !default.is_empty() {
|
|
||||||
format!(" {default}")
|
|
||||||
} else {
|
|
||||||
default
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(file) = file.as_mut() {
|
|
||||||
file.write_fmt(format_args!("\n{doc}"))
|
|
||||||
.expect("written to config file");
|
|
||||||
|
|
||||||
file.write_fmt(format_args!("#{ident} ={default}\n"))
|
|
||||||
.expect("written to config file");
|
|
||||||
}
|
|
||||||
|
|
||||||
let display = get_doc_comment_line(field, "display");
|
let display = get_doc_comment_line(field, "display");
|
||||||
let display_directive = |key| {
|
let display_directive = |key| {
|
||||||
display
|
display
|
||||||
@@ -129,17 +108,77 @@ fn generate_example(input: &ItemStruct, args: &[Meta], write: bool) -> Result<To
|
|||||||
.flat_map(|display| display.split(' '))
|
.flat_map(|display| display.split(' '))
|
||||||
.any(|directive| directive == key)
|
.any(|directive| directive == key)
|
||||||
};
|
};
|
||||||
|
let is_nested = display_directive("nested");
|
||||||
|
let is_hidden = display_directive("hidden");
|
||||||
|
|
||||||
if !display_directive("hidden") {
|
// Only generate config file entries for non-nested, visible types
|
||||||
let value = if display_directive("sensitive") {
|
if !is_nested && !is_hidden {
|
||||||
quote! { "***********" }
|
let doc = get_doc_comment(field)
|
||||||
|
.unwrap_or_else(|| undocumented.into())
|
||||||
|
.trim_end()
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
let doc = if doc.ends_with('#') {
|
||||||
|
format!("{doc}\n")
|
||||||
} else {
|
} else {
|
||||||
quote! { format_args!("{:?}", self.#ident) }
|
format!("{doc}\n#\n")
|
||||||
};
|
};
|
||||||
|
|
||||||
let name = ident.to_string();
|
let default = get_doc_comment_line(field, "default")
|
||||||
|
.or_else(|| get_default(field))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let default = if !default.is_empty() {
|
||||||
|
format!(" {default}")
|
||||||
|
} else {
|
||||||
|
default
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(file) = file.as_mut() {
|
||||||
|
file.write_fmt(format_args!("\n{doc}"))
|
||||||
|
.expect("written to config file");
|
||||||
|
|
||||||
|
file.write_fmt(format_args!("#{ident} ={default}\n"))
|
||||||
|
.expect("written to config file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate Display implementation for all fields
|
||||||
|
let name = ident.to_string();
|
||||||
|
|
||||||
|
if display_directive("sensitive") {
|
||||||
summary.push(quote! {
|
summary.push(quote! {
|
||||||
writeln!(out, "| {} | {} |", #name, #value)?;
|
writeln!(out, "| {} | {} |", #name, "***********")?;
|
||||||
|
});
|
||||||
|
} else if is_nested {
|
||||||
|
let is_option = matches!(type_name.as_str(), "Option");
|
||||||
|
if is_option {
|
||||||
|
summary.push(quote! {
|
||||||
|
writeln!(out, "| {} | {} |", #name,
|
||||||
|
if self.#ident.is_some() { "[configured]" } else { "None" })?;
|
||||||
|
});
|
||||||
|
|
||||||
|
nested_displays.push(quote! {
|
||||||
|
if let Some(nested) = &self.#ident {
|
||||||
|
writeln!(out)?;
|
||||||
|
writeln!(out, "## {}", #name)?;
|
||||||
|
write!(out, "{}", nested)?;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
summary.push(quote! {
|
||||||
|
writeln!(out, "| {} | [configured] |", #name)?;
|
||||||
|
});
|
||||||
|
|
||||||
|
nested_displays.push(quote! {
|
||||||
|
writeln!(out)?;
|
||||||
|
writeln!(out, "## {}", #name)?;
|
||||||
|
write!(out, "{}", &self.#ident)?;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
summary.push(quote! {
|
||||||
|
writeln!(out, "| {} | {:?} |", #name, self.#ident)?;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,6 +198,7 @@ fn generate_example(input: &ItemStruct, args: &[Meta], write: bool) -> Result<To
|
|||||||
writeln!(out, "| name | value |")?;
|
writeln!(out, "| name | value |")?;
|
||||||
writeln!(out, "| :--- | :--- |")?;
|
writeln!(out, "| :--- | :--- |")?;
|
||||||
#( #summary )*
|
#( #summary )*
|
||||||
|
#( #nested_displays )*
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-11
@@ -1,15 +1,13 @@
|
|||||||
mod admin;
|
mod admin;
|
||||||
mod cargo;
|
|
||||||
mod config;
|
mod config;
|
||||||
mod debug;
|
mod debug;
|
||||||
mod implement;
|
mod implement;
|
||||||
mod refutable;
|
mod refutable;
|
||||||
mod rustc;
|
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use syn::{
|
use syn::{
|
||||||
Error, Item, ItemConst, ItemEnum, ItemFn, ItemStruct, Meta,
|
Error, Item, ItemEnum, ItemFn, ItemStruct, Meta,
|
||||||
parse::{Parse, Parser},
|
parse::{Parse, Parser},
|
||||||
parse_macro_input,
|
parse_macro_input,
|
||||||
};
|
};
|
||||||
@@ -26,19 +24,11 @@ pub fn admin_command_dispatch(args: TokenStream, input: TokenStream) -> TokenStr
|
|||||||
attribute_macro::<ItemEnum, _>(args, input, admin::command_dispatch)
|
attribute_macro::<ItemEnum, _>(args, input, admin::command_dispatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
|
||||||
pub fn cargo_manifest(args: TokenStream, input: TokenStream) -> TokenStream {
|
|
||||||
attribute_macro::<ItemConst, _>(args, input, cargo::manifest)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn recursion_depth(args: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn recursion_depth(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
attribute_macro::<Item, _>(args, input, debug::recursion_depth)
|
attribute_macro::<Item, _>(args, input, debug::recursion_depth)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro]
|
|
||||||
pub fn rustc_flags_capture(args: TokenStream) -> TokenStream { rustc::flags_capture(args) }
|
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn refutable(args: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn refutable(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
attribute_macro::<ItemFn, _>(args, input, refutable::refutable)
|
attribute_macro::<ItemFn, _>(args, input, refutable::refutable)
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
use proc_macro::TokenStream;
|
|
||||||
use quote::quote;
|
|
||||||
|
|
||||||
pub(super) fn flags_capture(args: TokenStream) -> TokenStream {
|
|
||||||
let cargo_crate_name = std::env::var("CARGO_CRATE_NAME");
|
|
||||||
let crate_name = match cargo_crate_name.as_ref() {
|
|
||||||
| Err(_) => return args,
|
|
||||||
| Ok(crate_name) => crate_name.trim_start_matches("conduwuit_"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let flag = std::env::args().collect::<Vec<_>>();
|
|
||||||
let flag_len = flag.len();
|
|
||||||
let ret = quote! {
|
|
||||||
pub static RUSTC_FLAGS: [&str; #flag_len] = [#( #flag ),*];
|
|
||||||
|
|
||||||
#[ctor]
|
|
||||||
fn _set_rustc_flags() {
|
|
||||||
conduwuit_core::info::rustc::FLAGS.lock().expect("locked").insert(#crate_name, &RUSTC_FLAGS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// static strings have to be yanked on module unload
|
|
||||||
#[dtor]
|
|
||||||
fn _unset_rustc_flags() {
|
|
||||||
conduwuit_core::info::rustc::FLAGS.lock().expect("locked").remove(#crate_name);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ret.into()
|
|
||||||
}
|
|
||||||
@@ -207,7 +207,6 @@ clap.workspace = true
|
|||||||
console-subscriber.optional = true
|
console-subscriber.optional = true
|
||||||
console-subscriber.workspace = true
|
console-subscriber.workspace = true
|
||||||
const-str.workspace = true
|
const-str.workspace = true
|
||||||
ctor.workspace = true
|
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
opentelemetry.optional = true
|
opentelemetry.optional = true
|
||||||
opentelemetry.workspace = true
|
opentelemetry.workspace = true
|
||||||
|
|||||||
+5
-6
@@ -2,27 +2,26 @@
|
|||||||
|
|
||||||
use std::sync::{Arc, atomic::Ordering};
|
use std::sync::{Arc, atomic::Ordering};
|
||||||
|
|
||||||
use conduwuit_core::{debug_info, error, rustc_flags_capture};
|
use conduwuit_core::{debug_info, error};
|
||||||
|
|
||||||
mod clap;
|
mod clap;
|
||||||
mod logging;
|
mod logging;
|
||||||
mod mods;
|
mod mods;
|
||||||
|
mod panic;
|
||||||
mod restart;
|
mod restart;
|
||||||
mod runtime;
|
mod runtime;
|
||||||
mod sentry;
|
mod sentry;
|
||||||
mod server;
|
mod server;
|
||||||
mod signal;
|
mod signal;
|
||||||
|
|
||||||
use ctor::{ctor, dtor};
|
|
||||||
use server::Server;
|
|
||||||
|
|
||||||
rustc_flags_capture! {}
|
|
||||||
|
|
||||||
pub use conduwuit_core::{Error, Result};
|
pub use conduwuit_core::{Error, Result};
|
||||||
|
use server::Server;
|
||||||
|
|
||||||
pub use crate::clap::Args;
|
pub use crate::clap::Args;
|
||||||
|
|
||||||
pub fn run() -> Result<()> {
|
pub fn run() -> Result<()> {
|
||||||
|
panic::init();
|
||||||
|
|
||||||
let args = clap::parse();
|
let args = clap::parse();
|
||||||
run_with_args(&args)
|
run_with_args(&args)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
use std::{backtrace::Backtrace, panic};
|
||||||
|
|
||||||
|
/// Initialize the panic hook to capture backtraces at the point of panic.
|
||||||
|
/// This is needed to capture the backtrace before the unwind destroys it.
|
||||||
|
pub(crate) fn init() {
|
||||||
|
let default_hook = panic::take_hook();
|
||||||
|
|
||||||
|
panic::set_hook(Box::new(move |info| {
|
||||||
|
let backtrace = Backtrace::force_capture();
|
||||||
|
|
||||||
|
let location_str = info.location().map_or_else(String::new, |loc| {
|
||||||
|
format!(" at {}:{}:{}", loc.file(), loc.line(), loc.column())
|
||||||
|
});
|
||||||
|
|
||||||
|
let message = if let Some(s) = info.payload().downcast_ref::<&str>() {
|
||||||
|
(*s).to_owned()
|
||||||
|
} else if let Some(s) = info.payload().downcast_ref::<String>() {
|
||||||
|
s.clone()
|
||||||
|
} else {
|
||||||
|
"Box<dyn Any>".to_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
let thread_name = std::thread::current()
|
||||||
|
.name()
|
||||||
|
.map_or_else(|| "<unnamed>".to_owned(), ToOwned::to_owned);
|
||||||
|
|
||||||
|
eprintln!(
|
||||||
|
"\nthread '{thread_name}' panicked{location_str}: \
|
||||||
|
{message}\n\nBacktrace:\n{backtrace}"
|
||||||
|
);
|
||||||
|
|
||||||
|
default_hook(info);
|
||||||
|
}));
|
||||||
|
}
|
||||||
@@ -122,7 +122,6 @@ tokio.workspace = true
|
|||||||
tower.workspace = true
|
tower.workspace = true
|
||||||
tower-http.workspace = true
|
tower-http.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
ctor.workspace = true
|
|
||||||
|
|
||||||
[target.'cfg(all(unix, target_os = "linux"))'.dependencies]
|
[target.'cfg(all(unix, target_os = "linux"))'.dependencies]
|
||||||
sd-notify.workspace = true
|
sd-notify.workspace = true
|
||||||
|
|||||||
@@ -12,12 +12,10 @@ use std::{panic::AssertUnwindSafe, pin::Pin, sync::Arc};
|
|||||||
|
|
||||||
use conduwuit::{Error, Result, Server};
|
use conduwuit::{Error, Result, Server};
|
||||||
use conduwuit_service::Services;
|
use conduwuit_service::Services;
|
||||||
use ctor::{ctor, dtor};
|
|
||||||
use futures::{Future, FutureExt, TryFutureExt};
|
use futures::{Future, FutureExt, TryFutureExt};
|
||||||
|
|
||||||
conduwuit::mod_ctor! {}
|
conduwuit::mod_ctor! {}
|
||||||
conduwuit::mod_dtor! {}
|
conduwuit::mod_dtor! {}
|
||||||
conduwuit::rustc_flags_capture! {}
|
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "Rust" fn start(
|
pub extern "Rust" fn start(
|
||||||
|
|||||||
@@ -118,7 +118,6 @@ webpage.optional = true
|
|||||||
blurhash.workspace = true
|
blurhash.workspace = true
|
||||||
blurhash.optional = true
|
blurhash.optional = true
|
||||||
recaptcha-verify = { version = "0.1.5", default-features = false }
|
recaptcha-verify = { version = "0.1.5", default-features = false }
|
||||||
ctor.workspace = true
|
|
||||||
|
|
||||||
[target.'cfg(all(unix, target_os = "linux"))'.dependencies]
|
[target.'cfg(all(unix, target_os = "linux"))'.dependencies]
|
||||||
sd-notify.workspace = true
|
sd-notify.workspace = true
|
||||||
|
|||||||
@@ -18,8 +18,9 @@
|
|||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use conduwuit::{Result, Server, debug, info, warn};
|
use conduwuit::{Result, Server, debug, error, info, 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::{
|
||||||
@@ -86,9 +87,27 @@ impl crate::Service for Service {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run the first check immediately and send errors to admin room
|
||||||
|
if let Err(e) = self.check().await {
|
||||||
|
error!(?e, "Failed to check for announcements on startup");
|
||||||
|
self.services
|
||||||
|
.admin
|
||||||
|
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||||
|
"Failed to check for announcements on startup: {e}"
|
||||||
|
)))
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
let first_check_jitter = {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let jitter_percent = rng.gen_range(-50.0..=10.0);
|
||||||
|
self.interval.mul_f64(1.0 + jitter_percent / 100.0)
|
||||||
|
};
|
||||||
|
|
||||||
let mut i = interval(self.interval);
|
let mut i = interval(self.interval);
|
||||||
i.set_missed_tick_behavior(MissedTickBehavior::Delay);
|
i.set_missed_tick_behavior(MissedTickBehavior::Delay);
|
||||||
i.reset_after(self.interval);
|
i.reset_after(first_check_jitter);
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
() = self.interrupt.notified() => break,
|
() = self.interrupt.notified() => break,
|
||||||
|
|||||||
+15
-41
@@ -56,9 +56,18 @@ pub async fn fetch_remote_content(
|
|||||||
|
|
||||||
let result = self
|
let result = self
|
||||||
.fetch_content_authenticated(mxc, user, server, timeout_ms)
|
.fetch_content_authenticated(mxc, user, server, timeout_ms)
|
||||||
.await;
|
.await
|
||||||
|
.inspect_err(|error| {
|
||||||
|
debug_warn!(
|
||||||
|
%mxc,
|
||||||
|
?user,
|
||||||
|
?server,
|
||||||
|
?error,
|
||||||
|
"Authenticated fetch of remote content failed"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
if let Err(Error::Request(NotFound, ..)) = &result {
|
if let Err(Error::Request(Unrecognized, ..)) = &result {
|
||||||
return self
|
return self
|
||||||
.fetch_content_unauthenticated(mxc, user, server, timeout_ms)
|
.fetch_content_unauthenticated(mxc, user, server, timeout_ms)
|
||||||
.await;
|
.await;
|
||||||
@@ -87,7 +96,7 @@ async fn fetch_thumbnail_authenticated(
|
|||||||
timeout_ms,
|
timeout_ms,
|
||||||
};
|
};
|
||||||
|
|
||||||
let Response { content, .. } = self.federation_request(mxc, user, server, request).await?;
|
let Response { content, .. } = self.federation_request(mxc, server, request).await?;
|
||||||
|
|
||||||
match content {
|
match content {
|
||||||
| FileOrLocation::File(content) =>
|
| FileOrLocation::File(content) =>
|
||||||
@@ -111,7 +120,7 @@ async fn fetch_content_authenticated(
|
|||||||
timeout_ms,
|
timeout_ms,
|
||||||
};
|
};
|
||||||
|
|
||||||
let Response { content, .. } = self.federation_request(mxc, user, server, request).await?;
|
let Response { content, .. } = self.federation_request(mxc, server, request).await?;
|
||||||
|
|
||||||
match content {
|
match content {
|
||||||
| FileOrLocation::File(content) => self.handle_content_file(mxc, user, content).await,
|
| FileOrLocation::File(content) => self.handle_content_file(mxc, user, content).await,
|
||||||
@@ -145,7 +154,7 @@ async fn fetch_thumbnail_unauthenticated(
|
|||||||
|
|
||||||
let Response {
|
let Response {
|
||||||
file, content_type, content_disposition, ..
|
file, content_type, content_disposition, ..
|
||||||
} = self.federation_request(mxc, user, server, request).await?;
|
} = self.federation_request(mxc, server, request).await?;
|
||||||
|
|
||||||
let content = Content { file, content_type, content_disposition };
|
let content = Content { file, content_type, content_disposition };
|
||||||
|
|
||||||
@@ -173,7 +182,7 @@ async fn fetch_content_unauthenticated(
|
|||||||
|
|
||||||
let Response {
|
let Response {
|
||||||
file, content_type, content_disposition, ..
|
file, content_type, content_disposition, ..
|
||||||
} = self.federation_request(mxc, user, server, request).await?;
|
} = self.federation_request(mxc, server, request).await?;
|
||||||
|
|
||||||
let content = Content { file, content_type, content_disposition };
|
let content = Content { file, content_type, content_disposition };
|
||||||
|
|
||||||
@@ -296,7 +305,6 @@ async fn location_request(&self, location: &str) -> Result<FileMeta> {
|
|||||||
async fn federation_request<Request>(
|
async fn federation_request<Request>(
|
||||||
&self,
|
&self,
|
||||||
mxc: &Mxc<'_>,
|
mxc: &Mxc<'_>,
|
||||||
user: Option<&UserId>,
|
|
||||||
server: Option<&ServerName>,
|
server: Option<&ServerName>,
|
||||||
request: Request,
|
request: Request,
|
||||||
) -> Result<Request::IncomingResponse>
|
) -> Result<Request::IncomingResponse>
|
||||||
@@ -307,40 +315,6 @@ where
|
|||||||
.sending
|
.sending
|
||||||
.send_federation_request(server.unwrap_or(mxc.server_name), request)
|
.send_federation_request(server.unwrap_or(mxc.server_name), request)
|
||||||
.await
|
.await
|
||||||
.map_err(|error| handle_federation_error(mxc, user, server, error))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handles and adjusts the error for the caller to determine if they should
|
|
||||||
// request the fallback endpoint or give up.
|
|
||||||
fn handle_federation_error(
|
|
||||||
mxc: &Mxc<'_>,
|
|
||||||
user: Option<&UserId>,
|
|
||||||
server: Option<&ServerName>,
|
|
||||||
error: Error,
|
|
||||||
) -> Error {
|
|
||||||
let fallback = || {
|
|
||||||
err!(Request(NotFound(
|
|
||||||
debug_error!(%mxc, user = user.map(tracing::field::display), server = server.map(tracing::field::display), ?error, "Remote media not found")
|
|
||||||
)))
|
|
||||||
};
|
|
||||||
|
|
||||||
// Matrix server responses for fallback always taken.
|
|
||||||
if error.kind() == NotFound || error.kind() == Unrecognized {
|
|
||||||
return fallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we get these from any middleware we'll try the other endpoint rather than
|
|
||||||
// giving up too early.
|
|
||||||
if error.status_code().is_redirection()
|
|
||||||
|| error.status_code().is_client_error()
|
|
||||||
|| error.status_code().is_server_error()
|
|
||||||
{
|
|
||||||
return fallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reached for 5xx errors. This is where we don't fallback given the likelihood
|
|
||||||
// the other endpoint will also be a 5xx and we're wasting time.
|
|
||||||
error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[implement(super::Service)]
|
#[implement(super::Service)]
|
||||||
|
|||||||
@@ -34,11 +34,9 @@ pub mod transaction_ids;
|
|||||||
pub mod uiaa;
|
pub mod uiaa;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
|
||||||
use ctor::{ctor, dtor};
|
|
||||||
pub(crate) use service::{Args, Dep, Service};
|
pub(crate) use service::{Args, Dep, Service};
|
||||||
|
|
||||||
pub use crate::services::Services;
|
pub use crate::services::Services;
|
||||||
|
|
||||||
conduwuit::mod_ctor! {}
|
conduwuit::mod_ctor! {}
|
||||||
conduwuit::mod_dtor! {}
|
conduwuit::mod_dtor! {}
|
||||||
conduwuit::rustc_flags_capture! {}
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Event, Result, debug::INFO_SPAN_LEVEL, defer, err, implement, utils::stream::IterStream,
|
Err, Event, Result, debug::INFO_SPAN_LEVEL, defer, err, implement, info,
|
||||||
warn,
|
utils::stream::IterStream, warn,
|
||||||
};
|
};
|
||||||
use futures::{
|
use futures::{
|
||||||
FutureExt, TryFutureExt, TryStreamExt,
|
FutureExt, TryFutureExt, TryStreamExt,
|
||||||
@@ -70,7 +70,7 @@ pub async fn handle_incoming_pdu<'a>(
|
|||||||
return Err!(Request(TooLarge("PDU is too large")));
|
return Err!(Request(TooLarge("PDU is too large")));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1.1 Check the server is in the room
|
// 1.1 Check we even know about the room
|
||||||
let meta_exists = self.services.metadata.exists(room_id).map(Ok);
|
let meta_exists = self.services.metadata.exists(room_id).map(Ok);
|
||||||
|
|
||||||
// 1.2 Check if the room is disabled
|
// 1.2 Check if the room is disabled
|
||||||
@@ -114,6 +114,19 @@ pub async fn handle_incoming_pdu<'a>(
|
|||||||
return Err!(Request(Forbidden("Federation of this room is disabled by this server.")));
|
return Err!(Request(Forbidden("Federation of this room is disabled by this server.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !self
|
||||||
|
.services
|
||||||
|
.state_cache
|
||||||
|
.server_in_room(self.services.globals.server_name(), room_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
info!(
|
||||||
|
%origin,
|
||||||
|
"Dropping inbound PDU for room we aren't participating in"
|
||||||
|
);
|
||||||
|
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||||
|
}
|
||||||
|
|
||||||
let (incoming_pdu, val) = self
|
let (incoming_pdu, val) = self
|
||||||
.handle_outlier_pdu(origin, create_event, event_id, room_id, value, false)
|
.handle_outlier_pdu(origin, create_event, event_id, room_id, value, false)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use conduwuit::{implement, utils::stream::ReadyExt};
|
use conduwuit::{implement, utils::stream::ReadyExt, warn};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
EventId, RoomId, ServerName,
|
EventId, RoomId, ServerName,
|
||||||
@@ -19,7 +19,12 @@ pub async fn server_can_see_event(
|
|||||||
event_id: &EventId,
|
event_id: &EventId,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let Ok(shortstatehash) = self.pdu_shortstatehash(event_id).await else {
|
let Ok(shortstatehash) = self.pdu_shortstatehash(event_id).await else {
|
||||||
return true;
|
warn!(
|
||||||
|
"Unable to visibility check event {} in room {} for server {}: shortstatehash not \
|
||||||
|
found",
|
||||||
|
event_id, room_id, origin
|
||||||
|
);
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
let history_visibility = self
|
let history_visibility = self
|
||||||
|
|||||||
@@ -233,6 +233,16 @@ pub async fn try_auth(
|
|||||||
| AuthData::Dummy(_) => {
|
| AuthData::Dummy(_) => {
|
||||||
uiaainfo.completed.push(AuthType::Dummy);
|
uiaainfo.completed.push(AuthType::Dummy);
|
||||||
},
|
},
|
||||||
|
| AuthData::FallbackAcknowledgement(_) => {
|
||||||
|
// The client is checking if authentication has succeeded out-of-band. This is
|
||||||
|
// possible if the client is using "fallback auth" (see spec section
|
||||||
|
// 4.9.1.4), which we don't support (and probably never will, because it's a
|
||||||
|
// disgusting hack).
|
||||||
|
|
||||||
|
// Return early to tell the client that no, authentication did not succeed while
|
||||||
|
// it wasn't looking.
|
||||||
|
return Ok((false, uiaainfo));
|
||||||
|
},
|
||||||
| k => error!("type not supported: {:?}", k),
|
| k => error!("type not supported: {:?}", k),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -304,7 +304,11 @@ impl Service {
|
|||||||
pub fn enable_login(&self, user_id: &UserId) { self.db.userid_logindisabled.remove(user_id); }
|
pub fn enable_login(&self, user_id: &UserId) { self.db.userid_logindisabled.remove(user_id); }
|
||||||
|
|
||||||
pub async fn is_login_disabled(&self, user_id: &UserId) -> bool {
|
pub async fn is_login_disabled(&self, user_id: &UserId) -> bool {
|
||||||
self.db.userid_logindisabled.contains(user_id).await
|
self.db
|
||||||
|
.userid_logindisabled
|
||||||
|
.exists(user_id.as_str())
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if account is active, infallible
|
/// Check if account is active, infallible
|
||||||
|
|||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
declare module "*.css"
|
||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
import { HomeLayout as BasicHomeLayout, DocContent } from "@rspress/core/theme";
|
import { HomeLayout as BasicHomeLayout, DocContent } from "@rspress/core/theme-original";
|
||||||
|
|
||||||
import { useFrontmatter } from '@rspress/core/runtime';
|
import { useFrontmatter } from '@rspress/core/runtime';
|
||||||
interface HomeLayoutProps {
|
interface HomeLayoutProps {
|
||||||
@@ -25,5 +25,5 @@ function HomeLayout(props: HomeLayoutProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
export { HomeLayout };
|
export { HomeLayout };
|
||||||
export * from "@rspress/core/theme";
|
export * from "@rspress/core/theme-original";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|||||||
Reference in New Issue
Block a user