mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 422db2105c | |||
| 8b206564aa | |||
| 127030ac38 | |||
| 303ad4ab9c | |||
| 053860e496 | |||
| b0e3b9eeb5 | |||
| d636da06e2 | |||
| 3cec9d0077 |
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
dotenv_if_exists
|
dotenv_if_exists
|
||||||
|
|
||||||
if command -v nix >/dev/null 2>&1; then
|
if [ -f /etc/os-release ] && grep -q '^ID=nixos' /etc/os-release; then
|
||||||
use flake ".#${DIRENV_DEVSHELL:-default}"
|
use flake ".#${DIRENV_DEVSHELL:-default}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -149,6 +149,37 @@ runs:
|
|||||||
- name: Setup sccache
|
- name: Setup sccache
|
||||||
uses: https://git.tomfos.tr/tom/sccache-action@v1
|
uses: https://git.tomfos.tr/tom/sccache-action@v1
|
||||||
|
|
||||||
|
- name: Cache dependencies
|
||||||
|
id: deps-cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
target/**/.fingerprint
|
||||||
|
target/**/deps
|
||||||
|
target/**/*.d
|
||||||
|
target/**/.cargo-lock
|
||||||
|
target/**/CACHEDIR.TAG
|
||||||
|
target/**/.rustc_info.json
|
||||||
|
/timelord/
|
||||||
|
# Dependencies cache - based on Cargo.lock, survives source code changes
|
||||||
|
key: >-
|
||||||
|
continuwuity-deps-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ steps.rust-setup.outputs.version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-${{ hashFiles('rust-toolchain.toml', '**/Cargo.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
continuwuity-deps-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ steps.rust-setup.outputs.version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-
|
||||||
|
|
||||||
|
- name: Cache incremental compilation
|
||||||
|
id: incremental-cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
target/**/incremental
|
||||||
|
# Incremental cache - based on source code changes
|
||||||
|
key: >-
|
||||||
|
continuwuity-incremental-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ steps.rust-setup.outputs.version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-${{ hashFiles('rust-toolchain.toml', '**/Cargo.lock') }}-${{ hashFiles('**/*.rs', '**/Cargo.toml') }}
|
||||||
|
restore-keys: |
|
||||||
|
continuwuity-incremental-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ steps.rust-setup.outputs.version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-${{ hashFiles('rust-toolchain.toml', '**/Cargo.lock') }}-
|
||||||
|
continuwuity-incremental-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ steps.rust-setup.outputs.version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-
|
||||||
|
|
||||||
- name: End build cache restore group
|
- name: End build cache restore group
|
||||||
shell: bash
|
shell: bash
|
||||||
run: echo "::endgroup::"
|
run: echo "::endgroup::"
|
||||||
|
|||||||
@@ -16,19 +16,48 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
fetch-tags: false
|
||||||
|
fetch-single-branch: true
|
||||||
|
submodules: false
|
||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
token: ${{ secrets.FORGEJO_TOKEN }}
|
token: ${{ secrets.FORGEJO_TOKEN }}
|
||||||
|
|
||||||
- name: Install Lix
|
- uses: https://github.com/cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0
|
||||||
uses: https://github.com/samueldr/lix-gha-installer-action@f5e94192f565f53d84f41a056956dc0d3183b343
|
|
||||||
with:
|
with:
|
||||||
extra_nix_config: experimental-features = nix-command flakes flake-self-attrs
|
nix_path: nixpkgs=channel:nixos-unstable
|
||||||
|
|
||||||
|
# We can skip getting a toolchain hash if this was ran as a dispatch with the intent
|
||||||
|
# to update just the rocksdb hash. If this was ran as a dispatch and the toolchain
|
||||||
|
# files are changed, we still update them, as well as the rocksdb import.
|
||||||
|
- name: Detect changed files
|
||||||
|
id: changes
|
||||||
|
run: |
|
||||||
|
git fetch origin ${{ github.base_ref }} --depth=1 || true
|
||||||
|
if [ -n "${{ github.event.pull_request.base.sha }}" ]; then
|
||||||
|
base=${{ github.event.pull_request.base.sha }}
|
||||||
|
else
|
||||||
|
base=$(git rev-parse HEAD~1)
|
||||||
|
fi
|
||||||
|
echo "Base: $base"
|
||||||
|
echo "HEAD: $(git rev-parse HEAD)"
|
||||||
|
git diff --name-only $base HEAD > changed_files.txt
|
||||||
|
echo "detected changes in $(cat changed_files.txt)"
|
||||||
|
# Join files with commas
|
||||||
|
files=$(paste -sd, changed_files.txt)
|
||||||
|
echo "files=$files" >> $FORGEJO_OUTPUT
|
||||||
|
|
||||||
|
- name: Debug output
|
||||||
|
run: |
|
||||||
|
echo "State of output"
|
||||||
|
echo "Changed files: ${{ steps.changes.outputs.files }}"
|
||||||
|
|
||||||
- name: Get new toolchain hash
|
- name: Get new toolchain hash
|
||||||
|
if: contains(steps.changes.outputs.files, 'Cargo.toml') || contains(steps.changes.outputs.files, 'Cargo.lock') || contains(steps.changes.outputs.files, 'rust-toolchain.toml')
|
||||||
run: |
|
run: |
|
||||||
# Set the current sha256 to an empty hash to make `nix build` calculate a new one
|
# Set the current sha256 to an empty hash to make `nix build` calculate a new one
|
||||||
awk '/fromToolchainFile *\{/{found=1; print; next} found && /sha256 =/{sub(/sha256 = .*/, "sha256 = lib.fakeSha256;"); found=0} 1' nix/rust.nix > temp.nix
|
awk '/fromToolchainFile *\{/{found=1; print; next} found && /sha256 =/{sub(/sha256 = .*/, "sha256 = lib.fakeSha256;"); found=0} 1' nix/packages/rust.nix > temp.nix
|
||||||
mv temp.nix nix/rust.nix
|
mv temp.nix nix/packages/rust.nix
|
||||||
|
|
||||||
# Build continuwuity and filter for the new hash
|
# Build continuwuity and filter for the new hash
|
||||||
# We do `|| true` because we want this to fail without stopping the workflow
|
# We do `|| true` because we want this to fail without stopping the workflow
|
||||||
@@ -36,17 +65,36 @@ jobs:
|
|||||||
|
|
||||||
# Place the new hash in place of the empty hash
|
# Place the new hash in place of the empty hash
|
||||||
new_hash=$(cat new_toolchain_hash.txt)
|
new_hash=$(cat new_toolchain_hash.txt)
|
||||||
sed -i "s|lib.fakeSha256|\"$new_hash\"|" nix/rust.nix
|
sed -i "s|lib.fakeSha256|\"$new_hash\"|" nix/packages/rust.nix
|
||||||
|
|
||||||
echo "New hash:"
|
echo "New hash:"
|
||||||
awk -F'"' '/fromToolchainFile/{found=1; next} found && /sha256 =/{print $2; found=0}' nix/rust.nix
|
awk -F'"' '/fromToolchainFile/{found=1; next} found && /sha256 =/{print $2; found=0}' nix/packages/rust.nix
|
||||||
echo "Expected new hash:"
|
echo "Expected new hash:"
|
||||||
cat new_toolchain_hash.txt
|
cat new_toolchain_hash.txt
|
||||||
|
|
||||||
rm new_toolchain_hash.txt
|
rm new_toolchain_hash.txt
|
||||||
|
|
||||||
- name: Update rocksdb
|
- name: Get new rocksdb hash
|
||||||
run: nix run .#update-rocksdb
|
if: contains(steps.changes.outputs.files, '.nix') || contains(steps.changes.outputs.files, 'flake.lock')
|
||||||
|
run: |
|
||||||
|
# Set the current sha256 to an empty hash to make `nix build` calculate a new one
|
||||||
|
awk '/repo = "rocksdb";/{found=1; print; next} found && /sha256 =/{sub(/sha256 = .*/, "sha256 = lib.fakeSha256;"); found=0} 1' nix/packages/rocksdb/package.nix > temp.nix
|
||||||
|
mv temp.nix nix/packages/rocksdb/package.nix
|
||||||
|
|
||||||
|
# Build continuwuity and filter for the new hash
|
||||||
|
# We do `|| true` because we want this to fail without stopping the workflow
|
||||||
|
nix build .#default 2>&1 | tee >(grep 'got:' | awk '{print $2}' > new_rocksdb_hash.txt) || true
|
||||||
|
|
||||||
|
# Place the new hash in place of the empty hash
|
||||||
|
new_hash=$(cat new_rocksdb_hash.txt)
|
||||||
|
sed -i "s|lib.fakeSha256|\"$new_hash\"|" nix/packages/rocksdb/package.nix
|
||||||
|
|
||||||
|
echo "New hash:"
|
||||||
|
awk -F'"' '/repo = "rocksdb";/{found=1; next} found && /sha256 =/{print $2; found=0}' nix/packages/rocksdb/package.nix
|
||||||
|
echo "Expected new hash:"
|
||||||
|
cat new_rocksdb_hash.txt
|
||||||
|
|
||||||
|
rm new_rocksdb_hash.txt
|
||||||
|
|
||||||
- name: Show diff
|
- name: Show diff
|
||||||
run: git diff flake.nix nix
|
run: git diff flake.nix nix
|
||||||
|
|||||||
@@ -24,7 +24,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.45.0
|
rev: v1.44.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: typos
|
- id: typos
|
||||||
- id: typos
|
- id: typos
|
||||||
|
|||||||
+131
-1
@@ -1 +1,131 @@
|
|||||||
Contributors are expected to follow the [Continuwuity Community Guidelines](continuwuity.org/community/guidelines).
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||||
|
identity and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the overall
|
||||||
|
community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||||
|
any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email address,
|
||||||
|
without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement over Matrix at [#continuwuity:continuwuity.org](https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org) or email at <tom@tcpip.uk>, <jade@continuwuity.org> and <nex@continuwuity.org> respectively.
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series of
|
||||||
|
actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or permanent
|
||||||
|
ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||||
|
community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.1, available at
|
||||||
|
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by
|
||||||
|
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||||
|
[https://www.contributor-covenant.org/translations][translations].
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||||
|
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||||
|
[FAQ]: https://www.contributor-covenant.org/faq
|
||||||
|
[translations]: https://www.contributor-covenant.org/translations
|
||||||
|
|||||||
Generated
+15
-205
@@ -72,12 +72,6 @@ dependencies = [
|
|||||||
"alloc-no-stdlib",
|
"alloc-no-stdlib",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "allocator-api2"
|
|
||||||
version = "0.2.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android_system_properties"
|
name = "android_system_properties"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@@ -119,15 +113,6 @@ version = "1.0.102"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ar_archive_writer"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b"
|
|
||||||
dependencies = [
|
|
||||||
"object",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arbitrary"
|
name = "arbitrary"
|
||||||
version = "1.4.2"
|
version = "1.4.2"
|
||||||
@@ -835,16 +820,6 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "chumsky"
|
|
||||||
version = "0.9.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9"
|
|
||||||
dependencies = [
|
|
||||||
"hashbrown 0.14.5",
|
|
||||||
"stacker",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clang-sys"
|
name = "clang-sys"
|
||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
@@ -948,12 +923,10 @@ dependencies = [
|
|||||||
"conduwuit_build_metadata",
|
"conduwuit_build_metadata",
|
||||||
"conduwuit_core",
|
"conduwuit_core",
|
||||||
"conduwuit_database",
|
"conduwuit_database",
|
||||||
"conduwuit_macros",
|
|
||||||
"conduwuit_router",
|
"conduwuit_router",
|
||||||
"conduwuit_service",
|
"conduwuit_service",
|
||||||
"console-subscriber",
|
"console-subscriber",
|
||||||
"const-str",
|
"const-str",
|
||||||
"ctor",
|
|
||||||
"hardened_malloc-rs",
|
"hardened_malloc-rs",
|
||||||
"log",
|
"log",
|
||||||
"opentelemetry",
|
"opentelemetry",
|
||||||
@@ -983,9 +956,7 @@ dependencies = [
|
|||||||
"conduwuit_macros",
|
"conduwuit_macros",
|
||||||
"conduwuit_service",
|
"conduwuit_service",
|
||||||
"const-str",
|
"const-str",
|
||||||
"ctor",
|
|
||||||
"futures",
|
"futures",
|
||||||
"lettre",
|
|
||||||
"log",
|
"log",
|
||||||
"ruma",
|
"ruma",
|
||||||
"serde-saphyr",
|
"serde-saphyr",
|
||||||
@@ -1006,10 +977,8 @@ dependencies = [
|
|||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"conduwuit_core",
|
"conduwuit_core",
|
||||||
"conduwuit_macros",
|
|
||||||
"conduwuit_service",
|
"conduwuit_service",
|
||||||
"const-str",
|
"const-str",
|
||||||
"ctor",
|
|
||||||
"futures",
|
"futures",
|
||||||
"hmac",
|
"hmac",
|
||||||
"http",
|
"http",
|
||||||
@@ -1017,7 +986,6 @@ dependencies = [
|
|||||||
"hyper",
|
"hyper",
|
||||||
"ipaddress",
|
"ipaddress",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"lettre",
|
|
||||||
"log",
|
"log",
|
||||||
"rand 0.10.0",
|
"rand 0.10.0",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@@ -1035,7 +1003,6 @@ name = "conduwuit_build_metadata"
|
|||||||
version = "0.5.7-alpha.1"
|
version = "0.5.7-alpha.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"built",
|
"built",
|
||||||
"cargo_metadata",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1067,7 +1034,6 @@ dependencies = [
|
|||||||
"hyper-util",
|
"hyper-util",
|
||||||
"ipaddress",
|
"ipaddress",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"lettre",
|
|
||||||
"libc",
|
"libc",
|
||||||
"libloading 0.9.0",
|
"libloading 0.9.0",
|
||||||
"lock_api",
|
"lock_api",
|
||||||
@@ -1108,9 +1074,7 @@ version = "0.5.7-alpha.1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"async-channel",
|
"async-channel",
|
||||||
"conduwuit_core",
|
"conduwuit_core",
|
||||||
"conduwuit_macros",
|
|
||||||
"const-str",
|
"const-str",
|
||||||
"ctor",
|
|
||||||
"futures",
|
"futures",
|
||||||
"log",
|
"log",
|
||||||
"minicbor",
|
"minicbor",
|
||||||
@@ -1126,7 +1090,6 @@ dependencies = [
|
|||||||
name = "conduwuit_macros"
|
name = "conduwuit_macros"
|
||||||
version = "0.5.7-alpha.1"
|
version = "0.5.7-alpha.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cargo_toml",
|
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1145,11 +1108,9 @@ dependencies = [
|
|||||||
"conduwuit_admin",
|
"conduwuit_admin",
|
||||||
"conduwuit_api",
|
"conduwuit_api",
|
||||||
"conduwuit_core",
|
"conduwuit_core",
|
||||||
"conduwuit_macros",
|
|
||||||
"conduwuit_service",
|
"conduwuit_service",
|
||||||
"conduwuit_web",
|
"conduwuit_web",
|
||||||
"const-str",
|
"const-str",
|
||||||
"ctor",
|
|
||||||
"futures",
|
"futures",
|
||||||
"http",
|
"http",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
@@ -1180,23 +1141,18 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"conduwuit_core",
|
"conduwuit_core",
|
||||||
"conduwuit_database",
|
"conduwuit_database",
|
||||||
"conduwuit_macros",
|
|
||||||
"const-str",
|
"const-str",
|
||||||
"ctor",
|
|
||||||
"either",
|
"either",
|
||||||
"futures",
|
"futures",
|
||||||
"governor",
|
|
||||||
"hickory-resolver",
|
"hickory-resolver",
|
||||||
"http",
|
"http",
|
||||||
"image",
|
"image",
|
||||||
"ipaddress",
|
"ipaddress",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"ldap3",
|
"ldap3",
|
||||||
"lettre",
|
|
||||||
"log",
|
"log",
|
||||||
"loole",
|
"loole",
|
||||||
"lru-cache",
|
"lru-cache",
|
||||||
"nonzero_ext",
|
|
||||||
"rand 0.10.0",
|
"rand 0.10.0",
|
||||||
"recaptcha-verify",
|
"recaptcha-verify",
|
||||||
"regex",
|
"regex",
|
||||||
@@ -1304,7 +1260,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "continuwuity-admin-api"
|
name = "continuwuity-admin-api"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -1739,7 +1695,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "draupnir-antispam"
|
name = "draupnir-antispam"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -1801,22 +1757,6 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "email-encoding"
|
|
||||||
version = "0.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9298e6504d9b9e780ed3f7dfd43a61be8cd0e09eb07f7706a945b0072b6670b6"
|
|
||||||
dependencies = [
|
|
||||||
"base64 0.22.1",
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "email_address"
|
|
||||||
version = "0.2.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.35"
|
version = "0.8.35"
|
||||||
@@ -2018,12 +1958,6 @@ version = "0.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foldhash"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "form_urlencoded"
|
name = "form_urlencoded"
|
||||||
version = "1.2.2"
|
version = "1.2.2"
|
||||||
@@ -2140,12 +2074,6 @@ version = "0.3.32"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-timer"
|
|
||||||
version = "3.0.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-util"
|
name = "futures-util"
|
||||||
version = "0.3.32"
|
version = "0.3.32"
|
||||||
@@ -2236,25 +2164,6 @@ version = "0.3.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "governor"
|
|
||||||
version = "0.10.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9efcab3c1958580ff1f25a2a41be1668f7603d849bb63af523b208a3cc1223b8"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"futures-sink",
|
|
||||||
"futures-timer",
|
|
||||||
"futures-util",
|
|
||||||
"hashbrown 0.16.1",
|
|
||||||
"nonzero_ext",
|
|
||||||
"parking_lot",
|
|
||||||
"portable-atomic",
|
|
||||||
"smallvec",
|
|
||||||
"spinning_top",
|
|
||||||
"web-time",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.4.13"
|
version = "0.4.13"
|
||||||
@@ -2319,23 +2228,13 @@ version = "0.1.2+12"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "647deb1583b14d160f85f3ff626f20b6edd366e3852c9843b06077388f794cb6"
|
checksum = "647deb1583b14d160f85f3ff626f20b6edd366e3852c9843b06077388f794cb6"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.14.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
|
||||||
dependencies = [
|
|
||||||
"ahash",
|
|
||||||
"allocator-api2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.5"
|
version = "0.15.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"foldhash 0.1.5",
|
"foldhash",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2343,11 +2242,6 @@ name = "hashbrown"
|
|||||||
version = "0.16.1"
|
version = "0.16.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||||
dependencies = [
|
|
||||||
"allocator-api2",
|
|
||||||
"equivalent",
|
|
||||||
"foldhash 0.2.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hdrhistogram"
|
name = "hdrhistogram"
|
||||||
@@ -3005,37 +2899,6 @@ version = "0.5.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
|
checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lettre"
|
|
||||||
version = "0.11.19"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9e13e10e8818f8b2a60f52cb127041d388b89f3a96a62be9ceaffa22262fef7f"
|
|
||||||
dependencies = [
|
|
||||||
"async-trait",
|
|
||||||
"base64 0.22.1",
|
|
||||||
"chumsky",
|
|
||||||
"email-encoding",
|
|
||||||
"email_address",
|
|
||||||
"fastrand",
|
|
||||||
"futures-io",
|
|
||||||
"futures-util",
|
|
||||||
"hostname",
|
|
||||||
"httpdate",
|
|
||||||
"idna",
|
|
||||||
"mime",
|
|
||||||
"nom 8.0.0",
|
|
||||||
"percent-encoding",
|
|
||||||
"quoted_printable",
|
|
||||||
"rustls",
|
|
||||||
"rustls-native-certs",
|
|
||||||
"serde",
|
|
||||||
"socket2 0.6.3",
|
|
||||||
"tokio",
|
|
||||||
"tokio-rustls",
|
|
||||||
"tracing",
|
|
||||||
"url",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.183"
|
version = "0.2.183"
|
||||||
@@ -3254,7 +3117,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "meowlnir-antispam"
|
name = "meowlnir-antispam"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -3418,12 +3281,6 @@ version = "0.7.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7"
|
checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nonzero_ext"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "noop_proc_macro"
|
name = "noop_proc_macro"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@@ -4166,16 +4023,6 @@ dependencies = [
|
|||||||
"prost",
|
"prost",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "psm"
|
|
||||||
version = "0.1.30"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8"
|
|
||||||
dependencies = [
|
|
||||||
"ar_archive_writer",
|
|
||||||
"cc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pulldown-cmark"
|
name = "pulldown-cmark"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
@@ -4280,12 +4127,6 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quoted_printable"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "r-efi"
|
name = "r-efi"
|
||||||
version = "5.3.0"
|
version = "5.3.0"
|
||||||
@@ -4558,7 +4399,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma"
|
name = "ruma"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assign",
|
"assign",
|
||||||
"continuwuity-admin-api",
|
"continuwuity-admin-api",
|
||||||
@@ -4581,7 +4422,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-appservice-api"
|
name = "ruma-appservice-api"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
@@ -4593,7 +4434,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-client-api"
|
name = "ruma-client-api"
|
||||||
version = "0.18.0"
|
version = "0.18.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as_variant",
|
"as_variant",
|
||||||
"assign",
|
"assign",
|
||||||
@@ -4616,7 +4457,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-common"
|
name = "ruma-common"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as_variant",
|
"as_variant",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
@@ -4648,7 +4489,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-events"
|
name = "ruma-events"
|
||||||
version = "0.28.1"
|
version = "0.28.1"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as_variant",
|
"as_variant",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
@@ -4673,7 +4514,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-federation-api"
|
name = "ruma-federation-api"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"headers",
|
"headers",
|
||||||
@@ -4695,7 +4536,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-identifiers-validation"
|
name = "ruma-identifiers-validation"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
@@ -4704,7 +4545,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-identity-service-api"
|
name = "ruma-identity-service-api"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
@@ -4714,7 +4555,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-macros"
|
name = "ruma-macros"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
@@ -4729,7 +4570,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-push-gateway-api"
|
name = "ruma-push-gateway-api"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
@@ -4741,7 +4582,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-signatures"
|
name = "ruma-signatures"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
@@ -5414,15 +5255,6 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "spinning_top"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300"
|
|
||||||
dependencies = [
|
|
||||||
"lock_api",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spki"
|
name = "spki"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
@@ -5439,19 +5271,6 @@ version = "1.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "stacker"
|
|
||||||
version = "0.1.23"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"cfg-if",
|
|
||||||
"libc",
|
|
||||||
"psm",
|
|
||||||
"windows-sys 0.59.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strict"
|
name = "strict"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -6587,15 +6406,6 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.59.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
|
||||||
dependencies = [
|
|
||||||
"windows-targets 0.52.6",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.60.2"
|
version = "0.60.2"
|
||||||
|
|||||||
+3
-16
@@ -344,7 +344,7 @@ version = "0.1.2"
|
|||||||
[workspace.dependencies.ruma]
|
[workspace.dependencies.ruma]
|
||||||
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
|
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
|
||||||
#branch = "conduwuit-changes"
|
#branch = "conduwuit-changes"
|
||||||
rev = "1415caf8a32af4d943580c5ea4e12be1974593c2"
|
rev = "a97b91adcc012ef04991d823b8b5a79c6686ae48"
|
||||||
features = [
|
features = [
|
||||||
"compat",
|
"compat",
|
||||||
"rand",
|
"rand",
|
||||||
@@ -383,8 +383,7 @@ features = [
|
|||||||
"unstable-pdu",
|
"unstable-pdu",
|
||||||
"unstable-msc4155",
|
"unstable-msc4155",
|
||||||
"unstable-msc4143", # livekit well_known response
|
"unstable-msc4143", # livekit well_known response
|
||||||
"unstable-msc4284",
|
"unstable-msc4284"
|
||||||
"unstable-msc4439", # pgp_key in .well_known/matrix/support
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies.rust-rocksdb]
|
[workspace.dependencies.rust-rocksdb]
|
||||||
@@ -557,19 +556,6 @@ version = "1.0.1"
|
|||||||
[workspace.dependencies.askama]
|
[workspace.dependencies.askama]
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
|
|
||||||
[workspace.dependencies.lettre]
|
|
||||||
version = "0.11.19"
|
|
||||||
default-features = false
|
|
||||||
features = ["smtp-transport", "pool", "hostname", "builder", "rustls", "rustls-native-certs", "tokio1", "ring", "tokio1-rustls", "tracing", "serde"]
|
|
||||||
|
|
||||||
[workspace.dependencies.governor]
|
|
||||||
version = "0.10.4"
|
|
||||||
default-features = false
|
|
||||||
features = ["std"]
|
|
||||||
|
|
||||||
[workspace.dependencies.nonzero_ext]
|
|
||||||
version = "0.3.0"
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Patches
|
# Patches
|
||||||
#
|
#
|
||||||
@@ -930,6 +916,7 @@ fn_to_numeric_cast_any = "warn"
|
|||||||
format_push_string = "warn"
|
format_push_string = "warn"
|
||||||
get_unwrap = "warn"
|
get_unwrap = "warn"
|
||||||
impl_trait_in_params = "warn"
|
impl_trait_in_params = "warn"
|
||||||
|
let_underscore_untyped = "warn"
|
||||||
lossy_float_literal = "warn"
|
lossy_float_literal = "warn"
|
||||||
mem_forget = "warn"
|
mem_forget = "warn"
|
||||||
missing_assert_message = "warn"
|
missing_assert_message = "warn"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
Apache License
|
Apache License
|
||||||
Version 2.0, January 2004
|
Version 2.0, January 2004
|
||||||
http://www.apache.org/licenses/
|
http://www.apache.org/licenses/
|
||||||
@@ -186,7 +187,7 @@
|
|||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright 2023 Continuwuity Team and contributors
|
Copyright 2023 June
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
[book]
|
||||||
|
title = "continuwuity"
|
||||||
|
description = "continuwuity is a community continuation of the conduwuit Matrix homeserver, written in Rust."
|
||||||
|
language = "en"
|
||||||
|
authors = ["The continuwuity Community"]
|
||||||
|
text-direction = "ltr"
|
||||||
|
src = "docs"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
build-dir = "public"
|
||||||
|
create-missing = true
|
||||||
|
extra-watch-dirs = ["debian", "docs"]
|
||||||
|
|
||||||
|
[rust]
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[output.html]
|
||||||
|
edit-url-template = "https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/{path}"
|
||||||
|
git-repository-url = "https://forgejo.ellis.link/continuwuation/continuwuity"
|
||||||
|
git-repository-icon = "fa-git-alt"
|
||||||
|
|
||||||
|
[output.html.search]
|
||||||
|
limit-results = 15
|
||||||
@@ -1 +0,0 @@
|
|||||||
Added support for associating email addresses with accounts, requiring email addresses for registration, and resetting passwords via email. Contributed by @ginger
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Added support for requiring users to accept terms and conditions when registering.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Fixed error 500 when joining non-existent rooms. Contributed by @ezera.
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Drop unnecessary fields when converting an event into the federation format, saving bandwidth. Contributed by @nex.
|
||||||
@@ -1 +0,0 @@
|
|||||||
Refactored nix package. Breaking, since `all-features` package no longer exists. Continuwuity is now built with jemalloc and liburing by default. Contributed by @Henry-Hiles (QuadRadical).
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
Add new config option for [MSC4439](https://github.com/matrix-org/matrix-spec-proposals/pull/4439)
|
|
||||||
PGP key URIs. Contributed by LogN.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Added `!admin users reset-push-rules` command to reset the notification settings of users. Contributed by @nex.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Notification pushers are now automatically removed when their associated device is. Admin commands now exist for manual cleanup too. Contributed by @nex.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Fixed resolving IP of servers that only use SRV delegation. Contributed by @tulir.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Fixed compiler warning in cf_opts.rs when building in release. Contributed by @ezera.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Fixed "Sender must be a local user" error for make_join, make_knock, and make_leave federation routes. Contributed by @nex.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Added admin commands to get build information and features. Contributed by @Jade
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Fixed restricted joins not being signed when we are being used as an authorising server. Contributed by @nex, reported by [vel](matrix:u/vel:nhjkl.com?action=chat).
|
|
||||||
@@ -523,18 +523,6 @@
|
|||||||
#
|
#
|
||||||
#recaptcha_private_site_key =
|
#recaptcha_private_site_key =
|
||||||
|
|
||||||
# Policy documents, such as terms and conditions or a privacy policy,
|
|
||||||
# which users must agree to when registering an account.
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
# ```ignore
|
|
||||||
# [global.registration_terms.privacy_policy]
|
|
||||||
# en = { name = "Privacy Policy", url = "https://homeserver.example/en/privacy_policy.html" }
|
|
||||||
# es = { name = "Política de Privacidad", url = "https://homeserver.example/es/privacy_policy.html" }
|
|
||||||
# ```
|
|
||||||
#
|
|
||||||
#registration_terms = {}
|
|
||||||
|
|
||||||
# Controls whether encrypted rooms and events are allowed.
|
# Controls whether encrypted rooms and events are allowed.
|
||||||
#
|
#
|
||||||
#allow_encryption = true
|
#allow_encryption = true
|
||||||
@@ -1881,11 +1869,6 @@
|
|||||||
#
|
#
|
||||||
#support_mxid =
|
#support_mxid =
|
||||||
|
|
||||||
# PGP key URI for server support contacts, to be served as part of the
|
|
||||||
# MSC1929 server support endpoint.
|
|
||||||
#
|
|
||||||
#support_pgp_key =
|
|
||||||
|
|
||||||
# **DEPRECATED**: Use `[global.matrix_rtc].foci` instead.
|
# **DEPRECATED**: Use `[global.matrix_rtc].foci` instead.
|
||||||
#
|
#
|
||||||
# A list of MatrixRTC foci URLs which will be served as part of the
|
# A list of MatrixRTC foci URLs which will be served as part of the
|
||||||
@@ -2058,41 +2041,3 @@
|
|||||||
# web->synapseHTTPAntispam->authorization
|
# web->synapseHTTPAntispam->authorization
|
||||||
#
|
#
|
||||||
#secret =
|
#secret =
|
||||||
|
|
||||||
#[global.smtp]
|
|
||||||
|
|
||||||
# A `smtp://`` URI which will be used to connect to a mail server.
|
|
||||||
# Uncommenting the [global.smtp] group and setting this option enables
|
|
||||||
# features which depend on the ability to send email,
|
|
||||||
# such as self-service password resets.
|
|
||||||
#
|
|
||||||
# For most modern mail servers, format the URI like this:
|
|
||||||
# `smtps://username:password@hostname:port`
|
|
||||||
# Note that you will need to URL-encode the username and password. If your
|
|
||||||
# username _is_ your email address, you will need to replace the `@` with
|
|
||||||
# `%40`.
|
|
||||||
#
|
|
||||||
# For a guide on the accepted URI syntax, consult Lettre's documentation:
|
|
||||||
# https://docs.rs/lettre/latest/lettre/transport/smtp/struct.AsyncSmtpTransport.html#method.from_url
|
|
||||||
#
|
|
||||||
#connection_uri =
|
|
||||||
|
|
||||||
# The outgoing address which will be used for sending emails.
|
|
||||||
#
|
|
||||||
# For a syntax guide, see https://datatracker.ietf.org/doc/html/rfc2822#section-3.4
|
|
||||||
#
|
|
||||||
# ...or if you don't want to read the RFC, for some reason:
|
|
||||||
# - `Name <address@domain.org>` to specify a sender name
|
|
||||||
# - `address@domain.org` to not use a name
|
|
||||||
#
|
|
||||||
#sender =
|
|
||||||
|
|
||||||
# Whether to require that users provide an email address when they
|
|
||||||
# register.
|
|
||||||
#
|
|
||||||
#require_email_for_registration = false
|
|
||||||
|
|
||||||
# Whether to require that users who register with a registration token
|
|
||||||
# provide an email address.
|
|
||||||
#
|
|
||||||
#require_email_for_token_registration = false
|
|
||||||
|
|||||||
+2
-2
@@ -15,13 +15,13 @@ ARG LLVM_VERSION=21
|
|||||||
|
|
||||||
# Install repo tools
|
# Install repo tools
|
||||||
# Line one: compiler tools
|
# Line one: compiler tools
|
||||||
# Line two: curl, for downloading binaries and wget because llvm.sh is broken with curl
|
# Line two: curl, for downloading binaries
|
||||||
# Line three: for xx-verify
|
# Line three: for xx-verify
|
||||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||||
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
||||||
apt-get update && apt-get install -y \
|
apt-get update && apt-get install -y \
|
||||||
pkg-config make jq \
|
pkg-config make jq \
|
||||||
wget curl git software-properties-common \
|
curl git software-properties-common \
|
||||||
file
|
file
|
||||||
|
|
||||||
# LLVM packages
|
# LLVM packages
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ This guide assumes that you are using docker compose for deployment. LiveKit onl
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
You can find help setting up MatrixRTC in our dedicated room - [#matrixrtc:continuwuity.org](https://matrix.to/#/%23matrixrtc%3Acontinuwuity.org)
|
You can find help setting up Matrix RTC in our dedicated room - [#matrixrtc:continuwuity.org](https://matrix.to/#/%23matrixrtc%3Acontinuwuity.org)
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Instructions
|
## Instructions
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
# Continuwuity Community Guidelines
|
# Continuwuity Community Guidelines
|
||||||
|
|
||||||
Welcome to the Continuwuity commuwunity! We're excited to have you here.
|
Welcome to the Continuwuity commuwunity! We're excited to have you here. Continuwuity is a
|
||||||
|
continuation of the conduwuit homeserver, which in turn is a hard-fork of the Conduit homeserver,
|
||||||
|
aimed at making Matrix more accessible and inclusive for everyone.
|
||||||
|
|
||||||
Our project aims to make Matrix more accessible and inclusive for everyone. To that end, we are dedicated to fostering a positive, supportive, safe and welcoming environment for our community.
|
This space is dedicated to fostering a positive, supportive, and welcoming environment for everyone.
|
||||||
|
These guidelines apply to all Continuwuity spaces, including our Matrix rooms and any other
|
||||||
|
community channels that reference them. We've written these guidelines to help us all create an
|
||||||
|
environment where everyone feels safe and respected.
|
||||||
|
|
||||||
These guidelines apply to all Continuwuity spaces, including our Matrix rooms and code forge.
|
For code and contribution guidelines, please refer to the
|
||||||
|
[Contributor's Covenant](https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/CODE_OF_CONDUCT.md).
|
||||||
Our community spaces are intended for individuals aged 16 or over, because we expect maturity and respect from our community members.
|
Below are additional guidelines specific to the Continuwuity community.
|
||||||
|
|
||||||
## Our Values and Expected Behaviors
|
## Our Values and Expected Behaviors
|
||||||
|
|
||||||
@@ -24,21 +29,17 @@ all members to:
|
|||||||
|
|
||||||
3. **Communicate Clearly and Kindly**: Our community includes neurodivergent individuals and those
|
3. **Communicate Clearly and Kindly**: Our community includes neurodivergent individuals and those
|
||||||
who may not appreciate sarcasm or subtlety. Communicate clearly and kindly. Avoid ambiguity and
|
who may not appreciate sarcasm or subtlety. Communicate clearly and kindly. Avoid ambiguity and
|
||||||
ensure your messages can be easily understood by all.
|
ensure your messages can be easily understood by all. Avoid placing the burden of education on
|
||||||
|
|
||||||
4. **Be Considerate and Proactive**: Not everyone has the same time, resource and experience to spare.
|
|
||||||
Don't expect others to give up their time and labour for you; be thankful for what you have already been given.
|
|
||||||
Avoid placing the burden of education on
|
|
||||||
marginalized groups; please make an effort to look into your questions before asking others for
|
marginalized groups; please make an effort to look into your questions before asking others for
|
||||||
detailed explanations.
|
detailed explanations.
|
||||||
|
|
||||||
5. **Be Engaged and Open-Minded**: Actively participate in making our community more inclusive.
|
4. **Be Open to Improving Inclusivity**: Actively participate in making our community more inclusive.
|
||||||
Report behaviour that contradicts these guidelines (see Reporting and Enforcement below) and be
|
Report behaviour that contradicts these guidelines (see Reporting and Enforcement below) and be
|
||||||
open to constructive feedback aimed at improving our community. Understand that discussing
|
open to constructive feedback aimed at improving our community. Understand that discussing
|
||||||
negative experiences can be emotionally taxing; focus on the message, not the tone.
|
negative experiences can be emotionally taxing; focus on the message, not the tone.
|
||||||
|
|
||||||
6. **Commit to Our Values**: Building an inclusive community requires ongoing effort from everyone.
|
5. **Commit to Our Values**: Building an inclusive community requires ongoing effort from everyone.
|
||||||
Recognise that creating a welcoming and open community is a continuous process that needs commitment
|
Recognise that addressing bias and discrimination is a continuous process that needs commitment
|
||||||
and action from all members.
|
and action from all members.
|
||||||
|
|
||||||
## Unacceptable Behaviors
|
## Unacceptable Behaviors
|
||||||
@@ -71,6 +72,36 @@ within the Continuwuity community:
|
|||||||
This is not an exhaustive list. Any behaviour that makes others feel unsafe or unwelcome may be
|
This is not an exhaustive list. Any behaviour that makes others feel unsafe or unwelcome may be
|
||||||
subject to enforcement action.
|
subject to enforcement action.
|
||||||
|
|
||||||
|
## Matrix Community
|
||||||
|
|
||||||
|
These Community Guidelines apply to the entire
|
||||||
|
[Continuwuity Matrix Space](https://matrix.to/#/#space:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org) and its rooms, including:
|
||||||
|
|
||||||
|
### [#continuwuity:continuwuity.org](https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org)
|
||||||
|
|
||||||
|
This room is for support and discussions about Continuwuity. Ask questions, share insights, and help
|
||||||
|
each other out while adhering to these guidelines.
|
||||||
|
|
||||||
|
We ask that this room remain focused on the Continuwuity software specifically: the team are
|
||||||
|
typically happy to engage in conversations about related subjects in the off-topic room.
|
||||||
|
|
||||||
|
### [#offtopic:continuwuity.org](https://matrix.to/#/#offtopic:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org)
|
||||||
|
|
||||||
|
For off-topic community conversations about any subject. While this room allows for a wide range of
|
||||||
|
topics, the same guidelines apply. Please keep discussions respectful and inclusive, and avoid
|
||||||
|
divisive or stressful subjects like specific country/world politics unless handled with exceptional
|
||||||
|
care and respect for diverse viewpoints.
|
||||||
|
|
||||||
|
General topics, such as world events, are welcome as long as they follow the guidelines. If a member
|
||||||
|
of the team asks for the conversation to end, please respect their decision.
|
||||||
|
|
||||||
|
### [#dev:continuwuity.org](https://matrix.to/#/#dev:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org)
|
||||||
|
|
||||||
|
This room is dedicated to discussing active development of Continuwuity, including ongoing issues or
|
||||||
|
code development. Collaboration here must follow these guidelines, and please consider raising
|
||||||
|
[an issue](https://forgejo.ellis.link/continuwuation/continuwuity/issues) on the repository to help
|
||||||
|
track progress.
|
||||||
|
|
||||||
## Reporting and Enforcement
|
## Reporting and Enforcement
|
||||||
|
|
||||||
We take these Community Guidelines seriously to protect our community members. If you witness or
|
We take these Community Guidelines seriously to protect our community members. If you witness or
|
||||||
@@ -83,7 +114,6 @@ experience unacceptable behaviour, or have any other concerns, please report it.
|
|||||||
will immediately alert all available moderators.
|
will immediately alert all available moderators.
|
||||||
* **Direct Message:** If you're not comfortable raising the issue publicly, please send a direct
|
* **Direct Message:** If you're not comfortable raising the issue publicly, please send a direct
|
||||||
message (DM) to one of the room moderators.
|
message (DM) to one of the room moderators.
|
||||||
* **Email**: Please email Jade and/or Nex at `jade@continuwuity.org` and `nex@continuwuity.org` respectively, or email `team@continuwuity.org`.
|
|
||||||
|
|
||||||
Reports will be handled with discretion. We will investigate promptly and thoroughly.
|
Reports will be handled with discretion. We will investigate promptly and thoroughly.
|
||||||
|
|
||||||
|
|||||||
@@ -130,10 +130,6 @@ Trim memory usage
|
|||||||
|
|
||||||
List database files
|
List database files
|
||||||
|
|
||||||
## `!admin debug send-test-email`
|
|
||||||
|
|
||||||
Send a test email to the invoking admin's email address
|
|
||||||
|
|
||||||
## `!admin debug tester`
|
## `!admin debug tester`
|
||||||
|
|
||||||
Developer test stubs
|
Developer test stubs
|
||||||
|
|||||||
@@ -133,18 +133,6 @@ pusher service
|
|||||||
|
|
||||||
Returns all the pushers for the user
|
Returns all the pushers for the user
|
||||||
|
|
||||||
### `!admin query pusher delete-pusher`
|
|
||||||
|
|
||||||
Deletes a specific pusher by ID
|
|
||||||
|
|
||||||
### `!admin query pusher delete-all-user`
|
|
||||||
|
|
||||||
Deletes all pushers for a user
|
|
||||||
|
|
||||||
### `!admin query pusher delete-all-device`
|
|
||||||
|
|
||||||
Deletes all pushers associated with a device ID
|
|
||||||
|
|
||||||
## `!admin query short`
|
## `!admin query short`
|
||||||
|
|
||||||
short service
|
short service
|
||||||
|
|||||||
@@ -47,11 +47,3 @@ Restart the server
|
|||||||
## `!admin server shutdown`
|
## `!admin server shutdown`
|
||||||
|
|
||||||
Shutdown the server
|
Shutdown the server
|
||||||
|
|
||||||
## `!admin server list-features`
|
|
||||||
|
|
||||||
List features built into the server
|
|
||||||
|
|
||||||
## `!admin server build-info`
|
|
||||||
|
|
||||||
Build information
|
|
||||||
|
|||||||
@@ -12,24 +12,6 @@ Create a new user
|
|||||||
|
|
||||||
Reset user password
|
Reset user password
|
||||||
|
|
||||||
## `!admin users issue-password-reset-link`
|
|
||||||
|
|
||||||
Issue a self-service password reset link for a user
|
|
||||||
|
|
||||||
## `!admin users get-email`
|
|
||||||
|
|
||||||
Get a user's associated email address
|
|
||||||
|
|
||||||
## `!admin users get-user-by-email`
|
|
||||||
|
|
||||||
Get the user with the given email address
|
|
||||||
|
|
||||||
## `!admin users change-email`
|
|
||||||
|
|
||||||
Update or remove a user's email address.
|
|
||||||
|
|
||||||
If `email` is not supplied, the user's existing address will be removed.
|
|
||||||
|
|
||||||
## `!admin users deactivate`
|
## `!admin users deactivate`
|
||||||
|
|
||||||
Deactivate a user
|
Deactivate a user
|
||||||
@@ -157,7 +139,3 @@ Force joins all local users to the specified room.
|
|||||||
At least 1 server admin must be in the room to reduce abuse.
|
At least 1 server admin must be in the room to reduce abuse.
|
||||||
|
|
||||||
Requires the `--yes-i-want-to-do-this` flag.
|
Requires the `--yes-i-want-to-do-this` flag.
|
||||||
|
|
||||||
## `!admin users reset-push-rules`
|
|
||||||
|
|
||||||
Resets the push-rules (notification settings) of the target user to the server defaults
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
url = "github:edolstra/flake-compat?ref=master";
|
url = "github:edolstra/flake-compat?ref=master";
|
||||||
flake = false;
|
flake = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
@@ -36,10 +37,10 @@
|
|||||||
flake-parts.lib.mkFlake { inherit inputs; } {
|
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||||
imports = [ ./nix ];
|
imports = [ ./nix ];
|
||||||
systems = [
|
systems = [
|
||||||
|
# good support
|
||||||
"x86_64-linux"
|
"x86_64-linux"
|
||||||
"aarch64-linux"
|
|
||||||
# support untested but theoretically there
|
# support untested but theoretically there
|
||||||
"aarch64-darwin"
|
"aarch64-linux"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
{ inputs, ... }:
|
||||||
|
{
|
||||||
|
perSystem =
|
||||||
|
{
|
||||||
|
self',
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
uwulib = inputs.self.uwulib.init pkgs;
|
||||||
|
|
||||||
|
rocksdbAllFeatures = self'.packages.rocksdb.override {
|
||||||
|
enableJemalloc = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
commonAttrs = (uwulib.build.commonAttrs { }) // {
|
||||||
|
buildInputs = [
|
||||||
|
pkgs.liburing
|
||||||
|
pkgs.rust-jemalloc-sys-unprefixed
|
||||||
|
rocksdbAllFeatures
|
||||||
|
];
|
||||||
|
nativeBuildInputs = [
|
||||||
|
pkgs.pkg-config
|
||||||
|
# bindgen needs the build platform's libclang. Apparently due to "splicing
|
||||||
|
# weirdness", pkgs.rustPlatform.bindgenHook on its own doesn't quite do the
|
||||||
|
# right thing here.
|
||||||
|
pkgs.rustPlatform.bindgenHook
|
||||||
|
];
|
||||||
|
env = {
|
||||||
|
LIBCLANG_PATH = lib.makeLibraryPath [ pkgs.llvmPackages.libclang.lib ];
|
||||||
|
LD_LIBRARY_PATH = lib.makeLibraryPath [
|
||||||
|
pkgs.liburing
|
||||||
|
pkgs.rust-jemalloc-sys-unprefixed
|
||||||
|
rocksdbAllFeatures
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// uwulib.environment.buildPackageEnv
|
||||||
|
// {
|
||||||
|
ROCKSDB_INCLUDE_DIR = "${rocksdbAllFeatures}/include";
|
||||||
|
ROCKSDB_LIB_DIR = "${rocksdbAllFeatures}/lib";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
cargoArtifacts = self'.packages.continuwuity-all-features-deps;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# taken from
|
||||||
|
#
|
||||||
|
# https://crane.dev/examples/quick-start.html
|
||||||
|
checks = {
|
||||||
|
continuwuity-all-features-build = self'.packages.continuwuity-all-features-bin;
|
||||||
|
|
||||||
|
continuwuity-all-features-clippy = uwulib.build.craneLibForChecks.cargoClippy (
|
||||||
|
commonAttrs
|
||||||
|
// {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
cargoClippyExtraArgs = "-- --deny warnings";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
continuwuity-all-features-docs = uwulib.build.craneLibForChecks.cargoDoc (
|
||||||
|
commonAttrs
|
||||||
|
// {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
# This can be commented out or tweaked as necessary, e.g. set to
|
||||||
|
# `--deny rustdoc::broken-intra-doc-links` to only enforce that lint
|
||||||
|
env.RUSTDOCFLAGS = "--deny warnings";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
# Check formatting
|
||||||
|
continuwuity-all-features-fmt = uwulib.build.craneLibForChecks.cargoFmt {
|
||||||
|
src = uwulib.build.src;
|
||||||
|
};
|
||||||
|
|
||||||
|
continuwuity-all-features-toml-fmt = uwulib.build.craneLibForChecks.taploFmt {
|
||||||
|
src = pkgs.lib.sources.sourceFilesBySuffices uwulib.build.src [ ".toml" ];
|
||||||
|
# taplo arguments can be further customized below as needed
|
||||||
|
taploExtraArgs = "--config ${inputs.self}/taplo.toml";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Audit dependencies
|
||||||
|
continuwuity-all-features-audit = uwulib.build.craneLibForChecks.cargoAudit {
|
||||||
|
inherit (inputs) advisory-db;
|
||||||
|
src = uwulib.build.src;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Audit licenses
|
||||||
|
continuwuity-all-features-deny = uwulib.build.craneLibForChecks.cargoDeny {
|
||||||
|
src = uwulib.build.src;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Run tests with cargo-nextest
|
||||||
|
# Consider setting `doCheck = false` on `continuwuity-all-features` if you do not want
|
||||||
|
# the tests to run twice
|
||||||
|
continuwuity-all-features-nextest = uwulib.build.craneLibForChecks.cargoNextest (
|
||||||
|
commonAttrs
|
||||||
|
// {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
partitions = 1;
|
||||||
|
partitionType = "count";
|
||||||
|
cargoNextestPartitionsExtraArgs = "--no-tests=pass";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{ inputs, ... }:
|
|
||||||
{
|
|
||||||
perSystem =
|
|
||||||
{
|
|
||||||
pkgs,
|
|
||||||
self',
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
_module.args.craneLib = (inputs.crane.mkLib pkgs).overrideToolchain (
|
|
||||||
pkgs: self'.packages.stable-toolchain
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
+5
-4
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./rust.nix
|
./checks
|
||||||
./crane.nix
|
|
||||||
./packages
|
./packages
|
||||||
./devshell.nix
|
./shells
|
||||||
|
./tests
|
||||||
|
|
||||||
|
./hydra.nix
|
||||||
./fmt.nix
|
./fmt.nix
|
||||||
./rocksdb-updater.nix
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
{
|
|
||||||
perSystem =
|
|
||||||
{
|
|
||||||
craneLib,
|
|
||||||
self',
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
# basic nix shell containing all things necessary to build continuwuity in all flavors manually (on x86_64-linux)
|
|
||||||
devShells.default = craneLib.devShell {
|
|
||||||
packages = [
|
|
||||||
self'.packages.rocksdb
|
|
||||||
pkgs.nodejs
|
|
||||||
pkgs.pkg-config
|
|
||||||
]
|
|
||||||
++ lib.optionals pkgs.stdenv.isLinux [
|
|
||||||
pkgs.liburing
|
|
||||||
pkgs.rust-jemalloc-sys-unprefixed
|
|
||||||
];
|
|
||||||
|
|
||||||
env = {
|
|
||||||
LIBCLANG_PATH = lib.makeLibraryPath [ pkgs.llvmPackages.libclang.lib ];
|
|
||||||
LD_LIBRARY_PATH = lib.makeLibraryPath (
|
|
||||||
[
|
|
||||||
pkgs.stdenv.cc.cc.lib
|
|
||||||
]
|
|
||||||
++ lib.optionals pkgs.stdenv.isLinux [
|
|
||||||
pkgs.liburing
|
|
||||||
pkgs.jemalloc
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// lib.optionalAttrs pkgs.stdenv.isLinux {
|
|
||||||
PKG_CONFIG_PATH = lib.makeSearchPath "lib/pkgconfig" [
|
|
||||||
pkgs.liburing.dev
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{ inputs, ... }:
|
||||||
|
let
|
||||||
|
lib = inputs.nixpkgs.lib;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
flake.hydraJobs.packages = builtins.mapAttrs (
|
||||||
|
_name: lib.hydraJob
|
||||||
|
) inputs.self.packages.x86_64-linux;
|
||||||
|
}
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
{
|
|
||||||
lib,
|
|
||||||
self,
|
|
||||||
stdenv,
|
|
||||||
liburing,
|
|
||||||
craneLib,
|
|
||||||
pkg-config,
|
|
||||||
callPackage,
|
|
||||||
rustPlatform,
|
|
||||||
cargoExtraArgs ? "",
|
|
||||||
rocksdb ? callPackage ./rocksdb.nix { },
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
# see https://crane.dev/API.html#cranelibfiltercargosources
|
|
||||||
# we need to keep the `web` directory which would be filtered out by the regular source filtering function
|
|
||||||
# https://crane.dev/API.html#cranelibcleancargosource
|
|
||||||
isWebTemplate = path: _type: builtins.match ".*(src/(web|service)|docs).*" path != null;
|
|
||||||
isRust = craneLib.filterCargoSources;
|
|
||||||
isNix = path: _type: builtins.match ".+/nix.*" path != null;
|
|
||||||
webOrRustNotNix = p: t: !(isNix p t) && (isWebTemplate p t || isRust p t);
|
|
||||||
|
|
||||||
src = lib.cleanSourceWith {
|
|
||||||
src = self;
|
|
||||||
filter = webOrRustNotNix;
|
|
||||||
name = "source";
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs = {
|
|
||||||
inherit src;
|
|
||||||
nativeBuildInputs = [
|
|
||||||
pkg-config
|
|
||||||
rustPlatform.bindgenHook
|
|
||||||
];
|
|
||||||
buildInputs = lib.optionals stdenv.hostPlatform.isLinux [ liburing ];
|
|
||||||
env = {
|
|
||||||
ROCKSDB_INCLUDE_DIR = "${rocksdb}/include";
|
|
||||||
ROCKSDB_LIB_DIR = "${rocksdb}/lib";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
craneLib.buildPackage (
|
|
||||||
lib.recursiveUpdate attrs {
|
|
||||||
inherit cargoExtraArgs;
|
|
||||||
cargoArtifacts = craneLib.buildDepsOnly attrs;
|
|
||||||
|
|
||||||
# Needed to make continuwuity link to rocksdb
|
|
||||||
postFixup = lib.optionalString stdenv.hostPlatform.isLinux ''
|
|
||||||
old_rpath="$(patchelf --print-rpath $out/bin/conduwuit)"
|
|
||||||
extra_rpath="${
|
|
||||||
lib.makeLibraryPath [
|
|
||||||
rocksdb
|
|
||||||
]
|
|
||||||
}"
|
|
||||||
|
|
||||||
patchelf --set-rpath "$old_rpath:$extra_rpath" $out/bin/conduwuit
|
|
||||||
'';
|
|
||||||
|
|
||||||
meta = {
|
|
||||||
description = "A community-driven Matrix homeserver in Rust";
|
|
||||||
mainProgram = "conduwuit";
|
|
||||||
platforms = lib.platforms.all;
|
|
||||||
maintainers = with lib.maintainers; [ quadradical ];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
{ inputs, ... }:
|
||||||
|
{
|
||||||
|
perSystem =
|
||||||
|
{
|
||||||
|
self',
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
uwulib = inputs.self.uwulib.init pkgs;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
packages =
|
||||||
|
lib.pipe
|
||||||
|
[
|
||||||
|
# this is the default variant
|
||||||
|
{
|
||||||
|
variantName = "default";
|
||||||
|
commonAttrsArgs.profile = "release";
|
||||||
|
rocksdb = self'.packages.rocksdb;
|
||||||
|
features = { };
|
||||||
|
}
|
||||||
|
# this is the variant with all features enabled (liburing + jemalloc)
|
||||||
|
{
|
||||||
|
variantName = "all-features";
|
||||||
|
commonAttrsArgs.profile = "release";
|
||||||
|
rocksdb = self'.packages.rocksdb.override {
|
||||||
|
enableJemalloc = true;
|
||||||
|
};
|
||||||
|
features = {
|
||||||
|
enabledFeatures = "all";
|
||||||
|
disabledFeatures = uwulib.features.defaultDisabledFeatures ++ [ "bindgen-static" ];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
]
|
||||||
|
[
|
||||||
|
(builtins.map (cfg: rec {
|
||||||
|
deps = {
|
||||||
|
name = "continuwuity-${cfg.variantName}-deps";
|
||||||
|
value = uwulib.build.buildDeps {
|
||||||
|
features = uwulib.features.calcFeatures cfg.features;
|
||||||
|
inherit (cfg) commonAttrsArgs rocksdb;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
bin = {
|
||||||
|
name = "continuwuity-${cfg.variantName}-bin";
|
||||||
|
value = uwulib.build.buildPackage {
|
||||||
|
deps = self'.packages.${deps.name};
|
||||||
|
features = uwulib.features.calcFeatures cfg.features;
|
||||||
|
inherit (cfg) commonAttrsArgs rocksdb;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}))
|
||||||
|
(builtins.concatMap builtins.attrValues)
|
||||||
|
builtins.listToAttrs
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,18 +1,14 @@
|
|||||||
{
|
{
|
||||||
self,
|
imports = [
|
||||||
...
|
./continuwuity
|
||||||
}:
|
./rocksdb
|
||||||
{
|
./rust.nix
|
||||||
|
./uwulib
|
||||||
|
];
|
||||||
|
|
||||||
perSystem =
|
perSystem =
|
||||||
|
{ self', ... }:
|
||||||
{
|
{
|
||||||
pkgs,
|
packages.default = self'.packages.continuwuity-default-bin;
|
||||||
craneLib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
packages = {
|
|
||||||
rocksdb = pkgs.callPackage ./rocksdb.nix { };
|
|
||||||
default = pkgs.callPackage ./continuwuity.nix { inherit self craneLib; };
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
{
|
|
||||||
stdenv,
|
|
||||||
rocksdb,
|
|
||||||
fetchFromGitea,
|
|
||||||
rust-jemalloc-sys-unprefixed,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
(rocksdb.override {
|
|
||||||
# rocksdb fails to build with prefixed jemalloc, which is required on
|
|
||||||
# darwin due to [1]. In this case, fall back to building rocksdb with
|
|
||||||
# libc malloc. This should not cause conflicts, because all of the
|
|
||||||
# jemalloc symbols are prefixed.
|
|
||||||
#
|
|
||||||
# [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17
|
|
||||||
jemalloc = rust-jemalloc-sys-unprefixed;
|
|
||||||
enableJemalloc = stdenv.hostPlatform.isLinux;
|
|
||||||
}).overrideAttrs
|
|
||||||
({
|
|
||||||
version = "continuwuity-v0.5.0-unstable-2026-03-27";
|
|
||||||
src = fetchFromGitea {
|
|
||||||
domain = "forgejo.ellis.link";
|
|
||||||
owner = "continuwuation";
|
|
||||||
repo = "rocksdb";
|
|
||||||
rev = "463f47afceebfe088f6922420265546bd237f249";
|
|
||||||
hash = "sha256-1ef75IDMs5Hba4VWEyXPJb02JyShy5k4gJfzGDhopRk=";
|
|
||||||
};
|
|
||||||
|
|
||||||
# We have this already at https://forgejo.ellis.link/continuwuation/rocksdb/commit/a935c0273e1ba44eacf88ce3685a9b9831486155
|
|
||||||
# Unsetting `patches` so we don't have to revert it and make this nix exclusive
|
|
||||||
patches = [ ];
|
|
||||||
|
|
||||||
# Unset postPatch, as our version override breaks version-specific sed calls in the original package
|
|
||||||
postPatch = "";
|
|
||||||
})
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
perSystem =
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
packages = {
|
||||||
|
rocksdb = pkgs.callPackage ./package.nix { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
stdenv,
|
||||||
|
|
||||||
|
rocksdb,
|
||||||
|
liburing,
|
||||||
|
rust-jemalloc-sys-unprefixed,
|
||||||
|
|
||||||
|
enableJemalloc ? false,
|
||||||
|
|
||||||
|
fetchFromGitea,
|
||||||
|
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
notDarwin = !stdenv.hostPlatform.isDarwin;
|
||||||
|
in
|
||||||
|
(rocksdb.override {
|
||||||
|
# Override the liburing input for the build with our own so
|
||||||
|
# we have it built with the library flag
|
||||||
|
inherit liburing;
|
||||||
|
jemalloc = rust-jemalloc-sys-unprefixed;
|
||||||
|
|
||||||
|
# rocksdb fails to build with prefixed jemalloc, which is required on
|
||||||
|
# darwin due to [1]. In this case, fall back to building rocksdb with
|
||||||
|
# libc malloc. This should not cause conflicts, because all of the
|
||||||
|
# jemalloc symbols are prefixed.
|
||||||
|
#
|
||||||
|
# [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17
|
||||||
|
enableJemalloc = enableJemalloc && notDarwin;
|
||||||
|
|
||||||
|
# for some reason enableLiburing in nixpkgs rocksdb is default true
|
||||||
|
# which breaks Darwin entirely
|
||||||
|
enableLiburing = notDarwin;
|
||||||
|
}).overrideAttrs
|
||||||
|
(old: {
|
||||||
|
src = fetchFromGitea {
|
||||||
|
domain = "forgejo.ellis.link";
|
||||||
|
owner = "continuwuation";
|
||||||
|
repo = "rocksdb";
|
||||||
|
rev = "10.5.fb";
|
||||||
|
sha256 = "sha256-X4ApGLkHF9ceBtBg77dimEpu720I79ffLoyPa8JMHaU=";
|
||||||
|
};
|
||||||
|
version = "10.5.fb";
|
||||||
|
cmakeFlags =
|
||||||
|
lib.subtractLists (builtins.map (flag: lib.cmakeBool flag true) [
|
||||||
|
# No real reason to have snappy or zlib, no one uses this
|
||||||
|
"WITH_SNAPPY"
|
||||||
|
"ZLIB"
|
||||||
|
"WITH_ZLIB"
|
||||||
|
# We don't need to use ldb or sst_dump (core_tools)
|
||||||
|
"WITH_CORE_TOOLS"
|
||||||
|
# We don't need to build rocksdb tests
|
||||||
|
"WITH_TESTS"
|
||||||
|
# We use rust-rocksdb via C interface and don't need C++ RTTI
|
||||||
|
"USE_RTTI"
|
||||||
|
# This doesn't exist in RocksDB, and USE_SSE is deprecated for
|
||||||
|
# PORTABLE=$(march)
|
||||||
|
"FORCE_SSE42"
|
||||||
|
]) old.cmakeFlags
|
||||||
|
++ (builtins.map (flag: lib.cmakeBool flag false) [
|
||||||
|
# No real reason to have snappy, no one uses this
|
||||||
|
"WITH_SNAPPY"
|
||||||
|
"ZLIB"
|
||||||
|
"WITH_ZLIB"
|
||||||
|
# We don't need to use ldb or sst_dump (core_tools)
|
||||||
|
"WITH_CORE_TOOLS"
|
||||||
|
# We don't need trace tools
|
||||||
|
"WITH_TRACE_TOOLS"
|
||||||
|
# We don't need to build rocksdb tests
|
||||||
|
"WITH_TESTS"
|
||||||
|
# We use rust-rocksdb via C interface and don't need C++ RTTI
|
||||||
|
"USE_RTTI"
|
||||||
|
]);
|
||||||
|
|
||||||
|
enableLiburing = notDarwin;
|
||||||
|
|
||||||
|
# outputs has "tools" which we don't need or use
|
||||||
|
outputs = [ "out" ];
|
||||||
|
|
||||||
|
# preInstall hooks has stuff for messing with ldb/sst_dump which we don't need or use
|
||||||
|
preInstall = "";
|
||||||
|
|
||||||
|
# We have this already at https://forgejo.ellis.link/continuwuation/rocksdb/commit/a935c0273e1ba44eacf88ce3685a9b9831486155
|
||||||
|
# Unsetting `patches` so we don't have to revert it and make this nix exclusive
|
||||||
|
patches = [ ];
|
||||||
|
})
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
{
|
{
|
||||||
system,
|
system,
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
@@ -12,7 +11,7 @@
|
|||||||
let
|
let
|
||||||
fnx = inputs.fenix.packages.${system};
|
fnx = inputs.fenix.packages.${system};
|
||||||
|
|
||||||
stable-toolchain = fnx.fromToolchainFile {
|
stable = fnx.fromToolchainFile {
|
||||||
file = inputs.self + "/rust-toolchain.toml";
|
file = inputs.self + "/rust-toolchain.toml";
|
||||||
|
|
||||||
# See also `rust-toolchain.toml`
|
# See also `rust-toolchain.toml`
|
||||||
@@ -20,10 +19,11 @@
|
|||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit stable-toolchain;
|
# used for building nix stuff (doesn't include rustfmt overhead)
|
||||||
|
build-toolchain = stable;
|
||||||
|
# used for dev shells
|
||||||
dev-toolchain = fnx.combine [
|
dev-toolchain = fnx.combine [
|
||||||
stable-toolchain
|
stable
|
||||||
# use the nightly rustfmt because we use nightly features
|
# use the nightly rustfmt because we use nightly features
|
||||||
fnx.complete.rustfmt
|
fnx.complete.rustfmt
|
||||||
];
|
];
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
args@{ pkgs, inputs, ... }:
|
||||||
|
let
|
||||||
|
inherit (pkgs) lib;
|
||||||
|
uwuenv = import ./environment.nix args;
|
||||||
|
selfpkgs = inputs.self.packages.${pkgs.stdenv.system};
|
||||||
|
in
|
||||||
|
rec {
|
||||||
|
# basic, very minimal instance of the crane library with a minimal rust toolchain
|
||||||
|
craneLib = (inputs.crane.mkLib pkgs).overrideToolchain (_: selfpkgs.build-toolchain);
|
||||||
|
# the checks require more rust toolchain components, hence we have this separate instance of the crane library
|
||||||
|
craneLibForChecks = (inputs.crane.mkLib pkgs).overrideToolchain (_: selfpkgs.dev-toolchain);
|
||||||
|
|
||||||
|
# meta information (name, version, etc) of the rust crate based on the Cargo.toml
|
||||||
|
crateInfo = craneLib.crateNameFromCargoToml { cargoToml = "${inputs.self}/Cargo.toml"; };
|
||||||
|
|
||||||
|
src =
|
||||||
|
let
|
||||||
|
# see https://crane.dev/API.html#cranelibfiltercargosources
|
||||||
|
#
|
||||||
|
# we need to keep the `web` directory which would be filtered out by the regular source filtering function
|
||||||
|
#
|
||||||
|
# https://crane.dev/API.html#cranelibcleancargosource
|
||||||
|
isWebTemplate = path: _type: builtins.match ".*(src/(web|service)|docs).*" path != null;
|
||||||
|
isRust = craneLib.filterCargoSources;
|
||||||
|
isNix = path: _type: builtins.match ".+/nix.*" path != null;
|
||||||
|
webOrRustNotNix = p: t: !(isNix p t) && (isWebTemplate p t || isRust p t);
|
||||||
|
in
|
||||||
|
lib.cleanSourceWith {
|
||||||
|
src = inputs.self;
|
||||||
|
filter = webOrRustNotNix;
|
||||||
|
name = "source";
|
||||||
|
};
|
||||||
|
|
||||||
|
# common attrs that are shared between building continuwuity's deps and the package itself
|
||||||
|
commonAttrs =
|
||||||
|
{
|
||||||
|
profile ? "dev",
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
inherit (crateInfo)
|
||||||
|
pname
|
||||||
|
version
|
||||||
|
;
|
||||||
|
inherit src;
|
||||||
|
|
||||||
|
# this prevents unnecessary rebuilds
|
||||||
|
strictDeps = true;
|
||||||
|
|
||||||
|
dontStrip = profile == "dev" || profile == "test";
|
||||||
|
dontPatchELF = profile == "dev" || profile == "test";
|
||||||
|
|
||||||
|
doCheck = true;
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
# bindgen needs the build platform's libclang. Apparently due to "splicing
|
||||||
|
# weirdness", pkgs.rustPlatform.bindgenHook on its own doesn't quite do the
|
||||||
|
# right thing here.
|
||||||
|
pkgs.rustPlatform.bindgenHook
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
makeRocksDBEnv =
|
||||||
|
{ rocksdb }:
|
||||||
|
{
|
||||||
|
ROCKSDB_INCLUDE_DIR = "${rocksdb}/include";
|
||||||
|
ROCKSDB_LIB_DIR = "${rocksdb}/lib";
|
||||||
|
};
|
||||||
|
|
||||||
|
# function that builds the continuwuity dependencies derivation
|
||||||
|
buildDeps =
|
||||||
|
{
|
||||||
|
rocksdb,
|
||||||
|
features,
|
||||||
|
commonAttrsArgs,
|
||||||
|
}:
|
||||||
|
craneLib.buildDepsOnly (
|
||||||
|
(commonAttrs commonAttrsArgs)
|
||||||
|
// {
|
||||||
|
env = uwuenv.buildDepsOnlyEnv
|
||||||
|
// (makeRocksDBEnv { inherit rocksdb; })
|
||||||
|
// {
|
||||||
|
# required since we started using unstable reqwest apparently ... otherwise the all-features build will fail
|
||||||
|
RUSTFLAGS = "--cfg reqwest_unstable";
|
||||||
|
};
|
||||||
|
inherit (features) cargoExtraArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
# function that builds the continuwuity package
|
||||||
|
buildPackage =
|
||||||
|
{
|
||||||
|
deps,
|
||||||
|
rocksdb,
|
||||||
|
features,
|
||||||
|
commonAttrsArgs,
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
rocksdbEnv = makeRocksDBEnv { inherit rocksdb; };
|
||||||
|
in
|
||||||
|
craneLib.buildPackage (
|
||||||
|
(commonAttrs commonAttrsArgs)
|
||||||
|
// {
|
||||||
|
postFixup = ''
|
||||||
|
patchelf --set-rpath "$(${pkgs.patchelf}/bin/patchelf --print-rpath $out/bin/${crateInfo.pname}):${rocksdb}/lib" $out/bin/${crateInfo.pname}
|
||||||
|
'';
|
||||||
|
cargoArtifacts = deps;
|
||||||
|
doCheck = true;
|
||||||
|
env =
|
||||||
|
uwuenv.buildPackageEnv
|
||||||
|
// rocksdbEnv
|
||||||
|
// {
|
||||||
|
# required since we started using unstable reqwest apparently ... otherwise the all-features build will fail
|
||||||
|
RUSTFLAGS = "--cfg reqwest_unstable";
|
||||||
|
};
|
||||||
|
passthru.env = uwuenv.buildPackageEnv // rocksdbEnv;
|
||||||
|
meta.mainProgram = crateInfo.pname;
|
||||||
|
inherit (features) cargoExtraArgs;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{ inputs, ... }:
|
||||||
|
{
|
||||||
|
flake.uwulib = {
|
||||||
|
init = pkgs: {
|
||||||
|
features = import ./features.nix { inherit pkgs inputs; };
|
||||||
|
environment = import ./environment.nix { inherit pkgs inputs; };
|
||||||
|
build = import ./build.nix { inherit pkgs inputs; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
args@{ pkgs, inputs, ... }:
|
||||||
|
let
|
||||||
|
uwubuild = import ./build.nix args;
|
||||||
|
in
|
||||||
|
rec {
|
||||||
|
buildDepsOnlyEnv = {
|
||||||
|
# https://crane.dev/faq/rebuilds-bindgen.html
|
||||||
|
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
|
||||||
|
CARGO_PROFILE = "release";
|
||||||
|
}
|
||||||
|
// uwubuild.craneLib.mkCrossToolchainEnv (p: pkgs.clangStdenv);
|
||||||
|
|
||||||
|
buildPackageEnv = {
|
||||||
|
GIT_COMMIT_HASH = inputs.self.rev or inputs.self.dirtyRev or "";
|
||||||
|
GIT_COMMIT_HASH_SHORT = inputs.self.shortRev or inputs.self.dirtyShortRev or "";
|
||||||
|
}
|
||||||
|
// buildDepsOnlyEnv;
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
{ pkgs, inputs, ... }:
|
||||||
|
let
|
||||||
|
inherit (pkgs) lib;
|
||||||
|
in
|
||||||
|
rec {
|
||||||
|
defaultDisabledFeatures = [
|
||||||
|
# dont include experimental features
|
||||||
|
"experimental"
|
||||||
|
# jemalloc profiling/stats features are expensive and shouldn't
|
||||||
|
# be expected on non-debug builds.
|
||||||
|
"jemalloc_prof"
|
||||||
|
"jemalloc_stats"
|
||||||
|
# this is non-functional on nix for some reason
|
||||||
|
"hardened_malloc"
|
||||||
|
# conduwuit_mods is a development-only hot reload feature
|
||||||
|
"conduwuit_mods"
|
||||||
|
# we don't want to enable this feature set by default but be more specific about it
|
||||||
|
"full"
|
||||||
|
];
|
||||||
|
# We perform default-feature unification in nix, because some of the dependencies
|
||||||
|
# on the nix side depend on feature values.
|
||||||
|
calcFeatures =
|
||||||
|
{
|
||||||
|
tomlPath ? "${inputs.self}/src/main",
|
||||||
|
# either a list of feature names or a string "all" which enables all non-default features
|
||||||
|
enabledFeatures ? [ ],
|
||||||
|
disabledFeatures ? defaultDisabledFeatures,
|
||||||
|
default_features ? true,
|
||||||
|
disable_release_max_log_level ? false,
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
# simple helper to get the contents of a Cargo.toml file in a nix format
|
||||||
|
getToml = path: lib.importTOML "${path}/Cargo.toml";
|
||||||
|
|
||||||
|
# get all the features except for the default features
|
||||||
|
allFeatures = lib.pipe tomlPath [
|
||||||
|
getToml
|
||||||
|
(manifest: manifest.features)
|
||||||
|
lib.attrNames
|
||||||
|
(lib.remove "default")
|
||||||
|
];
|
||||||
|
|
||||||
|
# get just the default enabled features
|
||||||
|
allDefaultFeatures = lib.pipe tomlPath [
|
||||||
|
getToml
|
||||||
|
(manifest: manifest.features.default)
|
||||||
|
];
|
||||||
|
|
||||||
|
# depending on the value of enabledFeatures choose just a set or all non-default features
|
||||||
|
#
|
||||||
|
# - [ list of features ] -> choose exactly the features listed
|
||||||
|
# - "all" -> choose all non-default features
|
||||||
|
additionalFeatures = if enabledFeatures == "all" then allFeatures else enabledFeatures;
|
||||||
|
|
||||||
|
# unification with default features (if enabled)
|
||||||
|
features = lib.unique (additionalFeatures ++ lib.optionals default_features allDefaultFeatures);
|
||||||
|
|
||||||
|
# prepare the features that are subtracted from the set
|
||||||
|
disabledFeatures' =
|
||||||
|
disabledFeatures ++ lib.optionals disable_release_max_log_level [ "release_max_log_level" ];
|
||||||
|
|
||||||
|
# construct the final feature set
|
||||||
|
finalFeatures = lib.subtractLists disabledFeatures' features;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# final feature set, useful for querying it
|
||||||
|
features = finalFeatures;
|
||||||
|
|
||||||
|
# crane flag with the relevant features
|
||||||
|
cargoExtraArgs = builtins.concatStringsSep " " [
|
||||||
|
"--no-default-features"
|
||||||
|
"--locked"
|
||||||
|
(lib.optionalString (finalFeatures != [ ]) "--features")
|
||||||
|
(builtins.concatStringsSep "," finalFeatures)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
perSystem =
|
|
||||||
{ pkgs, ... }:
|
|
||||||
{
|
|
||||||
apps.update-rocksdb = {
|
|
||||||
type = "app";
|
|
||||||
program = pkgs.writeShellApplication {
|
|
||||||
name = "update-rocksdb";
|
|
||||||
runtimeInputs = [ pkgs.nix-update ];
|
|
||||||
text = "nix-update rocksdb -F --version branch";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{ inputs, ... }:
|
||||||
|
{
|
||||||
|
perSystem =
|
||||||
|
{
|
||||||
|
self',
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
uwulib = inputs.self.uwulib.init pkgs;
|
||||||
|
rocksdbAllFeatures = self'.packages.rocksdb.override {
|
||||||
|
enableJemalloc = true;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# basic nix shell containing all things necessary to build continuwuity in all flavors manually (on x86_64-linux)
|
||||||
|
devShells.default = uwulib.build.craneLib.devShell {
|
||||||
|
packages = [
|
||||||
|
pkgs.nodejs
|
||||||
|
pkgs.pkg-config
|
||||||
|
pkgs.liburing
|
||||||
|
pkgs.rust-jemalloc-sys-unprefixed
|
||||||
|
rocksdbAllFeatures
|
||||||
|
];
|
||||||
|
env.LIBCLANG_PATH = lib.makeLibraryPath [ pkgs.llvmPackages.libclang.lib ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
{
|
||||||
|
perSystem =
|
||||||
|
{
|
||||||
|
self',
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
baseTestScript =
|
||||||
|
pkgs.writers.writePython3Bin "do_test" { libraries = [ pkgs.python3Packages.matrix-nio ]; }
|
||||||
|
''
|
||||||
|
import asyncio
|
||||||
|
import nio
|
||||||
|
|
||||||
|
|
||||||
|
async def main() -> None:
|
||||||
|
# Connect to continuwuity
|
||||||
|
client = nio.AsyncClient("http://continuwuity:6167", "alice")
|
||||||
|
|
||||||
|
# Register as user alice
|
||||||
|
response = await client.register("alice", "my-secret-password")
|
||||||
|
|
||||||
|
# Log in as user alice
|
||||||
|
response = await client.login("my-secret-password")
|
||||||
|
|
||||||
|
# Create a new room
|
||||||
|
response = await client.room_create(federate=False)
|
||||||
|
print("Matrix room create response:", response)
|
||||||
|
assert isinstance(response, nio.RoomCreateResponse)
|
||||||
|
room_id = response.room_id
|
||||||
|
|
||||||
|
# Join the room
|
||||||
|
response = await client.join(room_id)
|
||||||
|
print("Matrix join response:", response)
|
||||||
|
assert isinstance(response, nio.JoinResponse)
|
||||||
|
|
||||||
|
# Send a message to the room
|
||||||
|
response = await client.room_send(
|
||||||
|
room_id=room_id,
|
||||||
|
message_type="m.room.message",
|
||||||
|
content={
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"body": "Hello continuwuity!"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
print("Matrix room send response:", response)
|
||||||
|
assert isinstance(response, nio.RoomSendResponse)
|
||||||
|
|
||||||
|
# Sync responses
|
||||||
|
response = await client.sync(timeout=30000)
|
||||||
|
print("Matrix sync response:", response)
|
||||||
|
assert isinstance(response, nio.SyncResponse)
|
||||||
|
|
||||||
|
# Check the message was received by continuwuity
|
||||||
|
last_message = response.rooms.join[room_id].timeline.events[-1].body
|
||||||
|
assert last_message == "Hello continuwuity!"
|
||||||
|
|
||||||
|
# Leave the room
|
||||||
|
response = await client.room_leave(room_id)
|
||||||
|
print("Matrix room leave response:", response)
|
||||||
|
assert isinstance(response, nio.RoomLeaveResponse)
|
||||||
|
|
||||||
|
# Close the client
|
||||||
|
await client.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# run some nixos tests as checks
|
||||||
|
checks = lib.pipe self'.packages [
|
||||||
|
# we take all packages (names)
|
||||||
|
builtins.attrNames
|
||||||
|
# we filter out all packages that end with `-bin` (which we are interested in for testing)
|
||||||
|
(builtins.filter (lib.hasSuffix "-bin"))
|
||||||
|
# for each of these binaries we built the basic nixos test
|
||||||
|
#
|
||||||
|
# this test was initially yoinked from
|
||||||
|
#
|
||||||
|
# https://github.com/NixOS/nixpkgs/blob/960ce26339661b1b69c6f12b9063ca51b688615f/nixos/tests/matrix/continuwuity.nix
|
||||||
|
(builtins.concatMap (
|
||||||
|
name:
|
||||||
|
builtins.map
|
||||||
|
(
|
||||||
|
{ config, suffix }:
|
||||||
|
{
|
||||||
|
name = "test-${name}-${suffix}";
|
||||||
|
value = pkgs.testers.runNixOSTest {
|
||||||
|
inherit name;
|
||||||
|
|
||||||
|
nodes = {
|
||||||
|
continuwuity = {
|
||||||
|
services.matrix-continuwuity = {
|
||||||
|
enable = true;
|
||||||
|
package = self'.packages.${name};
|
||||||
|
settings = config;
|
||||||
|
extraEnvironment.RUST_BACKTRACE = "yes";
|
||||||
|
};
|
||||||
|
networking.firewall.allowedTCPPorts = [ 6167 ];
|
||||||
|
};
|
||||||
|
client.environment.systemPackages = [ baseTestScript ];
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
start_all()
|
||||||
|
|
||||||
|
with subtest("start continuwuity"):
|
||||||
|
continuwuity.wait_for_unit("continuwuity.service")
|
||||||
|
continuwuity.wait_for_open_port(6167)
|
||||||
|
|
||||||
|
with subtest("ensure messages can be exchanged"):
|
||||||
|
client.succeed("${lib.getExe baseTestScript} >&2")
|
||||||
|
'';
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
[
|
||||||
|
{
|
||||||
|
suffix = "base";
|
||||||
|
config = {
|
||||||
|
global = {
|
||||||
|
server_name = name;
|
||||||
|
address = [ "0.0.0.0" ];
|
||||||
|
allow_registration = true;
|
||||||
|
yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
suffix = "with-room-version";
|
||||||
|
config = {
|
||||||
|
global = {
|
||||||
|
server_name = name;
|
||||||
|
address = [ "0.0.0.0" ];
|
||||||
|
allow_registration = true;
|
||||||
|
yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true;
|
||||||
|
default_room_version = "12";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
]
|
||||||
|
))
|
||||||
|
builtins.listToAttrs
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
Generated
+80
-80
@@ -123,14 +123,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rsbuild/core": {
|
"node_modules/@rsbuild/core": {
|
||||||
"version": "2.0.0-beta.11",
|
"version": "2.0.0-beta.10",
|
||||||
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.0.0-beta.11.tgz",
|
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.0.0-beta.10.tgz",
|
||||||
"integrity": "sha512-IBbQx7SrnSpD7j2p2qyq3qDxoqmG4E6lcflTpbBitX6iUrzpVRQbP4rktXZ2iuY7ph9+FtUK/SVAVA+Ocm3Nig==",
|
"integrity": "sha512-6xalOGzWjamJQvC+qnAipo6azfW3cn9JSRSkTMBz/hiXFzcfy54GX31gCDhRY0TooEisyJ2wbGWjGcT8zPwwxg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rspack/core": "2.0.0-beta.9",
|
"@rspack/core": "2.0.0-beta.8",
|
||||||
"@swc/helpers": "^0.5.20"
|
"@swc/helpers": "^0.5.19"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"rsbuild": "bin/rsbuild.js"
|
"rsbuild": "bin/rsbuild.js"
|
||||||
@@ -167,28 +167,28 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding": {
|
"node_modules/@rspack/binding": {
|
||||||
"version": "2.0.0-beta.9",
|
"version": "2.0.0-beta.8",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.0.0-beta.9.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.0.0-beta.8.tgz",
|
||||||
"integrity": "sha512-QgkOvzl6BJc4Vg5eaY9r7MkHNfXvVZPgTIeYkdBEOYPowdyCLhlG9vH7QltqLKP9KDNel70YIeMyUrpTqez01w==",
|
"integrity": "sha512-6tG/yYhUIF1zcEF7qw9GPA1Bwj5gq+Hqy4OzVzIBUWOn/2bKsFTWuorEJh8Yx1LwOnjNO7O+NbsATvk5zEOGKQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rspack/binding-darwin-arm64": "2.0.0-beta.9",
|
"@rspack/binding-darwin-arm64": "2.0.0-beta.8",
|
||||||
"@rspack/binding-darwin-x64": "2.0.0-beta.9",
|
"@rspack/binding-darwin-x64": "2.0.0-beta.8",
|
||||||
"@rspack/binding-linux-arm64-gnu": "2.0.0-beta.9",
|
"@rspack/binding-linux-arm64-gnu": "2.0.0-beta.8",
|
||||||
"@rspack/binding-linux-arm64-musl": "2.0.0-beta.9",
|
"@rspack/binding-linux-arm64-musl": "2.0.0-beta.8",
|
||||||
"@rspack/binding-linux-x64-gnu": "2.0.0-beta.9",
|
"@rspack/binding-linux-x64-gnu": "2.0.0-beta.8",
|
||||||
"@rspack/binding-linux-x64-musl": "2.0.0-beta.9",
|
"@rspack/binding-linux-x64-musl": "2.0.0-beta.8",
|
||||||
"@rspack/binding-wasm32-wasi": "2.0.0-beta.9",
|
"@rspack/binding-wasm32-wasi": "2.0.0-beta.8",
|
||||||
"@rspack/binding-win32-arm64-msvc": "2.0.0-beta.9",
|
"@rspack/binding-win32-arm64-msvc": "2.0.0-beta.8",
|
||||||
"@rspack/binding-win32-ia32-msvc": "2.0.0-beta.9",
|
"@rspack/binding-win32-ia32-msvc": "2.0.0-beta.8",
|
||||||
"@rspack/binding-win32-x64-msvc": "2.0.0-beta.9"
|
"@rspack/binding-win32-x64-msvc": "2.0.0-beta.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding-darwin-arm64": {
|
"node_modules/@rspack/binding-darwin-arm64": {
|
||||||
"version": "2.0.0-beta.9",
|
"version": "2.0.0-beta.8",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.0.0-beta.9.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.0.0-beta.8.tgz",
|
||||||
"integrity": "sha512-9Aao24b+lrVGG25itl2c7e6HK6eNH5J5ao1Uq5UoSwSJZOxRPuY+QlHIvE2tyt833Ly9qcT1J7os2AIUNlF6Vw==",
|
"integrity": "sha512-h3x2GreEh8J36A3cWFeHZGTuz4vjUArk9dBDq8fZSyaUQQQox/lp8bUOGa/2YuYUOXk0gei2GN+/BVi2R5p39A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -200,9 +200,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding-darwin-x64": {
|
"node_modules/@rspack/binding-darwin-x64": {
|
||||||
"version": "2.0.0-beta.9",
|
"version": "2.0.0-beta.8",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.0.0-beta.9.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.0.0-beta.8.tgz",
|
||||||
"integrity": "sha512-sP6gusMsxm3W4aHpRsmVaBQU09n1p/1+XpLHT/gZy6nJ7Wy3nqfNKNoybNBORwCuFcGUon6cVRcieN9AEm6iJA==",
|
"integrity": "sha512-+XTA37+FZjXgwxkNX94T/EqspFO8Q3Km4CklQ3nOQzieMi31w+TLBB0uTsnT1ugp0UTN5PHLd4DFK1SQB7Ckbg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -214,9 +214,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding-linux-arm64-gnu": {
|
"node_modules/@rspack/binding-linux-arm64-gnu": {
|
||||||
"version": "2.0.0-beta.9",
|
"version": "2.0.0-beta.8",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.0.0-beta.9.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.0.0-beta.8.tgz",
|
||||||
"integrity": "sha512-k2DPN3B2qaz4L/h/R+l7rbDk/lLwbR/sayfsHZ8sLdZ3f6pvaSI9ejrsFv0nU4OmKCQsz4zYuoKTVFPtDfbGjA==",
|
"integrity": "sha512-vD2+ztbMmeBR65jBlwUZCNIjUzO0exp/LaPSMIhLlqPlk670gMCQ7fmKo3tSgQ9tobfizEA/Atdy3/lW1Rl64A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -231,9 +231,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding-linux-arm64-musl": {
|
"node_modules/@rspack/binding-linux-arm64-musl": {
|
||||||
"version": "2.0.0-beta.9",
|
"version": "2.0.0-beta.8",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.0.0-beta.9.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.0.0-beta.8.tgz",
|
||||||
"integrity": "sha512-7+XwAsqhfc2rIHMc9mY6RMBTP76RRqmUm1UjidqYdJl5hYBa5apffjeZfJYgAhVbSwKB/tUffzPpEffGUuc5kw==",
|
"integrity": "sha512-jJ1XB7Yz9YdPRA6MJ35S9/mb+3jeI4p9v78E3dexzCPA3G4X7WXbyOcRbUlYcyOlE5MtX5O19rDexqWlkD9tVw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -248,9 +248,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding-linux-x64-gnu": {
|
"node_modules/@rspack/binding-linux-x64-gnu": {
|
||||||
"version": "2.0.0-beta.9",
|
"version": "2.0.0-beta.8",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.0.0-beta.9.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.0.0-beta.8.tgz",
|
||||||
"integrity": "sha512-z/EOUKEq5rq4sYsVSFL9uzdPtTPVA82x3gsRJlDTfEcruZZI7Y6JKUkpDYkC0LivXqyOnoOz8slAFd2/dByRtA==",
|
"integrity": "sha512-qy+fK/tiYw3KvGjTGGMu/mWOdvBYrMO8xva/ouiaRTrx64PPZ6vyqFXOUfHj9rhY5L6aU2NTObpV6HZHcBtmhQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -265,9 +265,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding-linux-x64-musl": {
|
"node_modules/@rspack/binding-linux-x64-musl": {
|
||||||
"version": "2.0.0-beta.9",
|
"version": "2.0.0-beta.8",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.0.0-beta.9.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.0.0-beta.8.tgz",
|
||||||
"integrity": "sha512-LVIXrqtAOy/DowIB04jyUyYy+5kHtZNJ0W5EJd39OwY/9gGvhgAEVvSWu7JrRAvKW1kQsV7GnRT5ninbDrRw1A==",
|
"integrity": "sha512-eJF1IsayHhsURu5Dp6fzdr5jYGeJmoREOZAc9UV3aEqY6zNAcWgZT1RwKCCujJylmHgCTCOuxqdK/VdFJqWDyw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -282,9 +282,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding-wasm32-wasi": {
|
"node_modules/@rspack/binding-wasm32-wasi": {
|
||||||
"version": "2.0.0-beta.9",
|
"version": "2.0.0-beta.8",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.0.0-beta.9.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.0.0-beta.8.tgz",
|
||||||
"integrity": "sha512-Vl7aDAt7DCqtZ/RJd8hLFjQqufX+efL/XZG3qADsagl/SspH1ItJ7N6X1S8o50eKoshy27Jr7mQYZEdufX9qhQ==",
|
"integrity": "sha512-HssdOQE8i+nUWoK+NDeD5OSyNxf80k3elKCl/due3WunoNn0h6tUTSZ8QB+bhcT4tjH9vTbibWZIT91avtvUNw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"wasm32"
|
"wasm32"
|
||||||
],
|
],
|
||||||
@@ -296,9 +296,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding-win32-arm64-msvc": {
|
"node_modules/@rspack/binding-win32-arm64-msvc": {
|
||||||
"version": "2.0.0-beta.9",
|
"version": "2.0.0-beta.8",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.0.0-beta.9.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.0.0-beta.8.tgz",
|
||||||
"integrity": "sha512-g4Fc3JjfibuHt5ltoV64eK0bs6NKlh8kgHA8Go3ETwEGO6OBck877e+5CqPtjTH8c1/KQPbnCoccGR1OScoZGg==",
|
"integrity": "sha512-RuHbXuIMJr0ANMFoGXIb3sUZE5VwIsJw70u3TKPwfoaOFiJjgW7Pi2JTLPoTYfOlE+CNcu2ldX8VJRBbktR4NA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -310,9 +310,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding-win32-ia32-msvc": {
|
"node_modules/@rspack/binding-win32-ia32-msvc": {
|
||||||
"version": "2.0.0-beta.9",
|
"version": "2.0.0-beta.8",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.0.0-beta.9.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.0.0-beta.8.tgz",
|
||||||
"integrity": "sha512-Oii4HpCEH3CBDKSXcS6EVlV9nGYVKAV/uBLSsuZ0RNdEG0i+OHvEiicqHAwuIYZNlH4Ea/Vwc+Dl5PM2twCZ4Q==",
|
"integrity": "sha512-ajzIOk30zjTKPiay+d6oV7lqzzqdgIXQhDD5YtcOqPn7NTh7949EB1NZX5l3Ueh1m8k4DSe7n07qFLjHDhZ8jw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -324,9 +324,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding-win32-x64-msvc": {
|
"node_modules/@rspack/binding-win32-x64-msvc": {
|
||||||
"version": "2.0.0-beta.9",
|
"version": "2.0.0-beta.8",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.0.0-beta.9.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.0.0-beta.8.tgz",
|
||||||
"integrity": "sha512-7UFjyy7QMtWvf1CBEVQkHL6bJBKaVY9yq9+Qxb7ggtxvpBbkoYykdsrhMTvr/f5TBjBqHmyeb0/oYXqo5pWFBQ==",
|
"integrity": "sha512-MqPuHCbxyLSEjavbhYapHs7cvs2zSA9GKd8nJtDuSMmDTVHFzwHfUXTffUMFB4JTCAvdpMn8XtOG/UOW5QVRCA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -338,13 +338,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/core": {
|
"node_modules/@rspack/core": {
|
||||||
"version": "2.0.0-beta.9",
|
"version": "2.0.0-beta.8",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.0.0-beta.9.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.0.0-beta.8.tgz",
|
||||||
"integrity": "sha512-4sN3f72l4cj8n/dSCdWn6FkSjfHiDxHWrO1Kmqd0Bk0MmgyW+ldHitsSWPETCAxjTJGXY34r5sou5sYzb0DRww==",
|
"integrity": "sha512-GHiMNhcxfzJV3DqxIYYjiBGzhFkwwt+jSJl8+aVFRbQM0AYRdZJSfQDH4G5rHD1gO2yc3ktOOMHYnZWNtXCwdA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rspack/binding": "2.0.0-beta.9"
|
"@rspack/binding": "2.0.0-beta.8"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^20.19.0 || >=22.12.0"
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
@@ -383,17 +383,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rspress/core": {
|
"node_modules/@rspress/core": {
|
||||||
"version": "2.0.8",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.7.tgz",
|
||||||
"integrity": "sha512-MDkpm6fO0+NoW+Lx0KVL/n9DSRGQcoggeXY+EtlC+ySqF9VxQk4hu87fQhD8q2ikMOd7lbVsWmKspd3rIFD88g==",
|
"integrity": "sha512-+HH6EVSs1SVvm+6l78lluK8u70ihKVX26VHEqYJzTBHLtipMXllmX2mfkjCoATEP2uqU+es4xSPurss+AztHWg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdx-js/mdx": "^3.1.1",
|
"@mdx-js/mdx": "^3.1.1",
|
||||||
"@mdx-js/react": "^3.1.1",
|
"@mdx-js/react": "^3.1.1",
|
||||||
"@rsbuild/core": "2.0.0-beta.11",
|
"@rsbuild/core": "2.0.0-beta.10",
|
||||||
"@rsbuild/plugin-react": "~1.4.6",
|
"@rsbuild/plugin-react": "~1.4.6",
|
||||||
"@rspress/shared": "2.0.8",
|
"@rspress/shared": "2.0.7",
|
||||||
"@shikijs/rehype": "^4.0.2",
|
"@shikijs/rehype": "^4.0.2",
|
||||||
"@types/unist": "^3.0.3",
|
"@types/unist": "^3.0.3",
|
||||||
"@unhead/react": "^2.1.12",
|
"@unhead/react": "^2.1.12",
|
||||||
@@ -417,7 +417,7 @@
|
|||||||
"react-lazy-with-preload": "^2.2.1",
|
"react-lazy-with-preload": "^2.2.1",
|
||||||
"react-reconciler": "0.33.0",
|
"react-reconciler": "0.33.0",
|
||||||
"react-render-to-markdown": "19.0.1",
|
"react-render-to-markdown": "19.0.1",
|
||||||
"react-router-dom": "^7.13.2",
|
"react-router-dom": "^7.13.1",
|
||||||
"rehype-external-links": "^3.0.0",
|
"rehype-external-links": "^3.0.0",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
"remark-cjk-friendly": "^2.0.1",
|
"remark-cjk-friendly": "^2.0.1",
|
||||||
@@ -443,39 +443,39 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rspress/plugin-client-redirects": {
|
"node_modules/@rspress/plugin-client-redirects": {
|
||||||
"version": "2.0.8",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.7.tgz",
|
||||||
"integrity": "sha512-6/+CYf4u2PGOmuQkqvzLeUKTdOlj+Fnt3D/6IgjZmbXcSDweLvHhC+dHgdZw7T4paiqIxeCqU0duYX8W5agAug==",
|
"integrity": "sha512-fH8HMUktt5ar6alSpGSDE/+dgY+TU/h0bW+4ntysUApKodNF1vg4ta60qju+5guWeRXD/35nH6rMV2Z0nW9BfQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^20.19.0 || >=22.12.0"
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@rspress/core": "^2.0.8"
|
"@rspress/core": "^2.0.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rspress/plugin-sitemap": {
|
"node_modules/@rspress/plugin-sitemap": {
|
||||||
"version": "2.0.8",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.7.tgz",
|
||||||
"integrity": "sha512-V3u+wRvzmJmC+GkigvQYDQfEo43xUlya9OVLqSyRcB/crQ0U99oto6v73isZB/qS/pTb2wbFY+CbzOqrD1uBsA==",
|
"integrity": "sha512-K4Y8yhpiQkF+cbpfgVhdj/sfwq0K/12AusT06gfVSdJV7R4GvJO28y1GxfrsHlki8nopWu8Zqfqb3dcrNlccfA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^20.19.0 || >=22.12.0"
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@rspress/core": "^2.0.8"
|
"@rspress/core": "^2.0.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rspress/shared": {
|
"node_modules/@rspress/shared": {
|
||||||
"version": "2.0.8",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.7.tgz",
|
||||||
"integrity": "sha512-kvfBUvMvWcn/7PJHqZxPeu1yblzvAuB1/gk/1orp5KsYu3wbZ7X3Hsm9smDJVs5Plw1iPt67t9fOYNSM0+VjUA==",
|
"integrity": "sha512-x7OqCGP5Ir1/X+6fvhtApw/ObHfwVIdWne6LlX3GGUHyHF+01yci6vrUzEP2R6PainnqySzW2+345C6zZ3dZuA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rsbuild/core": "2.0.0-beta.11",
|
"@rsbuild/core": "2.0.0-beta.10",
|
||||||
"@shikijs/rehype": "^4.0.2",
|
"@shikijs/rehype": "^4.0.2",
|
||||||
"gray-matter": "4.0.3",
|
"gray-matter": "4.0.3",
|
||||||
"lodash-es": "^4.17.23",
|
"lodash-es": "^4.17.23",
|
||||||
@@ -609,9 +609,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@swc/helpers": {
|
"node_modules/@swc/helpers": {
|
||||||
"version": "0.5.20",
|
"version": "0.5.19",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.20.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.19.tgz",
|
||||||
"integrity": "sha512-2egEBHUMasdypIzrprsu8g+OEVd7Vp2MM3a2eVlM/cyFYto0nGz5BX5BTgh/ShZZI9ed+ozEq+Ngt+rgmUs8tw==",
|
"integrity": "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -3151,9 +3151,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-router": {
|
"node_modules/react-router": {
|
||||||
"version": "7.13.2",
|
"version": "7.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz",
|
||||||
"integrity": "sha512-tX1Aee+ArlKQP+NIUd7SE6Li+CiGKwQtbS+FfRxPX6Pe4vHOo6nr9d++u5cwg+Z8K/x8tP+7qLmujDtfrAoUJA==",
|
"integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -3174,13 +3174,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-router-dom": {
|
"node_modules/react-router-dom": {
|
||||||
"version": "7.13.2",
|
"version": "7.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz",
|
||||||
"integrity": "sha512-aR7SUORwTqAW0JDeiWF07e9SBE9qGpByR9I8kJT5h/FrBKxPMS6TiC7rmVO+gC0q52Bx7JnjWe8Z1sR9faN4YA==",
|
"integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react-router": "7.13.2"
|
"react-router": "7.13.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
name = "conduwuit_admin"
|
name = "conduwuit_admin"
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
homepage.workspace = true
|
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
readme.workspace = true
|
readme.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
@@ -80,9 +79,7 @@ conduwuit-database.workspace = true
|
|||||||
conduwuit-macros.workspace = true
|
conduwuit-macros.workspace = true
|
||||||
conduwuit-service.workspace = true
|
conduwuit-service.workspace = true
|
||||||
const-str.workspace = true
|
const-str.workspace = true
|
||||||
ctor.workspace = true
|
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
lettre.workspace = true
|
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
ruma.workspace = true
|
ruma.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ use conduwuit::{
|
|||||||
warn,
|
warn,
|
||||||
};
|
};
|
||||||
use futures::{FutureExt, StreamExt, TryStreamExt};
|
use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||||
use lettre::message::Mailbox;
|
|
||||||
use ruma::{
|
use ruma::{
|
||||||
CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId,
|
CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId,
|
||||||
OwnedRoomOrAliasId, OwnedServerName, RoomId, RoomVersionId,
|
OwnedRoomOrAliasId, OwnedServerName, RoomId, RoomVersionId,
|
||||||
@@ -877,31 +876,3 @@ pub(super) async fn trim_memory(&self) -> Result {
|
|||||||
|
|
||||||
writeln!(self, "done").await
|
writeln!(self, "done").await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[admin_command]
|
|
||||||
pub(super) async fn send_test_email(&self) -> Result {
|
|
||||||
self.bail_restricted()?;
|
|
||||||
|
|
||||||
let mailer = self.services.mailer.expect_mailer()?;
|
|
||||||
let Some(sender) = self.sender else {
|
|
||||||
return Err!("No sender user provided in context");
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(email) = self
|
|
||||||
.services
|
|
||||||
.threepid
|
|
||||||
.get_email_for_localpart(sender.localpart())
|
|
||||||
.await
|
|
||||||
else {
|
|
||||||
return Err!("{} has no associated email address", sender);
|
|
||||||
};
|
|
||||||
|
|
||||||
mailer
|
|
||||||
.send(Mailbox::new(None, email.clone()), service::mailer::messages::Test)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
self.write_str(&format!("Test email successfully sent to {email}"))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -225,9 +225,6 @@ pub enum DebugCommand {
|
|||||||
level: Option<i32>,
|
level: Option<i32>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Send a test email to the invoking admin's email address
|
|
||||||
SendTestEmail,
|
|
||||||
|
|
||||||
/// Developer test stubs
|
/// Developer test stubs
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
#![allow(clippy::enum_glob_use)]
|
#![allow(clippy::enum_glob_use)]
|
||||||
#![allow(clippy::too_many_arguments)]
|
#![allow(clippy::too_many_arguments)]
|
||||||
|
|
||||||
conduwuit_macros::introspect_crate! {}
|
|
||||||
|
|
||||||
pub(crate) mod admin;
|
pub(crate) mod admin;
|
||||||
pub(crate) mod context;
|
pub(crate) mod context;
|
||||||
pub(crate) mod processor;
|
pub(crate) mod processor;
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use conduwuit::{
|
use conduwuit::Result;
|
||||||
Result,
|
use ruma::OwnedUserId;
|
||||||
utils::{IterStream, stream::BroadbandExt},
|
|
||||||
};
|
|
||||||
use futures::StreamExt;
|
|
||||||
use ruma::{OwnedDeviceId, OwnedUserId};
|
|
||||||
|
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
|
|
||||||
@@ -15,23 +11,6 @@ pub enum PusherCommand {
|
|||||||
/// Full user ID
|
/// Full user ID
|
||||||
user_id: OwnedUserId,
|
user_id: OwnedUserId,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Deletes a specific pusher by ID
|
|
||||||
DeletePusher {
|
|
||||||
user_id: OwnedUserId,
|
|
||||||
pusher_id: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Deletes all pushers for a user
|
|
||||||
DeleteAllUser {
|
|
||||||
user_id: OwnedUserId,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Deletes all pushers associated with a device ID
|
|
||||||
DeleteAllDevice {
|
|
||||||
user_id: OwnedUserId,
|
|
||||||
device_id: OwnedDeviceId,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn process(subcommand: PusherCommand, context: &Context<'_>) -> Result {
|
pub(super) async fn process(subcommand: PusherCommand, context: &Context<'_>) -> Result {
|
||||||
@@ -45,51 +24,6 @@ pub(super) async fn process(subcommand: PusherCommand, context: &Context<'_>) ->
|
|||||||
|
|
||||||
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
|
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
|
||||||
},
|
},
|
||||||
| PusherCommand::DeletePusher { user_id, pusher_id } => {
|
|
||||||
services.pusher.delete_pusher(&user_id, &pusher_id).await;
|
|
||||||
write!(context, "Deleted pusher {pusher_id} for {user_id}.")
|
|
||||||
},
|
|
||||||
| PusherCommand::DeleteAllUser { user_id } => {
|
|
||||||
let pushers = services
|
|
||||||
.pusher
|
|
||||||
.get_pushkeys(&user_id)
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.await;
|
|
||||||
let pusher_count = pushers.len();
|
|
||||||
pushers
|
|
||||||
.stream()
|
|
||||||
.for_each(async |pushkey| {
|
|
||||||
services.pusher.delete_pusher(&user_id, pushkey).await;
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
write!(context, "Deleted {pusher_count} pushers for {user_id}.")
|
|
||||||
},
|
|
||||||
| PusherCommand::DeleteAllDevice { user_id, device_id } => {
|
|
||||||
let pushers = services
|
|
||||||
.pusher
|
|
||||||
.get_pushkeys(&user_id)
|
|
||||||
.map(ToOwned::to_owned)
|
|
||||||
.broad_filter_map(async |pushkey| {
|
|
||||||
services
|
|
||||||
.pusher
|
|
||||||
.get_pusher_device(&pushkey)
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|pusher_device| pusher_device == &device_id)
|
|
||||||
.then_some(pushkey)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.await;
|
|
||||||
let pusher_count = pushers.len();
|
|
||||||
pushers
|
|
||||||
.stream()
|
|
||||||
.for_each(async |pushkey| {
|
|
||||||
services.pusher.delete_pusher(&user_id, &pushkey).await;
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
write!(context, "Deleted {pusher_count} pushers for {device_id}.")
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::{fmt::Write, path::PathBuf, sync::Arc};
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Result,
|
Err, Result,
|
||||||
@@ -153,97 +153,3 @@ pub(super) async fn shutdown(&self) -> Result {
|
|||||||
|
|
||||||
self.write_str("Shutting down server...").await
|
self.write_str("Shutting down server...").await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[admin_command]
|
|
||||||
pub(super) async fn list_features(&self) -> Result {
|
|
||||||
let mut enabled_features = conduwuit::info::introspection::ENABLED_FEATURES
|
|
||||||
.lock()
|
|
||||||
.expect("locked")
|
|
||||||
.iter()
|
|
||||||
.flat_map(|(_, f)| f.iter())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
enabled_features.sort_unstable();
|
|
||||||
enabled_features.dedup();
|
|
||||||
|
|
||||||
let mut available_features = conduwuit::build_metadata::WORKSPACE_FEATURES
|
|
||||||
.iter()
|
|
||||||
.flat_map(|(_, f)| f.iter())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
available_features.sort_unstable();
|
|
||||||
available_features.dedup();
|
|
||||||
|
|
||||||
let mut features = String::new();
|
|
||||||
|
|
||||||
for feature in available_features {
|
|
||||||
let active = enabled_features.contains(&feature);
|
|
||||||
let emoji = if active { "✅" } else { "❌" };
|
|
||||||
let remark = if active { "[enabled]" } else { "" };
|
|
||||||
writeln!(features, "{emoji} {feature} {remark}")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.write_str(&features).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[admin_command]
|
|
||||||
pub(super) async fn build_info(&self) -> Result {
|
|
||||||
use conduwuit::build_metadata::built;
|
|
||||||
|
|
||||||
let mut info = String::new();
|
|
||||||
|
|
||||||
// Version information
|
|
||||||
writeln!(info, "# Build Information\n")?;
|
|
||||||
writeln!(info, "**Version:** {}", built::PKG_VERSION)?;
|
|
||||||
writeln!(info, "**Package:** {}", built::PKG_NAME)?;
|
|
||||||
writeln!(info, "**Description:** {}", built::PKG_DESCRIPTION)?;
|
|
||||||
|
|
||||||
// Git information
|
|
||||||
writeln!(info, "\n## Git Information\n")?;
|
|
||||||
if let Some(hash) = conduwuit::build_metadata::GIT_COMMIT_HASH {
|
|
||||||
writeln!(info, "**Commit Hash:** {hash}")?;
|
|
||||||
}
|
|
||||||
if let Some(hash) = conduwuit::build_metadata::GIT_COMMIT_HASH_SHORT {
|
|
||||||
writeln!(info, "**Commit Hash (short):** {hash}")?;
|
|
||||||
}
|
|
||||||
if let Some(url) = conduwuit::build_metadata::GIT_REMOTE_WEB_URL {
|
|
||||||
writeln!(info, "**Repository:** {url}")?;
|
|
||||||
}
|
|
||||||
if let Some(url) = conduwuit::build_metadata::GIT_REMOTE_COMMIT_URL {
|
|
||||||
writeln!(info, "**Commit URL:** {url}")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build environment
|
|
||||||
writeln!(info, "\n## Build Environment\n")?;
|
|
||||||
writeln!(info, "**Profile:** {}", built::PROFILE)?;
|
|
||||||
writeln!(info, "**Optimization Level:** {}", built::OPT_LEVEL)?;
|
|
||||||
writeln!(info, "**Debug:** {}", built::DEBUG)?;
|
|
||||||
writeln!(info, "**Target:** {}", built::TARGET)?;
|
|
||||||
writeln!(info, "**Host:** {}", built::HOST)?;
|
|
||||||
|
|
||||||
// Rust compiler information
|
|
||||||
writeln!(info, "\n## Compiler Information\n")?;
|
|
||||||
writeln!(info, "**Rustc Version:** {}", built::RUSTC_VERSION)?;
|
|
||||||
if !built::RUSTDOC_VERSION.is_empty() {
|
|
||||||
writeln!(info, "**Rustdoc Version:** {}", built::RUSTDOC_VERSION)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Target configuration
|
|
||||||
writeln!(info, "\n## Target Configuration\n")?;
|
|
||||||
writeln!(info, "**Architecture:** {}", built::CFG_TARGET_ARCH)?;
|
|
||||||
writeln!(info, "**OS:** {}", built::CFG_OS)?;
|
|
||||||
writeln!(info, "**Family:** {}", built::CFG_FAMILY)?;
|
|
||||||
writeln!(info, "**Endianness:** {}", built::CFG_ENDIAN)?;
|
|
||||||
writeln!(info, "**Pointer Width:** {} bits", built::CFG_POINTER_WIDTH)?;
|
|
||||||
if !built::CFG_ENV.is_empty() {
|
|
||||||
writeln!(info, "**Environment:** {}", built::CFG_ENV)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// CI information
|
|
||||||
if let Some(ci) = built::CI_PLATFORM {
|
|
||||||
writeln!(info, "\n## CI Platform\n")?;
|
|
||||||
writeln!(info, "**Platform:** {ci}")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.write_str(&info).await
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -52,10 +52,4 @@ pub enum ServerCommand {
|
|||||||
|
|
||||||
/// Shutdown the server
|
/// Shutdown the server
|
||||||
Shutdown,
|
Shutdown,
|
||||||
|
|
||||||
/// List features built into the server
|
|
||||||
ListFeatures,
|
|
||||||
|
|
||||||
/// Build information
|
|
||||||
BuildInfo,
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-119
@@ -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_room, recreate_push_rules_and_return,
|
|
||||||
remote_leave_room,
|
|
||||||
};
|
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Result, debug_warn, error, info,
|
Err, Result, debug_warn, error, info,
|
||||||
matrix::{Event, pdu::PduBuilder},
|
matrix::{Event, pdu::PduBuilder},
|
||||||
@@ -14,7 +11,6 @@ use conduwuit::{
|
|||||||
warn,
|
warn,
|
||||||
};
|
};
|
||||||
use futures::{FutureExt, StreamExt};
|
use futures::{FutureExt, StreamExt};
|
||||||
use lettre::Address;
|
|
||||||
use ruma::{
|
use ruma::{
|
||||||
OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, UserId,
|
OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, UserId,
|
||||||
events::{
|
events::{
|
||||||
@@ -1098,117 +1094,3 @@ pub(super) async fn enable_login(&self, user_id: String) -> Result {
|
|||||||
|
|
||||||
self.write_str(&format!("{user_id} can now log in.")).await
|
self.write_str(&format!("{user_id} can now log in.")).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[admin_command]
|
|
||||||
pub(super) async fn get_email(&self, user_id: String) -> Result {
|
|
||||||
self.bail_restricted()?;
|
|
||||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
|
||||||
|
|
||||||
match self
|
|
||||||
.services
|
|
||||||
.threepid
|
|
||||||
.get_email_for_localpart(user_id.localpart())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
| Some(email) =>
|
|
||||||
self.write_str(&format!("{user_id} has the associated email address {email}."))
|
|
||||||
.await,
|
|
||||||
| None =>
|
|
||||||
self.write_str(&format!("{user_id} has no associated email address."))
|
|
||||||
.await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[admin_command]
|
|
||||||
pub(super) async fn get_user_by_email(&self, email: String) -> Result {
|
|
||||||
self.bail_restricted()?;
|
|
||||||
|
|
||||||
let Ok(email) = Address::try_from(email) else {
|
|
||||||
return Err!("Invalid email address.");
|
|
||||||
};
|
|
||||||
|
|
||||||
match self.services.threepid.get_localpart_for_email(&email).await {
|
|
||||||
| Some(localpart) => {
|
|
||||||
let user_id = OwnedUserId::parse(format!(
|
|
||||||
"@{localpart}:{}",
|
|
||||||
self.services.globals.server_name()
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
self.write_str(&format!("{email} belongs to {user_id}."))
|
|
||||||
.await
|
|
||||||
},
|
|
||||||
| None =>
|
|
||||||
self.write_str(&format!("No user has {email} as their email address."))
|
|
||||||
.await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[admin_command]
|
|
||||||
pub(super) async fn change_email(&self, user_id: String, email: Option<String>) -> Result {
|
|
||||||
self.bail_restricted()?;
|
|
||||||
|
|
||||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
|
||||||
let Ok(new_email) = email.map(Address::try_from).transpose() else {
|
|
||||||
return Err!("Invalid email address.");
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.services.mailer.mailer().is_none() {
|
|
||||||
warn!("SMTP has not been configured on this server, emails cannot be sent.");
|
|
||||||
}
|
|
||||||
|
|
||||||
let current_email = self
|
|
||||||
.services
|
|
||||||
.threepid
|
|
||||||
.get_email_for_localpart(user_id.localpart())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match (current_email, new_email) {
|
|
||||||
| (None, None) =>
|
|
||||||
self.write_str(&format!(
|
|
||||||
"{user_id} already had no associated email. No changes have been made."
|
|
||||||
))
|
|
||||||
.await,
|
|
||||||
| (current_email, Some(new_email)) => {
|
|
||||||
self.services
|
|
||||||
.threepid
|
|
||||||
.associate_localpart_email(user_id.localpart(), &new_email)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if let Some(current_email) = current_email {
|
|
||||||
self.write_str(&format!(
|
|
||||||
"The associated email of {user_id} has been changed from {current_email} to \
|
|
||||||
{new_email}."
|
|
||||||
))
|
|
||||||
.await
|
|
||||||
} else {
|
|
||||||
self.write_str(&format!(
|
|
||||||
"{user_id} has been associated with the email {new_email}."
|
|
||||||
))
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
},
|
|
||||||
| (Some(current_email), None) => {
|
|
||||||
self.services
|
|
||||||
.threepid
|
|
||||||
.disassociate_localpart_email(user_id.localpart())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
self.write_str(&format!(
|
|
||||||
"The associated email of {user_id} has been removed (it was {current_email})."
|
|
||||||
))
|
|
||||||
.await
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[admin_command]
|
|
||||||
pub(super) async fn reset_push_rules(&self, user_id: String) -> Result {
|
|
||||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
|
||||||
if !self.services.users.is_active(&user_id).await {
|
|
||||||
return Err!("User is not active.");
|
|
||||||
}
|
|
||||||
recreate_push_rules_and_return(self.services, &user_id).await?;
|
|
||||||
self.write_str("Reset user's push rules to the server default.")
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -35,24 +35,6 @@ pub enum UserCommand {
|
|||||||
username: String,
|
username: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Get a user's associated email address.
|
|
||||||
GetEmail {
|
|
||||||
user_id: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Get the user with the given email address.
|
|
||||||
GetUserByEmail {
|
|
||||||
email: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Update or remove a user's email address.
|
|
||||||
///
|
|
||||||
/// If `email` is not supplied, the user's existing address will be removed.
|
|
||||||
ChangeEmail {
|
|
||||||
user_id: String,
|
|
||||||
email: Option<String>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Deactivate a user
|
/// Deactivate a user
|
||||||
///
|
///
|
||||||
/// User will be removed from all rooms by default.
|
/// User will be removed from all rooms by default.
|
||||||
@@ -257,10 +239,4 @@ pub enum UserCommand {
|
|||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
yes_i_want_to_do_this: bool,
|
yes_i_want_to_do_this: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Resets the push-rules (notification settings) of the target user to the
|
|
||||||
/// server defaults.
|
|
||||||
ResetPushRules {
|
|
||||||
user_id: String,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
name = "conduwuit_api"
|
name = "conduwuit_api"
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
homepage.workspace = true
|
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
readme.workspace = true
|
readme.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
@@ -77,10 +76,8 @@ axum.workspace = true
|
|||||||
base64.workspace = true
|
base64.workspace = true
|
||||||
bytes.workspace = true
|
bytes.workspace = true
|
||||||
conduwuit-core.workspace = true
|
conduwuit-core.workspace = true
|
||||||
conduwuit-macros.workspace = true
|
|
||||||
conduwuit-service.workspace = true
|
conduwuit-service.workspace = true
|
||||||
const-str.workspace = true
|
const-str.workspace = true
|
||||||
ctor.workspace = true
|
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
hmac.workspace = true
|
hmac.workspace = true
|
||||||
http.workspace = true
|
http.workspace = true
|
||||||
@@ -88,7 +85,6 @@ http-body-util.workspace = true
|
|||||||
hyper.workspace = true
|
hyper.workspace = true
|
||||||
ipaddress.workspace = true
|
ipaddress.workspace = true
|
||||||
itertools.workspace = true
|
itertools.workspace = true
|
||||||
lettre.workspace = true
|
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
reqwest.workspace = true
|
reqwest.workspace = true
|
||||||
|
|||||||
@@ -0,0 +1,980 @@
|
|||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
use axum::extract::State;
|
||||||
|
use axum_client_ip::InsecureClientIp;
|
||||||
|
use conduwuit::{
|
||||||
|
Err, Error, Event, Result, debug_info, err, error, info,
|
||||||
|
matrix::pdu::PduBuilder,
|
||||||
|
utils::{self, ReadyExt, stream::BroadbandExt},
|
||||||
|
warn,
|
||||||
|
};
|
||||||
|
use conduwuit_service::Services;
|
||||||
|
use futures::{FutureExt, StreamExt};
|
||||||
|
use register::RegistrationKind;
|
||||||
|
use ruma::{
|
||||||
|
OwnedRoomId, UserId,
|
||||||
|
api::client::{
|
||||||
|
account::{
|
||||||
|
ThirdPartyIdRemovalStatus, change_password, check_registration_token_validity,
|
||||||
|
deactivate, get_3pids, get_username_availability,
|
||||||
|
register::{self, LoginType},
|
||||||
|
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn,
|
||||||
|
whoami,
|
||||||
|
},
|
||||||
|
uiaa::{AuthFlow, AuthType, UiaaInfo},
|
||||||
|
},
|
||||||
|
events::{
|
||||||
|
GlobalAccountDataEventType, StateEventType,
|
||||||
|
room::{
|
||||||
|
member::{MembershipState, RoomMemberEventContent},
|
||||||
|
message::RoomMessageEventContent,
|
||||||
|
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
push,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH, join_room_by_id_helper};
|
||||||
|
use crate::Ruma;
|
||||||
|
|
||||||
|
const RANDOM_USER_ID_LENGTH: usize = 10;
|
||||||
|
|
||||||
|
/// # `GET /_matrix/client/v3/register/available`
|
||||||
|
///
|
||||||
|
/// Checks if a username is valid and available on this server.
|
||||||
|
///
|
||||||
|
/// Conditions for returning true:
|
||||||
|
/// - The user id is not historical
|
||||||
|
/// - The server name of the user id matches this server
|
||||||
|
/// - No user or appservice on this server already claimed this username
|
||||||
|
///
|
||||||
|
/// Note: This will not reserve the username, so the username might become
|
||||||
|
/// invalid when trying to register
|
||||||
|
#[tracing::instrument(skip_all, fields(%client), name = "register_available", level = "info")]
|
||||||
|
pub(crate) async fn get_register_available_route(
|
||||||
|
State(services): State<crate::State>,
|
||||||
|
InsecureClientIp(client): InsecureClientIp,
|
||||||
|
body: Ruma<get_username_availability::v3::Request>,
|
||||||
|
) -> Result<get_username_availability::v3::Response> {
|
||||||
|
// workaround for https://github.com/matrix-org/matrix-appservice-irc/issues/1780 due to inactivity of fixing the issue
|
||||||
|
let is_matrix_appservice_irc = body.appservice_info.as_ref().is_some_and(|appservice| {
|
||||||
|
appservice.registration.id == "irc"
|
||||||
|
|| appservice.registration.id.contains("matrix-appservice-irc")
|
||||||
|
|| appservice.registration.id.contains("matrix_appservice_irc")
|
||||||
|
});
|
||||||
|
|
||||||
|
if services
|
||||||
|
.globals
|
||||||
|
.forbidden_usernames()
|
||||||
|
.is_match(&body.username)
|
||||||
|
{
|
||||||
|
return Err!(Request(Forbidden("Username is forbidden")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't force the username lowercase if it's from matrix-appservice-irc
|
||||||
|
let body_username = if is_matrix_appservice_irc {
|
||||||
|
body.username.clone()
|
||||||
|
} else {
|
||||||
|
body.username.to_lowercase()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validate user id
|
||||||
|
let user_id =
|
||||||
|
match UserId::parse_with_server_name(&body_username, services.globals.server_name()) {
|
||||||
|
| Ok(user_id) => {
|
||||||
|
if let Err(e) = user_id.validate_strict() {
|
||||||
|
// unless the username is from the broken matrix appservice IRC bridge, we
|
||||||
|
// should follow synapse's behaviour on not allowing things like spaces
|
||||||
|
// and UTF-8 characters in usernames
|
||||||
|
if !is_matrix_appservice_irc {
|
||||||
|
return Err!(Request(InvalidUsername(debug_warn!(
|
||||||
|
"Username {body_username} contains disallowed characters or spaces: \
|
||||||
|
{e}"
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user_id
|
||||||
|
},
|
||||||
|
| Err(e) => {
|
||||||
|
return Err!(Request(InvalidUsername(debug_warn!(
|
||||||
|
"Username {body_username} is not valid: {e}"
|
||||||
|
))));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if username is creative enough
|
||||||
|
if services.users.exists(&user_id).await {
|
||||||
|
return Err!(Request(UserInUse("User ID is not available.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref info) = body.appservice_info {
|
||||||
|
if !info.is_user_match(&user_id) {
|
||||||
|
return Err!(Request(Exclusive("Username is not in an appservice namespace.")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if services.appservice.is_exclusive_user_id(&user_id).await {
|
||||||
|
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(get_username_availability::v3::Response { available: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # `POST /_matrix/client/v3/register`
|
||||||
|
///
|
||||||
|
/// Register an account on this homeserver.
|
||||||
|
///
|
||||||
|
/// You can use [`GET
|
||||||
|
/// /_matrix/client/v3/register/available`](fn.get_register_available_route.
|
||||||
|
/// html) to check if the user id is valid and available.
|
||||||
|
///
|
||||||
|
/// - Only works if registration is enabled
|
||||||
|
/// - If type is guest: ignores all parameters except
|
||||||
|
/// initial_device_display_name
|
||||||
|
/// - If sender is not appservice: Requires UIAA (but we only use a dummy stage)
|
||||||
|
/// - If type is not guest and no username is given: Always fails after UIAA
|
||||||
|
/// check
|
||||||
|
/// - Creates a new account and populates it with default account data
|
||||||
|
/// - If `inhibit_login` is false: Creates a device and returns device id and
|
||||||
|
/// access_token
|
||||||
|
#[allow(clippy::doc_markdown)]
|
||||||
|
#[tracing::instrument(skip_all, fields(%client), name = "register", level = "info")]
|
||||||
|
pub(crate) async fn register_route(
|
||||||
|
State(services): State<crate::State>,
|
||||||
|
InsecureClientIp(client): InsecureClientIp,
|
||||||
|
body: Ruma<register::v3::Request>,
|
||||||
|
) -> Result<register::v3::Response> {
|
||||||
|
let is_guest = body.kind == RegistrationKind::Guest;
|
||||||
|
let emergency_mode_enabled = services.config.emergency_password.is_some();
|
||||||
|
|
||||||
|
// Allow registration if it's enabled in the config file or if this is the first
|
||||||
|
// run (so the first user account can be created)
|
||||||
|
let allow_registration =
|
||||||
|
services.config.allow_registration || services.firstrun.is_first_run();
|
||||||
|
|
||||||
|
if !allow_registration && body.appservice_info.is_none() {
|
||||||
|
match (body.username.as_ref(), body.initial_device_display_name.as_ref()) {
|
||||||
|
| (Some(username), Some(device_display_name)) => {
|
||||||
|
info!(
|
||||||
|
%is_guest,
|
||||||
|
user = %username,
|
||||||
|
device_name = %device_display_name,
|
||||||
|
"Rejecting registration attempt as registration is disabled"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
| (Some(username), _) => {
|
||||||
|
info!(
|
||||||
|
%is_guest,
|
||||||
|
user = %username,
|
||||||
|
"Rejecting registration attempt as registration is disabled"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
| (_, Some(device_display_name)) => {
|
||||||
|
info!(
|
||||||
|
%is_guest,
|
||||||
|
device_name = %device_display_name,
|
||||||
|
"Rejecting registration attempt as registration is disabled"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
| (None, _) => {
|
||||||
|
info!(
|
||||||
|
%is_guest,
|
||||||
|
"Rejecting registration attempt as registration is disabled"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return Err!(Request(Forbidden(
|
||||||
|
"This server is not accepting registrations at this time."
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_guest && !services.config.allow_guest_registration {
|
||||||
|
info!(
|
||||||
|
"Guest registration disabled, rejecting guest registration attempt, initial device \
|
||||||
|
name: \"{}\"",
|
||||||
|
body.initial_device_display_name.as_deref().unwrap_or("")
|
||||||
|
);
|
||||||
|
return Err!(Request(GuestAccessForbidden("Guest registration is disabled.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// forbid guests from registering if there is not a real admin user yet. give
|
||||||
|
// generic user error.
|
||||||
|
if is_guest && services.users.count().await < 2 {
|
||||||
|
warn!(
|
||||||
|
"Guest account attempted to register before a real admin user has been registered, \
|
||||||
|
rejecting registration. Guest's initial device name: \"{}\"",
|
||||||
|
body.initial_device_display_name.as_deref().unwrap_or("")
|
||||||
|
);
|
||||||
|
return Err!(Request(Forbidden(
|
||||||
|
"This server is not accepting registrations at this time."
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let user_id = match (body.username.as_ref(), is_guest) {
|
||||||
|
| (Some(username), false) => {
|
||||||
|
// workaround for https://github.com/matrix-org/matrix-appservice-irc/issues/1780 due to inactivity of fixing the issue
|
||||||
|
let is_matrix_appservice_irc =
|
||||||
|
body.appservice_info.as_ref().is_some_and(|appservice| {
|
||||||
|
appservice.registration.id == "irc"
|
||||||
|
|| appservice.registration.id.contains("matrix-appservice-irc")
|
||||||
|
|| appservice.registration.id.contains("matrix_appservice_irc")
|
||||||
|
});
|
||||||
|
|
||||||
|
if services.globals.forbidden_usernames().is_match(username)
|
||||||
|
&& !emergency_mode_enabled
|
||||||
|
{
|
||||||
|
return Err!(Request(Forbidden("Username is forbidden")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't force the username lowercase if it's from matrix-appservice-irc
|
||||||
|
let body_username = if is_matrix_appservice_irc {
|
||||||
|
username.clone()
|
||||||
|
} else {
|
||||||
|
username.to_lowercase()
|
||||||
|
};
|
||||||
|
|
||||||
|
let proposed_user_id = match UserId::parse_with_server_name(
|
||||||
|
&body_username,
|
||||||
|
services.globals.server_name(),
|
||||||
|
) {
|
||||||
|
| Ok(user_id) => {
|
||||||
|
if let Err(e) = user_id.validate_strict() {
|
||||||
|
// unless the username is from the broken matrix appservice IRC bridge, or
|
||||||
|
// we are in emergency mode, we should follow synapse's behaviour on
|
||||||
|
// not allowing things like spaces and UTF-8 characters in usernames
|
||||||
|
if !is_matrix_appservice_irc && !emergency_mode_enabled {
|
||||||
|
return Err!(Request(InvalidUsername(debug_warn!(
|
||||||
|
"Username {body_username} contains disallowed characters or \
|
||||||
|
spaces: {e}"
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't allow registration with user IDs that aren't local
|
||||||
|
if !services.globals.user_is_local(&user_id) {
|
||||||
|
return Err!(Request(InvalidUsername(
|
||||||
|
"Username {body_username} is not local to this server"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
user_id
|
||||||
|
},
|
||||||
|
| Err(e) => {
|
||||||
|
return Err!(Request(InvalidUsername(debug_warn!(
|
||||||
|
"Username {body_username} is not valid: {e}"
|
||||||
|
))));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if services.users.exists(&proposed_user_id).await {
|
||||||
|
return Err!(Request(UserInUse("User ID is not available.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
proposed_user_id
|
||||||
|
},
|
||||||
|
| _ => loop {
|
||||||
|
let proposed_user_id = UserId::parse_with_server_name(
|
||||||
|
utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(),
|
||||||
|
services.globals.server_name(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
if !services.users.exists(&proposed_user_id).await {
|
||||||
|
break proposed_user_id;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if body.body.login_type == Some(LoginType::ApplicationService) {
|
||||||
|
match body.appservice_info {
|
||||||
|
| Some(ref info) =>
|
||||||
|
if !info.is_user_match(&user_id) && !emergency_mode_enabled {
|
||||||
|
return Err!(Request(Exclusive(
|
||||||
|
"Username is not in an appservice namespace."
|
||||||
|
)));
|
||||||
|
},
|
||||||
|
| _ => {
|
||||||
|
return Err!(Request(MissingToken("Missing appservice token.")));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else if services.appservice.is_exclusive_user_id(&user_id).await && !emergency_mode_enabled
|
||||||
|
{
|
||||||
|
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// UIAA
|
||||||
|
let mut uiaainfo = UiaaInfo {
|
||||||
|
flows: Vec::new(),
|
||||||
|
completed: Vec::new(),
|
||||||
|
params: Box::default(),
|
||||||
|
session: None,
|
||||||
|
auth_error: None,
|
||||||
|
};
|
||||||
|
let skip_auth = body.appservice_info.is_some() || is_guest;
|
||||||
|
|
||||||
|
// Populate required UIAA flows
|
||||||
|
|
||||||
|
if services.firstrun.is_first_run() {
|
||||||
|
// Registration token forced while in first-run mode
|
||||||
|
uiaainfo.flows.push(AuthFlow {
|
||||||
|
stages: vec![AuthType::RegistrationToken],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if services
|
||||||
|
.registration_tokens
|
||||||
|
.iterate_tokens()
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
// Registration token required
|
||||||
|
uiaainfo.flows.push(AuthFlow {
|
||||||
|
stages: vec![AuthType::RegistrationToken],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if services.config.recaptcha_private_site_key.is_some() {
|
||||||
|
if let Some(pubkey) = &services.config.recaptcha_site_key {
|
||||||
|
// ReCaptcha required
|
||||||
|
uiaainfo
|
||||||
|
.flows
|
||||||
|
.push(AuthFlow { stages: vec![AuthType::ReCaptcha] });
|
||||||
|
uiaainfo.params = serde_json::value::to_raw_value(&serde_json::json!({
|
||||||
|
"m.login.recaptcha": {
|
||||||
|
"public_key": pubkey,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
.expect("Failed to serialize recaptcha params");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if uiaainfo.flows.is_empty() && !skip_auth {
|
||||||
|
// Registration isn't _disabled_, but there's no captcha configured and no
|
||||||
|
// registration tokens currently set. Bail out by default unless open
|
||||||
|
// registration was explicitly enabled.
|
||||||
|
if !services
|
||||||
|
.config
|
||||||
|
.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse
|
||||||
|
{
|
||||||
|
return Err!(Request(Forbidden(
|
||||||
|
"This server is not accepting registrations at this time."
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have open registration enabled (😧), provide a dummy stage
|
||||||
|
uiaainfo = UiaaInfo {
|
||||||
|
flows: vec![AuthFlow { stages: vec![AuthType::Dummy] }],
|
||||||
|
completed: Vec::new(),
|
||||||
|
params: Box::default(),
|
||||||
|
session: None,
|
||||||
|
auth_error: None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !skip_auth {
|
||||||
|
match &body.auth {
|
||||||
|
| Some(auth) => {
|
||||||
|
let (worked, uiaainfo) = services
|
||||||
|
.uiaa
|
||||||
|
.try_auth(
|
||||||
|
&UserId::parse_with_server_name("", services.globals.server_name())
|
||||||
|
.unwrap(),
|
||||||
|
"".into(),
|
||||||
|
auth,
|
||||||
|
&uiaainfo,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
if !worked {
|
||||||
|
return Err(Error::Uiaa(uiaainfo));
|
||||||
|
}
|
||||||
|
// Success!
|
||||||
|
},
|
||||||
|
| _ => match body.json_body {
|
||||||
|
| Some(ref json) => {
|
||||||
|
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||||
|
services.uiaa.create(
|
||||||
|
&UserId::parse_with_server_name("", services.globals.server_name())
|
||||||
|
.unwrap(),
|
||||||
|
"".into(),
|
||||||
|
&uiaainfo,
|
||||||
|
json,
|
||||||
|
);
|
||||||
|
return Err(Error::Uiaa(uiaainfo));
|
||||||
|
},
|
||||||
|
| _ => {
|
||||||
|
return Err!(Request(NotJson("JSON body is not valid")));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let password = if is_guest { None } else { body.password.as_deref() };
|
||||||
|
|
||||||
|
// Create user
|
||||||
|
services.users.create(&user_id, password, None).await?;
|
||||||
|
|
||||||
|
// Default to pretty displayname
|
||||||
|
let mut displayname = user_id.localpart().to_owned();
|
||||||
|
|
||||||
|
// If `new_user_displayname_suffix` is set, registration will push whatever
|
||||||
|
// content is set to the user's display name with a space before it
|
||||||
|
if !services.globals.new_user_displayname_suffix().is_empty()
|
||||||
|
&& body.appservice_info.is_none()
|
||||||
|
{
|
||||||
|
write!(displayname, " {}", services.server.config.new_user_displayname_suffix)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
services
|
||||||
|
.users
|
||||||
|
.set_displayname(&user_id, Some(displayname.clone()));
|
||||||
|
|
||||||
|
// Initial account data
|
||||||
|
services
|
||||||
|
.account_data
|
||||||
|
.update(
|
||||||
|
None,
|
||||||
|
&user_id,
|
||||||
|
GlobalAccountDataEventType::PushRules.to_string().into(),
|
||||||
|
&serde_json::to_value(ruma::events::push_rules::PushRulesEvent {
|
||||||
|
content: ruma::events::push_rules::PushRulesEventContent {
|
||||||
|
global: push::Ruleset::server_default(&user_id),
|
||||||
|
},
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Generate new device id if the user didn't specify one
|
||||||
|
let no_device = body.inhibit_login
|
||||||
|
|| body
|
||||||
|
.appservice_info
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|aps| aps.registration.device_management);
|
||||||
|
let (token, device) = if !no_device {
|
||||||
|
// Don't create a device for inhibited logins
|
||||||
|
let device_id = if is_guest { None } else { body.device_id.clone() }
|
||||||
|
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
|
||||||
|
|
||||||
|
// Generate new token for the device
|
||||||
|
let new_token = utils::random_string(TOKEN_LENGTH);
|
||||||
|
|
||||||
|
// Create device for this account
|
||||||
|
services
|
||||||
|
.users
|
||||||
|
.create_device(
|
||||||
|
&user_id,
|
||||||
|
&device_id,
|
||||||
|
&new_token,
|
||||||
|
body.initial_device_display_name.clone(),
|
||||||
|
Some(client.to_string()),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
debug_info!(%user_id, %device_id, "User account was created");
|
||||||
|
(Some(new_token), Some(device_id))
|
||||||
|
} else {
|
||||||
|
(None, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
let device_display_name = body.initial_device_display_name.as_deref().unwrap_or("");
|
||||||
|
|
||||||
|
// log in conduit admin channel if a non-guest user registered
|
||||||
|
if body.appservice_info.is_none() && !is_guest {
|
||||||
|
if !device_display_name.is_empty() {
|
||||||
|
let notice = format!(
|
||||||
|
"New user \"{user_id}\" registered on this server from IP {client} and device \
|
||||||
|
display name \"{device_display_name}\""
|
||||||
|
);
|
||||||
|
|
||||||
|
info!("{notice}");
|
||||||
|
if services.server.config.admin_room_notices {
|
||||||
|
services.admin.notice(¬ice).await;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let notice = format!("New user \"{user_id}\" registered on this server.");
|
||||||
|
|
||||||
|
info!("{notice}");
|
||||||
|
if services.server.config.admin_room_notices {
|
||||||
|
services.admin.notice(¬ice).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// log in conduit admin channel if a guest registered
|
||||||
|
if body.appservice_info.is_none() && is_guest && services.config.log_guest_registrations {
|
||||||
|
debug_info!("New guest user \"{user_id}\" registered on this server.");
|
||||||
|
|
||||||
|
if !device_display_name.is_empty() {
|
||||||
|
if services.server.config.admin_room_notices {
|
||||||
|
services
|
||||||
|
.admin
|
||||||
|
.notice(&format!(
|
||||||
|
"Guest user \"{user_id}\" with device display name \
|
||||||
|
\"{device_display_name}\" registered on this server from IP {client}"
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
#[allow(clippy::collapsible_else_if)]
|
||||||
|
if services.server.config.admin_room_notices {
|
||||||
|
services
|
||||||
|
.admin
|
||||||
|
.notice(&format!(
|
||||||
|
"Guest user \"{user_id}\" with no device display name registered on \
|
||||||
|
this server from IP {client}",
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !is_guest {
|
||||||
|
// Make the first user to register an administrator and disable first-run mode.
|
||||||
|
let was_first_user = services.firstrun.empower_first_user(&user_id).await?;
|
||||||
|
|
||||||
|
// If the registering user was not the first and we're suspending users on
|
||||||
|
// register, suspend them.
|
||||||
|
if !was_first_user && services.config.suspend_on_register {
|
||||||
|
// Note that we can still do auto joins for suspended users
|
||||||
|
services
|
||||||
|
.users
|
||||||
|
.suspend_account(&user_id, &services.globals.server_user)
|
||||||
|
.await;
|
||||||
|
// And send an @room notice to the admin room, to prompt admins to review the
|
||||||
|
// new user and ideally unsuspend them if deemed appropriate.
|
||||||
|
if services.server.config.admin_room_notices {
|
||||||
|
services
|
||||||
|
.admin
|
||||||
|
.send_loud_message(RoomMessageEventContent::text_plain(format!(
|
||||||
|
"User {user_id} has been suspended as they are not the first user on \
|
||||||
|
this server. Please review and unsuspend them if appropriate."
|
||||||
|
)))
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if body.appservice_info.is_none()
|
||||||
|
&& !services.server.config.auto_join_rooms.is_empty()
|
||||||
|
&& (services.config.allow_guests_auto_join_rooms || !is_guest)
|
||||||
|
{
|
||||||
|
for room in &services.server.config.auto_join_rooms {
|
||||||
|
let Ok(room_id) = services.rooms.alias.resolve(room).await else {
|
||||||
|
error!(
|
||||||
|
"Failed to resolve room alias to room ID when attempting to auto join \
|
||||||
|
{room}, skipping"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.server_in_room(services.globals.server_name(), &room_id)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
warn!(
|
||||||
|
"Skipping room {room} to automatically join as we have never joined before."
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(room_server_name) = room.server_name() {
|
||||||
|
match join_room_by_id_helper(
|
||||||
|
&services,
|
||||||
|
&user_id,
|
||||||
|
&room_id,
|
||||||
|
Some("Automatically joining this room upon registration".to_owned()),
|
||||||
|
&[services.globals.server_name().to_owned(), room_server_name.to_owned()],
|
||||||
|
&body.appservice_info,
|
||||||
|
)
|
||||||
|
.boxed()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
| Err(e) => {
|
||||||
|
// don't return this error so we don't fail registrations
|
||||||
|
error!(
|
||||||
|
"Failed to automatically join room {room} for user {user_id}: {e}"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
| _ => {
|
||||||
|
info!("Automatically joined room {room} for user {user_id}");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(register::v3::Response {
|
||||||
|
access_token: token,
|
||||||
|
user_id,
|
||||||
|
device_id: device,
|
||||||
|
refresh_token: None,
|
||||||
|
expires_in: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # `POST /_matrix/client/r0/account/password`
|
||||||
|
///
|
||||||
|
/// Changes the password of this account.
|
||||||
|
///
|
||||||
|
/// - Requires UIAA to verify user password
|
||||||
|
/// - Changes the password of the sender user
|
||||||
|
/// - The password hash is calculated using argon2 with 32 character salt, the
|
||||||
|
/// plain password is
|
||||||
|
/// not saved
|
||||||
|
///
|
||||||
|
/// If logout_devices is true it does the following for each device except the
|
||||||
|
/// sender device:
|
||||||
|
/// - Invalidates access token
|
||||||
|
/// - Deletes device metadata (device id, device display name, last seen ip,
|
||||||
|
/// last seen ts)
|
||||||
|
/// - Forgets to-device events
|
||||||
|
/// - Triggers device list updates
|
||||||
|
#[tracing::instrument(skip_all, fields(%client), name = "change_password", level = "info")]
|
||||||
|
pub(crate) async fn change_password_route(
|
||||||
|
State(services): State<crate::State>,
|
||||||
|
InsecureClientIp(client): InsecureClientIp,
|
||||||
|
body: Ruma<change_password::v3::Request>,
|
||||||
|
) -> Result<change_password::v3::Response> {
|
||||||
|
// Authentication for this endpoint was made optional, but we need
|
||||||
|
// authentication currently
|
||||||
|
let sender_user = body
|
||||||
|
.sender_user
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
|
||||||
|
|
||||||
|
let mut uiaainfo = UiaaInfo {
|
||||||
|
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
||||||
|
completed: Vec::new(),
|
||||||
|
params: Box::default(),
|
||||||
|
session: None,
|
||||||
|
auth_error: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
match &body.auth {
|
||||||
|
| Some(auth) => {
|
||||||
|
let (worked, uiaainfo) = services
|
||||||
|
.uiaa
|
||||||
|
.try_auth(sender_user, body.sender_device(), auth, &uiaainfo)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !worked {
|
||||||
|
return Err(Error::Uiaa(uiaainfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success!
|
||||||
|
},
|
||||||
|
| _ => match body.json_body {
|
||||||
|
| Some(ref json) => {
|
||||||
|
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||||
|
services
|
||||||
|
.uiaa
|
||||||
|
.create(sender_user, body.sender_device(), &uiaainfo, json);
|
||||||
|
|
||||||
|
return Err(Error::Uiaa(uiaainfo));
|
||||||
|
},
|
||||||
|
| _ => {
|
||||||
|
return Err!(Request(NotJson("JSON body is not valid")));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
services
|
||||||
|
.users
|
||||||
|
.set_password(sender_user, Some(&body.new_password))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if body.logout_devices {
|
||||||
|
// Logout all devices except the current one
|
||||||
|
services
|
||||||
|
.users
|
||||||
|
.all_device_ids(sender_user)
|
||||||
|
.ready_filter(|id| *id != body.sender_device())
|
||||||
|
.for_each(|id| services.users.remove_device(sender_user, id))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Remove all pushers except the ones associated with this session
|
||||||
|
services
|
||||||
|
.pusher
|
||||||
|
.get_pushkeys(sender_user)
|
||||||
|
.map(ToOwned::to_owned)
|
||||||
|
.broad_filter_map(async |pushkey| {
|
||||||
|
services
|
||||||
|
.pusher
|
||||||
|
.get_pusher_device(&pushkey)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.filter(|pusher_device| pusher_device != body.sender_device())
|
||||||
|
.is_some()
|
||||||
|
.then_some(pushkey)
|
||||||
|
})
|
||||||
|
.for_each(async |pushkey| {
|
||||||
|
services.pusher.delete_pusher(sender_user, &pushkey).await;
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("User {sender_user} changed their password.");
|
||||||
|
|
||||||
|
if services.server.config.admin_room_notices {
|
||||||
|
services
|
||||||
|
.admin
|
||||||
|
.notice(&format!("User {sender_user} changed their password."))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(change_password::v3::Response {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # `GET /_matrix/client/v3/account/whoami`
|
||||||
|
///
|
||||||
|
/// Get `user_id` of the sender user.
|
||||||
|
///
|
||||||
|
/// Note: Also works for Application Services
|
||||||
|
pub(crate) async fn whoami_route(
|
||||||
|
State(services): State<crate::State>,
|
||||||
|
body: Ruma<whoami::v3::Request>,
|
||||||
|
) -> Result<whoami::v3::Response> {
|
||||||
|
let is_guest = services
|
||||||
|
.users
|
||||||
|
.is_deactivated(body.sender_user())
|
||||||
|
.await
|
||||||
|
.map_err(|_| {
|
||||||
|
err!(Request(Forbidden("Application service has not registered this user.")))
|
||||||
|
})? && body.appservice_info.is_none();
|
||||||
|
Ok(whoami::v3::Response {
|
||||||
|
user_id: body.sender_user().to_owned(),
|
||||||
|
device_id: body.sender_device.clone(),
|
||||||
|
is_guest,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # `POST /_matrix/client/r0/account/deactivate`
|
||||||
|
///
|
||||||
|
/// Deactivate sender user account.
|
||||||
|
///
|
||||||
|
/// - Leaves all rooms and rejects all invitations
|
||||||
|
/// - Invalidates all access tokens
|
||||||
|
/// - Deletes all device metadata (device id, device display name, last seen ip,
|
||||||
|
/// last seen ts)
|
||||||
|
/// - Forgets all to-device events
|
||||||
|
/// - Triggers device list updates
|
||||||
|
/// - Removes ability to log in again
|
||||||
|
#[tracing::instrument(skip_all, fields(%client), name = "deactivate", level = "info")]
|
||||||
|
pub(crate) async fn deactivate_route(
|
||||||
|
State(services): State<crate::State>,
|
||||||
|
InsecureClientIp(client): InsecureClientIp,
|
||||||
|
body: Ruma<deactivate::v3::Request>,
|
||||||
|
) -> Result<deactivate::v3::Response> {
|
||||||
|
// Authentication for this endpoint was made optional, but we need
|
||||||
|
// authentication currently
|
||||||
|
let sender_user = body
|
||||||
|
.sender_user
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
|
||||||
|
|
||||||
|
let mut uiaainfo = UiaaInfo {
|
||||||
|
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
||||||
|
completed: Vec::new(),
|
||||||
|
params: Box::default(),
|
||||||
|
session: None,
|
||||||
|
auth_error: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
match &body.auth {
|
||||||
|
| Some(auth) => {
|
||||||
|
let (worked, uiaainfo) = services
|
||||||
|
.uiaa
|
||||||
|
.try_auth(sender_user, body.sender_device(), auth, &uiaainfo)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !worked {
|
||||||
|
return Err(Error::Uiaa(uiaainfo));
|
||||||
|
}
|
||||||
|
// Success!
|
||||||
|
},
|
||||||
|
| _ => match body.json_body {
|
||||||
|
| Some(ref json) => {
|
||||||
|
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||||
|
services
|
||||||
|
.uiaa
|
||||||
|
.create(sender_user, body.sender_device(), &uiaainfo, json);
|
||||||
|
|
||||||
|
return Err(Error::Uiaa(uiaainfo));
|
||||||
|
},
|
||||||
|
| _ => {
|
||||||
|
return Err!(Request(NotJson("JSON body is not valid")));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove profile pictures and display name
|
||||||
|
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.rooms_joined(sender_user)
|
||||||
|
.map(Into::into)
|
||||||
|
.collect()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
full_user_deactivate(&services, sender_user, &all_joined_rooms)
|
||||||
|
.boxed()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("User {sender_user} deactivated their account.");
|
||||||
|
|
||||||
|
if services.server.config.admin_room_notices {
|
||||||
|
services
|
||||||
|
.admin
|
||||||
|
.notice(&format!("User {sender_user} deactivated their account."))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(deactivate::v3::Response {
|
||||||
|
id_server_unbind_result: ThirdPartyIdRemovalStatus::NoSupport,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # `GET _matrix/client/v3/account/3pid`
|
||||||
|
///
|
||||||
|
/// Get a list of third party identifiers associated with this account.
|
||||||
|
///
|
||||||
|
/// - Currently always returns empty list
|
||||||
|
pub(crate) async fn third_party_route(
|
||||||
|
body: Ruma<get_3pids::v3::Request>,
|
||||||
|
) -> Result<get_3pids::v3::Response> {
|
||||||
|
let _sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
|
Ok(get_3pids::v3::Response::new(Vec::new()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # `POST /_matrix/client/v3/account/3pid/email/requestToken`
|
||||||
|
///
|
||||||
|
/// "This API should be used to request validation tokens when adding an email
|
||||||
|
/// address to an account"
|
||||||
|
///
|
||||||
|
/// - 403 signals that The homeserver does not allow the third party identifier
|
||||||
|
/// as a contact option.
|
||||||
|
pub(crate) async fn request_3pid_management_token_via_email_route(
|
||||||
|
_body: Ruma<request_3pid_management_token_via_email::v3::Request>,
|
||||||
|
) -> Result<request_3pid_management_token_via_email::v3::Response> {
|
||||||
|
Err!(Request(ThreepidDenied("Third party identifiers are not implemented")))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # `POST /_matrix/client/v3/account/3pid/msisdn/requestToken`
|
||||||
|
///
|
||||||
|
/// "This API should be used to request validation tokens when adding an phone
|
||||||
|
/// number to an account"
|
||||||
|
///
|
||||||
|
/// - 403 signals that The homeserver does not allow the third party identifier
|
||||||
|
/// as a contact option.
|
||||||
|
pub(crate) async fn request_3pid_management_token_via_msisdn_route(
|
||||||
|
_body: Ruma<request_3pid_management_token_via_msisdn::v3::Request>,
|
||||||
|
) -> Result<request_3pid_management_token_via_msisdn::v3::Response> {
|
||||||
|
Err!(Request(ThreepidDenied("Third party identifiers are not implemented")))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # `GET /_matrix/client/v1/register/m.login.registration_token/validity`
|
||||||
|
///
|
||||||
|
/// Checks if the provided registration token is valid at the time of checking.
|
||||||
|
pub(crate) async fn check_registration_token_validity(
|
||||||
|
State(services): State<crate::State>,
|
||||||
|
body: Ruma<check_registration_token_validity::v1::Request>,
|
||||||
|
) -> Result<check_registration_token_validity::v1::Response> {
|
||||||
|
// TODO: ratelimit this pretty heavily
|
||||||
|
|
||||||
|
let valid = services
|
||||||
|
.registration_tokens
|
||||||
|
.validate_token(body.token.clone())
|
||||||
|
.await
|
||||||
|
.is_some();
|
||||||
|
|
||||||
|
Ok(check_registration_token_validity::v1::Response { valid })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs through all the deactivation steps:
|
||||||
|
///
|
||||||
|
/// - Mark as deactivated
|
||||||
|
/// - Removing display name
|
||||||
|
/// - Removing avatar URL and blurhash
|
||||||
|
/// - Removing all profile data
|
||||||
|
/// - Leaving all rooms (and forgets all of them)
|
||||||
|
pub async fn full_user_deactivate(
|
||||||
|
services: &Services,
|
||||||
|
user_id: &UserId,
|
||||||
|
all_joined_rooms: &[OwnedRoomId],
|
||||||
|
) -> Result<()> {
|
||||||
|
services.users.deactivate_account(user_id).await.ok();
|
||||||
|
|
||||||
|
services
|
||||||
|
.users
|
||||||
|
.all_profile_keys(user_id)
|
||||||
|
.ready_for_each(|(profile_key, _)| {
|
||||||
|
services.users.set_profile_key(user_id, &profile_key, None);
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// TODO: Rescind all user invites
|
||||||
|
|
||||||
|
let mut pdu_queue: Vec<(PduBuilder, &OwnedRoomId)> = Vec::new();
|
||||||
|
|
||||||
|
for room_id in all_joined_rooms {
|
||||||
|
let room_power_levels = services
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.room_state_get_content::<RoomPowerLevelsEventContent>(
|
||||||
|
room_id,
|
||||||
|
&StateEventType::RoomPowerLevels,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
let user_can_demote_self =
|
||||||
|
room_power_levels
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|power_levels_content| {
|
||||||
|
RoomPowerLevels::from(power_levels_content.clone())
|
||||||
|
.user_can_change_user_power_level(user_id, user_id)
|
||||||
|
}) || services
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.room_state_get(room_id, &StateEventType::RoomCreate, "")
|
||||||
|
.await
|
||||||
|
.is_ok_and(|event| event.sender() == user_id);
|
||||||
|
|
||||||
|
if user_can_demote_self {
|
||||||
|
let mut power_levels_content = room_power_levels.unwrap_or_default();
|
||||||
|
power_levels_content.users.remove(user_id);
|
||||||
|
let pl_evt = PduBuilder::state(String::new(), &power_levels_content);
|
||||||
|
pdu_queue.push((pl_evt, room_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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::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(())
|
||||||
|
}
|
||||||
@@ -1,436 +0,0 @@
|
|||||||
use axum::extract::State;
|
|
||||||
use axum_client_ip::InsecureClientIp;
|
|
||||||
use conduwuit::{
|
|
||||||
Err, Event, Result, err, info,
|
|
||||||
pdu::PduBuilder,
|
|
||||||
utils::{ReadyExt, stream::BroadbandExt},
|
|
||||||
};
|
|
||||||
use conduwuit_service::Services;
|
|
||||||
use futures::{FutureExt, StreamExt};
|
|
||||||
use lettre::{Address, message::Mailbox};
|
|
||||||
use ruma::{
|
|
||||||
OwnedRoomId, OwnedUserId, UserId,
|
|
||||||
api::client::{
|
|
||||||
account::{
|
|
||||||
ThirdPartyIdRemovalStatus, change_password, check_registration_token_validity,
|
|
||||||
deactivate, get_username_availability, request_password_change_token_via_email,
|
|
||||||
whoami,
|
|
||||||
},
|
|
||||||
uiaa::{AuthFlow, AuthType},
|
|
||||||
},
|
|
||||||
events::{
|
|
||||||
StateEventType,
|
|
||||||
room::{
|
|
||||||
member::{MembershipState, RoomMemberEventContent},
|
|
||||||
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use service::{mailer::messages, uiaa::Identity};
|
|
||||||
|
|
||||||
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH, join_room_by_id_helper};
|
|
||||||
use crate::Ruma;
|
|
||||||
|
|
||||||
pub(crate) mod register;
|
|
||||||
pub(crate) mod threepid;
|
|
||||||
|
|
||||||
/// # `GET /_matrix/client/v3/register/available`
|
|
||||||
///
|
|
||||||
/// Checks if a username is valid and available on this server.
|
|
||||||
///
|
|
||||||
/// Conditions for returning true:
|
|
||||||
/// - The user id is not historical
|
|
||||||
/// - The server name of the user id matches this server
|
|
||||||
/// - No user or appservice on this server already claimed this username
|
|
||||||
///
|
|
||||||
/// Note: This will not reserve the username, so the username might become
|
|
||||||
/// invalid when trying to register
|
|
||||||
#[tracing::instrument(skip_all, fields(%client), name = "register_available", level = "info")]
|
|
||||||
pub(crate) async fn get_register_available_route(
|
|
||||||
State(services): State<crate::State>,
|
|
||||||
InsecureClientIp(client): InsecureClientIp,
|
|
||||||
body: Ruma<get_username_availability::v3::Request>,
|
|
||||||
) -> Result<get_username_availability::v3::Response> {
|
|
||||||
// Validate user id
|
|
||||||
let user_id =
|
|
||||||
match UserId::parse_with_server_name(&body.username, services.globals.server_name()) {
|
|
||||||
| Ok(user_id) => {
|
|
||||||
if let Err(e) = user_id.validate_strict() {
|
|
||||||
return Err!(Request(InvalidUsername(debug_warn!(
|
|
||||||
"Username {} contains disallowed characters or spaces: {e}",
|
|
||||||
body.username
|
|
||||||
))));
|
|
||||||
}
|
|
||||||
|
|
||||||
user_id
|
|
||||||
},
|
|
||||||
| Err(e) => {
|
|
||||||
return Err!(Request(InvalidUsername(debug_warn!(
|
|
||||||
"Username {} is not valid: {e}",
|
|
||||||
body.username
|
|
||||||
))));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if username is creative enough
|
|
||||||
if services.users.exists(&user_id).await {
|
|
||||||
return Err!(Request(UserInUse("User ID is not available.")));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ref info) = body.appservice_info {
|
|
||||||
if !info.is_user_match(&user_id) {
|
|
||||||
return Err!(Request(Exclusive("Username is not in an appservice namespace.")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if services.appservice.is_exclusive_user_id(&user_id).await {
|
|
||||||
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(get_username_availability::v3::Response { available: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # `POST /_matrix/client/r0/account/password`
|
|
||||||
///
|
|
||||||
/// Changes the password of this account.
|
|
||||||
///
|
|
||||||
/// - Requires UIAA to verify user password
|
|
||||||
/// - Changes the password of the sender user
|
|
||||||
/// - The password hash is calculated using argon2 with 32 character salt, the
|
|
||||||
/// plain password is
|
|
||||||
/// not saved
|
|
||||||
///
|
|
||||||
/// If logout_devices is true it does the following for each device except the
|
|
||||||
/// sender device:
|
|
||||||
/// - Invalidates access token
|
|
||||||
/// - Deletes device metadata (device id, device display name, last seen ip,
|
|
||||||
/// last seen ts)
|
|
||||||
/// - Forgets to-device events
|
|
||||||
/// - Triggers device list updates
|
|
||||||
#[tracing::instrument(skip_all, fields(%client), name = "change_password", level = "info")]
|
|
||||||
pub(crate) async fn change_password_route(
|
|
||||||
State(services): State<crate::State>,
|
|
||||||
InsecureClientIp(client): InsecureClientIp,
|
|
||||||
body: Ruma<change_password::v3::Request>,
|
|
||||||
) -> Result<change_password::v3::Response> {
|
|
||||||
let identity = if let Some(ref user_id) = body.sender_user {
|
|
||||||
// A signed-in user is trying to change their password, prompt them for their
|
|
||||||
// existing one
|
|
||||||
|
|
||||||
services
|
|
||||||
.uiaa
|
|
||||||
.authenticate(
|
|
||||||
&body.auth,
|
|
||||||
vec![AuthFlow::new(vec![AuthType::Password])],
|
|
||||||
Box::default(),
|
|
||||||
Some(Identity::from_user_id(user_id)),
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
} else {
|
|
||||||
// A signed-out user is trying to reset their password, prompt them for email
|
|
||||||
// confirmation. Note that we do not _send_ an email here, their client should
|
|
||||||
// have already hit `/account/password/requestToken` to send the email. We
|
|
||||||
// just validate it.
|
|
||||||
|
|
||||||
services
|
|
||||||
.uiaa
|
|
||||||
.authenticate(
|
|
||||||
&body.auth,
|
|
||||||
vec![AuthFlow::new(vec![AuthType::EmailIdentity])],
|
|
||||||
Box::default(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
};
|
|
||||||
|
|
||||||
let sender_user = OwnedUserId::parse(format!(
|
|
||||||
"@{}:{}",
|
|
||||||
identity.localpart.expect("localpart should be known"),
|
|
||||||
services.globals.server_name()
|
|
||||||
))
|
|
||||||
.expect("user ID should be valid");
|
|
||||||
|
|
||||||
services
|
|
||||||
.users
|
|
||||||
.set_password(&sender_user, Some(&body.new_password))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if body.logout_devices {
|
|
||||||
// Logout all devices except the current one
|
|
||||||
services
|
|
||||||
.users
|
|
||||||
.all_device_ids(&sender_user)
|
|
||||||
.ready_filter(|id| *id != body.sender_device())
|
|
||||||
.for_each(|id| services.users.remove_device(&sender_user, id))
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Remove all pushers except the ones associated with this session
|
|
||||||
services
|
|
||||||
.pusher
|
|
||||||
.get_pushkeys(&sender_user)
|
|
||||||
.map(ToOwned::to_owned)
|
|
||||||
.broad_filter_map(async |pushkey| {
|
|
||||||
services
|
|
||||||
.pusher
|
|
||||||
.get_pusher_device(&pushkey)
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
.filter(|pusher_device| pusher_device != body.sender_device())
|
|
||||||
.is_some()
|
|
||||||
.then_some(pushkey)
|
|
||||||
})
|
|
||||||
.for_each(async |pushkey| {
|
|
||||||
services.pusher.delete_pusher(&sender_user, &pushkey).await;
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("User {} changed their password.", &sender_user);
|
|
||||||
|
|
||||||
if services.server.config.admin_room_notices {
|
|
||||||
services
|
|
||||||
.admin
|
|
||||||
.notice(&format!("User {} changed their password.", &sender_user))
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(change_password::v3::Response {})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # `POST /_matrix/client/v3/account/password/email/requestToken`
|
|
||||||
///
|
|
||||||
/// Requests a validation email for the purpose of resetting a user's password.
|
|
||||||
pub(crate) async fn request_password_change_token_via_email_route(
|
|
||||||
State(services): State<crate::State>,
|
|
||||||
body: Ruma<request_password_change_token_via_email::v3::Request>,
|
|
||||||
) -> Result<request_password_change_token_via_email::v3::Response> {
|
|
||||||
let Ok(email) = Address::try_from(body.email.clone()) else {
|
|
||||||
return Err!(Request(InvalidParam("Invalid email address.")));
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(localpart) = services.threepid.get_localpart_for_email(&email).await else {
|
|
||||||
return Err!(Request(ThreepidNotFound(
|
|
||||||
"No account is associated with this email address"
|
|
||||||
)));
|
|
||||||
};
|
|
||||||
|
|
||||||
let user_id =
|
|
||||||
OwnedUserId::parse(format!("@{localpart}:{}", services.globals.server_name())).unwrap();
|
|
||||||
let display_name = services.users.displayname(&user_id).await.ok();
|
|
||||||
|
|
||||||
let session = services
|
|
||||||
.threepid
|
|
||||||
.send_validation_email(
|
|
||||||
Mailbox::new(display_name.clone(), email),
|
|
||||||
|verification_link| messages::PasswordReset {
|
|
||||||
display_name: display_name.as_deref(),
|
|
||||||
user_id: &user_id,
|
|
||||||
verification_link,
|
|
||||||
},
|
|
||||||
&body.client_secret,
|
|
||||||
body.send_attempt.try_into().unwrap(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(request_password_change_token_via_email::v3::Response::new(session))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # `GET /_matrix/client/v3/account/whoami`
|
|
||||||
///
|
|
||||||
/// Get `user_id` of the sender user.
|
|
||||||
///
|
|
||||||
/// Note: Also works for Application Services
|
|
||||||
pub(crate) async fn whoami_route(
|
|
||||||
State(services): State<crate::State>,
|
|
||||||
body: Ruma<whoami::v3::Request>,
|
|
||||||
) -> Result<whoami::v3::Response> {
|
|
||||||
let is_guest = services
|
|
||||||
.users
|
|
||||||
.is_deactivated(body.sender_user())
|
|
||||||
.await
|
|
||||||
.map_err(|_| {
|
|
||||||
err!(Request(Forbidden("Application service has not registered this user.")))
|
|
||||||
})? && body.appservice_info.is_none();
|
|
||||||
Ok(whoami::v3::Response {
|
|
||||||
user_id: body.sender_user().to_owned(),
|
|
||||||
device_id: body.sender_device.clone(),
|
|
||||||
is_guest,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # `POST /_matrix/client/r0/account/deactivate`
|
|
||||||
///
|
|
||||||
/// Deactivate sender user account.
|
|
||||||
///
|
|
||||||
/// - Leaves all rooms and rejects all invitations
|
|
||||||
/// - Invalidates all access tokens
|
|
||||||
/// - Deletes all device metadata (device id, device display name, last seen ip,
|
|
||||||
/// last seen ts)
|
|
||||||
/// - Forgets all to-device events
|
|
||||||
/// - Triggers device list updates
|
|
||||||
/// - Removes ability to log in again
|
|
||||||
#[tracing::instrument(skip_all, fields(%client), name = "deactivate", level = "info")]
|
|
||||||
pub(crate) async fn deactivate_route(
|
|
||||||
State(services): State<crate::State>,
|
|
||||||
InsecureClientIp(client): InsecureClientIp,
|
|
||||||
body: Ruma<deactivate::v3::Request>,
|
|
||||||
) -> Result<deactivate::v3::Response> {
|
|
||||||
// Authentication for this endpoint is technically optional,
|
|
||||||
// but we require the user to be logged in
|
|
||||||
let sender_user = body
|
|
||||||
.sender_user
|
|
||||||
.as_ref()
|
|
||||||
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
|
|
||||||
|
|
||||||
// Prompt the user to confirm with their password using UIAA
|
|
||||||
let _ = services
|
|
||||||
.uiaa
|
|
||||||
.authenticate_password(&body.auth, Some(Identity::from_user_id(sender_user)))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Remove profile pictures and display name
|
|
||||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
|
||||||
.rooms
|
|
||||||
.state_cache
|
|
||||||
.rooms_joined(sender_user)
|
|
||||||
.map(Into::into)
|
|
||||||
.collect()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
full_user_deactivate(&services, sender_user, &all_joined_rooms)
|
|
||||||
.boxed()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
info!("User {sender_user} deactivated their account.");
|
|
||||||
|
|
||||||
if services.server.config.admin_room_notices {
|
|
||||||
services
|
|
||||||
.admin
|
|
||||||
.notice(&format!("User {sender_user} deactivated their account."))
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(deactivate::v3::Response {
|
|
||||||
id_server_unbind_result: ThirdPartyIdRemovalStatus::Success,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # `GET /_matrix/client/v1/register/m.login.registration_token/validity`
|
|
||||||
///
|
|
||||||
/// Checks if the provided registration token is valid at the time of checking.
|
|
||||||
pub(crate) async fn check_registration_token_validity(
|
|
||||||
State(services): State<crate::State>,
|
|
||||||
body: Ruma<check_registration_token_validity::v1::Request>,
|
|
||||||
) -> Result<check_registration_token_validity::v1::Response> {
|
|
||||||
// TODO: ratelimit this pretty heavily
|
|
||||||
|
|
||||||
let valid = services
|
|
||||||
.registration_tokens
|
|
||||||
.validate_token(body.token.clone())
|
|
||||||
.await
|
|
||||||
.is_some();
|
|
||||||
|
|
||||||
Ok(check_registration_token_validity::v1::Response { valid })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs through all the deactivation steps:
|
|
||||||
///
|
|
||||||
/// - Mark as deactivated
|
|
||||||
/// - Removing display name
|
|
||||||
/// - Removing avatar URL and blurhash
|
|
||||||
/// - Removing all profile data
|
|
||||||
/// - Leaving all rooms (and forgets all of them)
|
|
||||||
pub async fn full_user_deactivate(
|
|
||||||
services: &Services,
|
|
||||||
user_id: &UserId,
|
|
||||||
all_joined_rooms: &[OwnedRoomId],
|
|
||||||
) -> Result<()> {
|
|
||||||
services.users.deactivate_account(user_id).await.ok();
|
|
||||||
|
|
||||||
if services.globals.user_is_local(user_id) {
|
|
||||||
let _ = services
|
|
||||||
.threepid
|
|
||||||
.disassociate_localpart_email(user_id.localpart())
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
|
|
||||||
services
|
|
||||||
.users
|
|
||||||
.all_profile_keys(user_id)
|
|
||||||
.ready_for_each(|(profile_key, _)| {
|
|
||||||
services.users.set_profile_key(user_id, &profile_key, None);
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
services
|
|
||||||
.pusher
|
|
||||||
.get_pushkeys(user_id)
|
|
||||||
.for_each(async |pushkey| {
|
|
||||||
services.pusher.delete_pusher(user_id, pushkey).await;
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// TODO: Rescind all user invites
|
|
||||||
|
|
||||||
let mut pdu_queue: Vec<(PduBuilder, &OwnedRoomId)> = Vec::new();
|
|
||||||
|
|
||||||
for room_id in all_joined_rooms {
|
|
||||||
let room_power_levels = services
|
|
||||||
.rooms
|
|
||||||
.state_accessor
|
|
||||||
.room_state_get_content::<RoomPowerLevelsEventContent>(
|
|
||||||
room_id,
|
|
||||||
&StateEventType::RoomPowerLevels,
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
let user_can_demote_self =
|
|
||||||
room_power_levels
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|power_levels_content| {
|
|
||||||
RoomPowerLevels::from(power_levels_content.clone())
|
|
||||||
.user_can_change_user_power_level(user_id, user_id)
|
|
||||||
}) || services
|
|
||||||
.rooms
|
|
||||||
.state_accessor
|
|
||||||
.room_state_get(room_id, &StateEventType::RoomCreate, "")
|
|
||||||
.await
|
|
||||||
.is_ok_and(|event| event.sender() == user_id);
|
|
||||||
|
|
||||||
if user_can_demote_self {
|
|
||||||
let mut power_levels_content = room_power_levels.unwrap_or_default();
|
|
||||||
power_levels_content.users.remove(user_id);
|
|
||||||
let pl_evt = PduBuilder::state(String::new(), &power_levels_content);
|
|
||||||
pdu_queue.push((pl_evt, room_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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::update_all_rooms(services, pdu_queue, user_id)
|
|
||||||
.boxed()
|
|
||||||
.await;
|
|
||||||
for room_id in all_joined_rooms {
|
|
||||||
services.rooms.state_cache.forget(room_id, user_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,626 +0,0 @@
|
|||||||
use std::{collections::HashMap, fmt::Write};
|
|
||||||
|
|
||||||
use axum::extract::State;
|
|
||||||
use axum_client_ip::InsecureClientIp;
|
|
||||||
use conduwuit::{
|
|
||||||
Err, Result, debug_info, error, info,
|
|
||||||
utils::{self},
|
|
||||||
warn,
|
|
||||||
};
|
|
||||||
use conduwuit_service::Services;
|
|
||||||
use futures::{FutureExt, StreamExt};
|
|
||||||
use lettre::{Address, message::Mailbox};
|
|
||||||
use register::RegistrationKind;
|
|
||||||
use ruma::{
|
|
||||||
OwnedUserId, UserId,
|
|
||||||
api::client::{
|
|
||||||
account::{
|
|
||||||
register::{self, LoginType},
|
|
||||||
request_registration_token_via_email,
|
|
||||||
},
|
|
||||||
uiaa::{AuthFlow, AuthType},
|
|
||||||
},
|
|
||||||
events::{GlobalAccountDataEventType, room::message::RoomMessageEventContent},
|
|
||||||
push,
|
|
||||||
};
|
|
||||||
use serde_json::value::RawValue;
|
|
||||||
use service::mailer::messages;
|
|
||||||
|
|
||||||
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH, join_room_by_id_helper};
|
|
||||||
use crate::Ruma;
|
|
||||||
|
|
||||||
const RANDOM_USER_ID_LENGTH: usize = 10;
|
|
||||||
|
|
||||||
/// # `POST /_matrix/client/v3/register`
|
|
||||||
///
|
|
||||||
/// Register an account on this homeserver.
|
|
||||||
///
|
|
||||||
/// You can use [`GET
|
|
||||||
/// /_matrix/client/v3/register/available`](fn.get_register_available_route.
|
|
||||||
/// html) to check if the user id is valid and available.
|
|
||||||
///
|
|
||||||
/// - Only works if registration is enabled
|
|
||||||
/// - If type is guest: ignores all parameters except
|
|
||||||
/// initial_device_display_name
|
|
||||||
/// - If sender is not appservice: Requires UIAA (but we only use a dummy stage)
|
|
||||||
/// - If type is not guest and no username is given: Always fails after UIAA
|
|
||||||
/// check
|
|
||||||
/// - Creates a new account and populates it with default account data
|
|
||||||
/// - If `inhibit_login` is false: Creates a device and returns device id and
|
|
||||||
/// access_token
|
|
||||||
#[allow(clippy::doc_markdown)]
|
|
||||||
#[tracing::instrument(skip_all, fields(%client), name = "register", level = "info")]
|
|
||||||
pub(crate) async fn register_route(
|
|
||||||
State(services): State<crate::State>,
|
|
||||||
InsecureClientIp(client): InsecureClientIp,
|
|
||||||
body: Ruma<register::v3::Request>,
|
|
||||||
) -> Result<register::v3::Response> {
|
|
||||||
let is_guest = body.kind == RegistrationKind::Guest;
|
|
||||||
let emergency_mode_enabled = services.config.emergency_password.is_some();
|
|
||||||
|
|
||||||
// Allow registration if it's enabled in the config file or if this is the first
|
|
||||||
// run (so the first user account can be created)
|
|
||||||
let allow_registration =
|
|
||||||
services.config.allow_registration || services.firstrun.is_first_run();
|
|
||||||
|
|
||||||
if !allow_registration && body.appservice_info.is_none() {
|
|
||||||
match (body.username.as_ref(), body.initial_device_display_name.as_ref()) {
|
|
||||||
| (Some(username), Some(device_display_name)) => {
|
|
||||||
info!(
|
|
||||||
%is_guest,
|
|
||||||
user = %username,
|
|
||||||
device_name = %device_display_name,
|
|
||||||
"Rejecting registration attempt as registration is disabled"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
| (Some(username), _) => {
|
|
||||||
info!(
|
|
||||||
%is_guest,
|
|
||||||
user = %username,
|
|
||||||
"Rejecting registration attempt as registration is disabled"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
| (_, Some(device_display_name)) => {
|
|
||||||
info!(
|
|
||||||
%is_guest,
|
|
||||||
device_name = %device_display_name,
|
|
||||||
"Rejecting registration attempt as registration is disabled"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
| (None, _) => {
|
|
||||||
info!(
|
|
||||||
%is_guest,
|
|
||||||
"Rejecting registration attempt as registration is disabled"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return Err!(Request(Forbidden(
|
|
||||||
"This server is not accepting registrations at this time."
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_guest && !services.config.allow_guest_registration {
|
|
||||||
info!(
|
|
||||||
"Guest registration disabled, rejecting guest registration attempt, initial device \
|
|
||||||
name: \"{}\"",
|
|
||||||
body.initial_device_display_name.as_deref().unwrap_or("")
|
|
||||||
);
|
|
||||||
return Err!(Request(GuestAccessForbidden("Guest registration is disabled.")));
|
|
||||||
}
|
|
||||||
|
|
||||||
// forbid guests from registering if there is not a real admin user yet. give
|
|
||||||
// generic user error.
|
|
||||||
if is_guest && services.firstrun.is_first_run() {
|
|
||||||
warn!(
|
|
||||||
"Guest account attempted to register before a real admin user has been registered, \
|
|
||||||
rejecting registration. Guest's initial device name: \"{}\"",
|
|
||||||
body.initial_device_display_name.as_deref().unwrap_or("")
|
|
||||||
);
|
|
||||||
return Err!(Request(Forbidden(
|
|
||||||
"This server is not accepting registrations at this time."
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Appeservices and guests get to skip auth
|
|
||||||
let skip_auth = body.appservice_info.is_some() || is_guest;
|
|
||||||
|
|
||||||
let identity = if skip_auth {
|
|
||||||
// Appservices and guests have no identity
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
// Perform UIAA to determine the user's identity
|
|
||||||
let (flows, params) = create_registration_uiaa_session(&services).await?;
|
|
||||||
|
|
||||||
Some(
|
|
||||||
services
|
|
||||||
.uiaa
|
|
||||||
.authenticate(&body.auth, flows, params, None)
|
|
||||||
.await?,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
// If the user didn't supply a username but did supply an email, use
|
|
||||||
// the email's user as their initial localpart to avoid falling back to
|
|
||||||
// a randomly generated localpart
|
|
||||||
let supplied_username = body.username.clone().or_else(|| {
|
|
||||||
if let Some(identity) = &identity
|
|
||||||
&& let Some(email) = &identity.email
|
|
||||||
{
|
|
||||||
Some(email.user().to_owned())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let user_id = determine_registration_user_id(
|
|
||||||
&services,
|
|
||||||
supplied_username,
|
|
||||||
is_guest,
|
|
||||||
emergency_mode_enabled,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if body.body.login_type == Some(LoginType::ApplicationService) {
|
|
||||||
// For appservice logins, make sure that the user ID is in the appservice's
|
|
||||||
// namespace
|
|
||||||
|
|
||||||
match body.appservice_info {
|
|
||||||
| Some(ref info) =>
|
|
||||||
if !info.is_user_match(&user_id) && !emergency_mode_enabled {
|
|
||||||
return Err!(Request(Exclusive(
|
|
||||||
"Username is not in an appservice namespace."
|
|
||||||
)));
|
|
||||||
},
|
|
||||||
| _ => {
|
|
||||||
return Err!(Request(MissingToken("Missing appservice token.")));
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else if services.appservice.is_exclusive_user_id(&user_id).await && !emergency_mode_enabled
|
|
||||||
{
|
|
||||||
// For non-appservice logins, ban user IDs which are in an appservice's
|
|
||||||
// namespace (unless emergency mode is enabled)
|
|
||||||
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
|
|
||||||
}
|
|
||||||
|
|
||||||
let password = if is_guest { None } else { body.password.as_deref() };
|
|
||||||
|
|
||||||
// Create user
|
|
||||||
services.users.create(&user_id, password, None).await?;
|
|
||||||
|
|
||||||
// Set an initial display name
|
|
||||||
let mut displayname = user_id.localpart().to_owned();
|
|
||||||
|
|
||||||
// Apply the new user displayname suffix, if it's set
|
|
||||||
if !services.globals.new_user_displayname_suffix().is_empty()
|
|
||||||
&& body.appservice_info.is_none()
|
|
||||||
{
|
|
||||||
write!(displayname, " {}", services.server.config.new_user_displayname_suffix)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
services
|
|
||||||
.users
|
|
||||||
.set_displayname(&user_id, Some(displayname.clone()));
|
|
||||||
|
|
||||||
// Initial account data
|
|
||||||
services
|
|
||||||
.account_data
|
|
||||||
.update(
|
|
||||||
None,
|
|
||||||
&user_id,
|
|
||||||
GlobalAccountDataEventType::PushRules.to_string().into(),
|
|
||||||
&serde_json::to_value(ruma::events::push_rules::PushRulesEvent {
|
|
||||||
content: ruma::events::push_rules::PushRulesEventContent {
|
|
||||||
global: push::Ruleset::server_default(&user_id),
|
|
||||||
},
|
|
||||||
})?,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Generate new device id if the user didn't specify one
|
|
||||||
let no_device = body.inhibit_login
|
|
||||||
|| body
|
|
||||||
.appservice_info
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|aps| aps.registration.device_management);
|
|
||||||
|
|
||||||
let (token, device) = if !no_device {
|
|
||||||
// Don't create a device for inhibited logins
|
|
||||||
let device_id = if is_guest { None } else { body.device_id.clone() }
|
|
||||||
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
|
|
||||||
|
|
||||||
// Generate new token for the device
|
|
||||||
let new_token = utils::random_string(TOKEN_LENGTH);
|
|
||||||
|
|
||||||
// Create device for this account
|
|
||||||
services
|
|
||||||
.users
|
|
||||||
.create_device(
|
|
||||||
&user_id,
|
|
||||||
&device_id,
|
|
||||||
&new_token,
|
|
||||||
body.initial_device_display_name.clone(),
|
|
||||||
Some(client.to_string()),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
debug_info!(%user_id, %device_id, "User account was created");
|
|
||||||
(Some(new_token), Some(device_id))
|
|
||||||
} else {
|
|
||||||
(None, None)
|
|
||||||
};
|
|
||||||
|
|
||||||
// If the user registered with an email, associate it with their account.
|
|
||||||
if let Some(identity) = identity
|
|
||||||
&& let Some(email) = identity.email
|
|
||||||
{
|
|
||||||
// This may fail if the email is already in use, but we already check for that
|
|
||||||
// in `/requestToken`, so ignoring the error is acceptable here in the rare case
|
|
||||||
// that an email is sniped by another user between the `/requestToken` request
|
|
||||||
// and the `/register` request.
|
|
||||||
let _ = services
|
|
||||||
.threepid
|
|
||||||
.associate_localpart_email(user_id.localpart(), &email)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
|
|
||||||
let device_display_name = body.initial_device_display_name.as_deref().unwrap_or("");
|
|
||||||
|
|
||||||
// log in conduit admin channel if a non-guest user registered
|
|
||||||
if body.appservice_info.is_none() && !is_guest {
|
|
||||||
if !device_display_name.is_empty() {
|
|
||||||
let notice = format!(
|
|
||||||
"New user \"{user_id}\" registered on this server from IP {client} and device \
|
|
||||||
display name \"{device_display_name}\""
|
|
||||||
);
|
|
||||||
|
|
||||||
info!("{notice}");
|
|
||||||
if services.server.config.admin_room_notices {
|
|
||||||
services.admin.notice(¬ice).await;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let notice = format!("New user \"{user_id}\" registered on this server.");
|
|
||||||
|
|
||||||
info!("{notice}");
|
|
||||||
if services.server.config.admin_room_notices {
|
|
||||||
services.admin.notice(¬ice).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// log in conduit admin channel if a guest registered
|
|
||||||
if body.appservice_info.is_none() && is_guest && services.config.log_guest_registrations {
|
|
||||||
debug_info!("New guest user \"{user_id}\" registered on this server.");
|
|
||||||
|
|
||||||
if !device_display_name.is_empty() {
|
|
||||||
if services.server.config.admin_room_notices {
|
|
||||||
services
|
|
||||||
.admin
|
|
||||||
.notice(&format!(
|
|
||||||
"Guest user \"{user_id}\" with device display name \
|
|
||||||
\"{device_display_name}\" registered on this server from IP {client}"
|
|
||||||
))
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
#[allow(clippy::collapsible_else_if)]
|
|
||||||
if services.server.config.admin_room_notices {
|
|
||||||
services
|
|
||||||
.admin
|
|
||||||
.notice(&format!(
|
|
||||||
"Guest user \"{user_id}\" with no device display name registered on \
|
|
||||||
this server from IP {client}",
|
|
||||||
))
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !is_guest {
|
|
||||||
// Make the first user to register an administrator and disable first-run mode.
|
|
||||||
let was_first_user = services.firstrun.empower_first_user(&user_id).await?;
|
|
||||||
|
|
||||||
// If the registering user was not the first and we're suspending users on
|
|
||||||
// register, suspend them.
|
|
||||||
if !was_first_user && services.config.suspend_on_register {
|
|
||||||
// Note that we can still do auto joins for suspended users
|
|
||||||
services
|
|
||||||
.users
|
|
||||||
.suspend_account(&user_id, &services.globals.server_user)
|
|
||||||
.await;
|
|
||||||
// And send an @room notice to the admin room, to prompt admins to review the
|
|
||||||
// new user and ideally unsuspend them if deemed appropriate.
|
|
||||||
if services.server.config.admin_room_notices {
|
|
||||||
services
|
|
||||||
.admin
|
|
||||||
.send_loud_message(RoomMessageEventContent::text_plain(format!(
|
|
||||||
"User {user_id} has been suspended as they are not the first user on \
|
|
||||||
this server. Please review and unsuspend them if appropriate."
|
|
||||||
)))
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if body.appservice_info.is_none()
|
|
||||||
&& !services.server.config.auto_join_rooms.is_empty()
|
|
||||||
&& (services.config.allow_guests_auto_join_rooms || !is_guest)
|
|
||||||
{
|
|
||||||
for room in &services.server.config.auto_join_rooms {
|
|
||||||
let Ok(room_id) = services.rooms.alias.resolve(room).await else {
|
|
||||||
error!(
|
|
||||||
"Failed to resolve room alias to room ID when attempting to auto join \
|
|
||||||
{room}, skipping"
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
if !services
|
|
||||||
.rooms
|
|
||||||
.state_cache
|
|
||||||
.server_in_room(services.globals.server_name(), &room_id)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
warn!(
|
|
||||||
"Skipping room {room} to automatically join as we have never joined before."
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(room_server_name) = room.server_name() {
|
|
||||||
match join_room_by_id_helper(
|
|
||||||
&services,
|
|
||||||
&user_id,
|
|
||||||
&room_id,
|
|
||||||
Some("Automatically joining this room upon registration".to_owned()),
|
|
||||||
&[services.globals.server_name().to_owned(), room_server_name.to_owned()],
|
|
||||||
&body.appservice_info,
|
|
||||||
)
|
|
||||||
.boxed()
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
| Err(e) => {
|
|
||||||
// don't return this error so we don't fail registrations
|
|
||||||
error!(
|
|
||||||
"Failed to automatically join room {room} for user {user_id}: {e}"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
| _ => {
|
|
||||||
info!("Automatically joined room {room} for user {user_id}");
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(register::v3::Response {
|
|
||||||
access_token: token,
|
|
||||||
user_id,
|
|
||||||
device_id: device,
|
|
||||||
refresh_token: None,
|
|
||||||
expires_in: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determine which flows and parameters should be presented when
|
|
||||||
/// registering a new account.
|
|
||||||
async fn create_registration_uiaa_session(
|
|
||||||
services: &Services,
|
|
||||||
) -> Result<(Vec<AuthFlow>, Box<RawValue>)> {
|
|
||||||
let mut params = HashMap::<String, serde_json::Value>::new();
|
|
||||||
|
|
||||||
let flows = if services.firstrun.is_first_run() {
|
|
||||||
// Registration token forced while in first-run mode
|
|
||||||
vec![AuthFlow::new(vec![AuthType::RegistrationToken])]
|
|
||||||
} else {
|
|
||||||
let mut flows = vec![];
|
|
||||||
|
|
||||||
if services
|
|
||||||
.registration_tokens
|
|
||||||
.iterate_tokens()
|
|
||||||
.next()
|
|
||||||
.await
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
// Trusted registration flow with a token is available
|
|
||||||
let mut token_flow = AuthFlow::new(vec![AuthType::RegistrationToken]);
|
|
||||||
|
|
||||||
if let Some(smtp) = &services.config.smtp
|
|
||||||
&& smtp.require_email_for_token_registration
|
|
||||||
{
|
|
||||||
// Email is required for token registrations
|
|
||||||
token_flow.stages.push(AuthType::EmailIdentity);
|
|
||||||
}
|
|
||||||
|
|
||||||
flows.push(token_flow);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut untrusted_flow = AuthFlow::default();
|
|
||||||
|
|
||||||
if services.config.recaptcha_private_site_key.is_some() {
|
|
||||||
if let Some(pubkey) = &services.config.recaptcha_site_key {
|
|
||||||
// ReCaptcha is configured for untrusted registrations
|
|
||||||
untrusted_flow.stages.push(AuthType::ReCaptcha);
|
|
||||||
|
|
||||||
params.insert(
|
|
||||||
AuthType::ReCaptcha.as_str().to_owned(),
|
|
||||||
serde_json::json!({
|
|
||||||
"public_key": pubkey,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(smtp) = &services.config.smtp
|
|
||||||
&& smtp.require_email_for_registration
|
|
||||||
{
|
|
||||||
// Email is required for untrusted registrations
|
|
||||||
untrusted_flow.stages.push(AuthType::EmailIdentity);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !untrusted_flow.stages.is_empty() {
|
|
||||||
flows.push(untrusted_flow);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Require all users to agree to the terms and conditions, if configured
|
|
||||||
let terms = &services.config.registration_terms;
|
|
||||||
if !terms.is_empty() {
|
|
||||||
let mut terms =
|
|
||||||
serde_json::to_value(terms.clone()).expect("failed to serialize terms");
|
|
||||||
|
|
||||||
// Insert a dummy `version` field
|
|
||||||
for (_, documents) in terms.as_object_mut().unwrap() {
|
|
||||||
let documents = documents.as_object_mut().unwrap();
|
|
||||||
|
|
||||||
documents.insert("version".to_owned(), "latest".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
params.insert(
|
|
||||||
AuthType::Terms.as_str().to_owned(),
|
|
||||||
serde_json::json!({
|
|
||||||
"policies": terms,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
for flow in &mut flows {
|
|
||||||
flow.stages.insert(0, AuthType::Terms);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if flows.is_empty() {
|
|
||||||
// No flows are configured. Bail out by default
|
|
||||||
// unless open registration was explicitly enabled.
|
|
||||||
if !services
|
|
||||||
.config
|
|
||||||
.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse
|
|
||||||
{
|
|
||||||
return Err!(Request(Forbidden(
|
|
||||||
"This server is not accepting registrations at this time."
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have open registration enabled (😧), provide a dummy flow
|
|
||||||
flows.push(AuthFlow::new(vec![AuthType::Dummy]));
|
|
||||||
}
|
|
||||||
|
|
||||||
flows
|
|
||||||
};
|
|
||||||
|
|
||||||
let params = serde_json::value::to_raw_value(¶ms).expect("params should be valid JSON");
|
|
||||||
|
|
||||||
Ok((flows, params))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn determine_registration_user_id(
|
|
||||||
services: &Services,
|
|
||||||
supplied_username: Option<String>,
|
|
||||||
is_guest: bool,
|
|
||||||
emergency_mode_enabled: bool,
|
|
||||||
) -> Result<OwnedUserId> {
|
|
||||||
if let Some(supplied_username) = supplied_username
|
|
||||||
&& !is_guest
|
|
||||||
{
|
|
||||||
// The user gets to pick their username. Do some validation to make sure it's
|
|
||||||
// acceptable.
|
|
||||||
|
|
||||||
// Don't allow registration with forbidden usernames.
|
|
||||||
if services
|
|
||||||
.globals
|
|
||||||
.forbidden_usernames()
|
|
||||||
.is_match(&supplied_username)
|
|
||||||
&& !emergency_mode_enabled
|
|
||||||
{
|
|
||||||
return Err!(Request(Forbidden("Username is forbidden")));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and validate the user ID
|
|
||||||
let user_id = match UserId::parse_with_server_name(
|
|
||||||
&supplied_username,
|
|
||||||
services.globals.server_name(),
|
|
||||||
) {
|
|
||||||
| Ok(user_id) => {
|
|
||||||
if let Err(e) = user_id.validate_strict() {
|
|
||||||
// Unless we are in emergency mode, we should follow synapse's behaviour on
|
|
||||||
// not allowing things like spaces and UTF-8 characters in usernames
|
|
||||||
if !emergency_mode_enabled {
|
|
||||||
return Err!(Request(InvalidUsername(debug_warn!(
|
|
||||||
"Username {supplied_username} contains disallowed characters or \
|
|
||||||
spaces: {e}"
|
|
||||||
))));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't allow registration with user IDs that aren't local
|
|
||||||
if !services.globals.user_is_local(&user_id) {
|
|
||||||
return Err!(Request(InvalidUsername(
|
|
||||||
"Username {supplied_username} is not local to this server"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
user_id
|
|
||||||
},
|
|
||||||
| Err(e) => {
|
|
||||||
return Err!(Request(InvalidUsername(debug_warn!(
|
|
||||||
"Username {supplied_username} is not valid: {e}"
|
|
||||||
))));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if services.users.exists(&user_id).await {
|
|
||||||
return Err!(Request(UserInUse("User ID is not available.")));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(user_id)
|
|
||||||
} else {
|
|
||||||
// The user is a guest or didn't specify a username. Generate a username for
|
|
||||||
// them.
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let user_id = UserId::parse_with_server_name(
|
|
||||||
utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(),
|
|
||||||
services.globals.server_name(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if !services.users.exists(&user_id).await {
|
|
||||||
break Ok(user_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # `POST /_matrix/client/v3/register/email/requestToken`
|
|
||||||
///
|
|
||||||
/// Requests a validation email for the purpose of registering a new account.
|
|
||||||
pub(crate) async fn request_registration_token_via_email_route(
|
|
||||||
State(services): State<crate::State>,
|
|
||||||
body: Ruma<request_registration_token_via_email::v3::Request>,
|
|
||||||
) -> Result<request_registration_token_via_email::v3::Response> {
|
|
||||||
let Ok(email) = Address::try_from(body.email.clone()) else {
|
|
||||||
return Err!(Request(InvalidParam("Invalid email address.")));
|
|
||||||
};
|
|
||||||
|
|
||||||
if services
|
|
||||||
.threepid
|
|
||||||
.get_localpart_for_email(&email)
|
|
||||||
.await
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
return Err!(Request(ThreepidInUse("This email address is already in use.")));
|
|
||||||
}
|
|
||||||
|
|
||||||
let session = services
|
|
||||||
.threepid
|
|
||||||
.send_validation_email(
|
|
||||||
Mailbox::new(None, email),
|
|
||||||
|verification_link| messages::NewAccount {
|
|
||||||
server_name: services.config.server_name.as_ref(),
|
|
||||||
verification_link,
|
|
||||||
},
|
|
||||||
&body.client_secret,
|
|
||||||
body.send_attempt.try_into().unwrap(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(request_registration_token_via_email::v3::Response::new(session))
|
|
||||||
}
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
use std::time::SystemTime;
|
|
||||||
|
|
||||||
use axum::extract::State;
|
|
||||||
use conduwuit::{Err, Result, err};
|
|
||||||
use lettre::{Address, message::Mailbox};
|
|
||||||
use ruma::{
|
|
||||||
MilliSecondsSinceUnixEpoch,
|
|
||||||
api::client::account::{
|
|
||||||
ThirdPartyIdRemovalStatus, add_3pid, delete_3pid, get_3pids,
|
|
||||||
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn,
|
|
||||||
},
|
|
||||||
thirdparty::{Medium, ThirdPartyIdentifierInit},
|
|
||||||
};
|
|
||||||
use service::{mailer::messages, uiaa::Identity};
|
|
||||||
|
|
||||||
use crate::Ruma;
|
|
||||||
|
|
||||||
/// # `GET _matrix/client/v3/account/3pid`
|
|
||||||
///
|
|
||||||
/// Get a list of third party identifiers associated with this account.
|
|
||||||
pub(crate) async fn third_party_route(
|
|
||||||
State(services): State<crate::State>,
|
|
||||||
body: Ruma<get_3pids::v3::Request>,
|
|
||||||
) -> Result<get_3pids::v3::Response> {
|
|
||||||
let sender_user = body.sender_user();
|
|
||||||
let mut threepids = vec![];
|
|
||||||
|
|
||||||
if let Some(email) = services
|
|
||||||
.threepid
|
|
||||||
.get_email_for_localpart(sender_user.localpart())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
threepids.push(
|
|
||||||
ThirdPartyIdentifierInit {
|
|
||||||
address: email.to_string(),
|
|
||||||
medium: Medium::Email,
|
|
||||||
// We don't currently track these, and they aren't used for much
|
|
||||||
validated_at: MilliSecondsSinceUnixEpoch::now(),
|
|
||||||
added_at: MilliSecondsSinceUnixEpoch::from_system_time(SystemTime::UNIX_EPOCH)
|
|
||||||
.unwrap(),
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(get_3pids::v3::Response::new(threepids))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # `POST /_matrix/client/v3/account/3pid/email/requestToken`
|
|
||||||
///
|
|
||||||
/// Requests a validation email for the purpose of changing an account's email.
|
|
||||||
pub(crate) async fn request_3pid_management_token_via_email_route(
|
|
||||||
State(services): State<crate::State>,
|
|
||||||
body: Ruma<request_3pid_management_token_via_email::v3::Request>,
|
|
||||||
) -> Result<request_3pid_management_token_via_email::v3::Response> {
|
|
||||||
let Ok(email) = Address::try_from(body.email.clone()) else {
|
|
||||||
return Err!(Request(InvalidParam("Invalid email address.")));
|
|
||||||
};
|
|
||||||
|
|
||||||
if services
|
|
||||||
.threepid
|
|
||||||
.get_localpart_for_email(&email)
|
|
||||||
.await
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
return Err!(Request(ThreepidInUse("This email address is already in use.")));
|
|
||||||
}
|
|
||||||
|
|
||||||
let session = services
|
|
||||||
.threepid
|
|
||||||
.send_validation_email(
|
|
||||||
Mailbox::new(None, email),
|
|
||||||
|verification_link| messages::ChangeEmail {
|
|
||||||
server_name: services.config.server_name.as_str(),
|
|
||||||
user_id: body.sender_user.as_deref(),
|
|
||||||
verification_link,
|
|
||||||
},
|
|
||||||
&body.client_secret,
|
|
||||||
body.send_attempt.try_into().unwrap(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(request_3pid_management_token_via_email::v3::Response::new(session))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # `POST /_matrix/client/v3/account/3pid/msisdn/requestToken`
|
|
||||||
///
|
|
||||||
/// "This API should be used to request validation tokens when adding an email
|
|
||||||
/// address to an account"
|
|
||||||
///
|
|
||||||
/// - 403 signals that The homeserver does not allow the third party identifier
|
|
||||||
/// as a contact option.
|
|
||||||
pub(crate) async fn request_3pid_management_token_via_msisdn_route(
|
|
||||||
_body: Ruma<request_3pid_management_token_via_msisdn::v3::Request>,
|
|
||||||
) -> Result<request_3pid_management_token_via_msisdn::v3::Response> {
|
|
||||||
Err!(Request(ThreepidMediumNotSupported(
|
|
||||||
"MSISDN third-party identifiers are not supported."
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # `POST /_matrix/client/v3/account/3pid/add`
|
|
||||||
pub(crate) async fn add_3pid_route(
|
|
||||||
State(services): State<crate::State>,
|
|
||||||
body: Ruma<add_3pid::v3::Request>,
|
|
||||||
) -> Result<add_3pid::v3::Response> {
|
|
||||||
let sender_user = body.sender_user();
|
|
||||||
|
|
||||||
// Require password auth to add an email
|
|
||||||
let _ = services
|
|
||||||
.uiaa
|
|
||||||
.authenticate_password(&body.auth, Some(Identity::from_user_id(sender_user)))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let email = services
|
|
||||||
.threepid
|
|
||||||
.consume_valid_session(&body.sid, &body.client_secret)
|
|
||||||
.await
|
|
||||||
.map_err(|message| err!(Request(ThreepidAuthFailed("{message}"))))?;
|
|
||||||
|
|
||||||
services
|
|
||||||
.threepid
|
|
||||||
.associate_localpart_email(sender_user.localpart(), &email)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(add_3pid::v3::Response::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # `POST /_matrix/client/v3/account/3pid/delete`
|
|
||||||
pub(crate) async fn delete_3pid_route(
|
|
||||||
State(services): State<crate::State>,
|
|
||||||
body: Ruma<delete_3pid::v3::Request>,
|
|
||||||
) -> Result<delete_3pid::v3::Response> {
|
|
||||||
let sender_user = body.sender_user();
|
|
||||||
|
|
||||||
if body.medium != Medium::Email {
|
|
||||||
return Ok(delete_3pid::v3::Response {
|
|
||||||
id_server_unbind_result: ThirdPartyIdRemovalStatus::NoSupport,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if services
|
|
||||||
.threepid
|
|
||||||
.disassociate_localpart_email(sender_user.localpart())
|
|
||||||
.await
|
|
||||||
.is_none()
|
|
||||||
{
|
|
||||||
return Err!(Request(ThreepidNotFound("Your account has no associated email.")));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(delete_3pid::v3::Response {
|
|
||||||
id_server_unbind_result: ThirdPartyIdRemovalStatus::Success,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -30,10 +30,8 @@ pub(crate) async fn get_capabilities_route(
|
|||||||
default: services.server.config.default_room_version.clone(),
|
default: services.server.config.default_room_version.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only allow 3pid changes if SMTP is configured
|
// we do not implement 3PID stuff
|
||||||
capabilities.thirdparty_id_changes = ThirdPartyIdChangesCapability {
|
capabilities.thirdparty_id_changes = ThirdPartyIdChangesCapability { enabled: false };
|
||||||
enabled: services.mailer.mailer().is_some(),
|
|
||||||
};
|
|
||||||
|
|
||||||
capabilities.get_login_token = GetLoginTokenCapability {
|
capabilities.get_login_token = GetLoginTokenCapability {
|
||||||
enabled: services.server.config.login_via_existing_session,
|
enabled: services.server.config.login_via_existing_session,
|
||||||
@@ -53,7 +51,7 @@ pub(crate) async fn get_capabilities_route(
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
// Advertise suspension API
|
// Advertise suspension API
|
||||||
capabilities.set("uk.timedout.msc4323", json!({"suspend": true, "lock": false}))?;
|
capabilities.set("uk.timedout.msc4323", json!({"suspend":true, "lock": false}))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(get_capabilities::v3::Response { capabilities })
|
Ok(get_capabilities::v3::Response { capabilities })
|
||||||
|
|||||||
+78
-16
@@ -1,15 +1,17 @@
|
|||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum_client_ip::InsecureClientIp;
|
use axum_client_ip::InsecureClientIp;
|
||||||
use conduwuit::{Err, Result, debug, err, utils};
|
use conduwuit::{Err, Error, Result, debug, err, utils};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
MilliSecondsSinceUnixEpoch, OwnedDeviceId,
|
MilliSecondsSinceUnixEpoch, OwnedDeviceId,
|
||||||
api::client::device::{
|
api::client::{
|
||||||
self, delete_device, delete_devices, get_device, get_devices, update_device,
|
device::{self, delete_device, delete_devices, get_device, get_devices, update_device},
|
||||||
|
error::ErrorKind,
|
||||||
|
uiaa::{AuthFlow, AuthType, UiaaInfo},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use service::uiaa::Identity;
|
|
||||||
|
|
||||||
|
use super::SESSION_ID_LENGTH;
|
||||||
use crate::{Ruma, client::DEVICE_ID_LENGTH};
|
use crate::{Ruma, client::DEVICE_ID_LENGTH};
|
||||||
|
|
||||||
/// # `GET /_matrix/client/r0/devices`
|
/// # `GET /_matrix/client/r0/devices`
|
||||||
@@ -121,7 +123,7 @@ pub(crate) async fn delete_device_route(
|
|||||||
State(services): State<crate::State>,
|
State(services): State<crate::State>,
|
||||||
body: Ruma<delete_device::v3::Request>,
|
body: Ruma<delete_device::v3::Request>,
|
||||||
) -> Result<delete_device::v3::Response> {
|
) -> Result<delete_device::v3::Response> {
|
||||||
let sender_user = body.sender_user();
|
let (sender_user, sender_device) = body.sender();
|
||||||
let appservice = body.appservice_info.as_ref();
|
let appservice = body.appservice_info.as_ref();
|
||||||
|
|
||||||
if appservice.is_some_and(|appservice| appservice.registration.device_management) {
|
if appservice.is_some_and(|appservice| appservice.registration.device_management) {
|
||||||
@@ -137,11 +139,41 @@ pub(crate) async fn delete_device_route(
|
|||||||
return Ok(delete_device::v3::Response {});
|
return Ok(delete_device::v3::Response {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prompt the user to confirm with their password using UIAA
|
// UIAA
|
||||||
let _ = services
|
let mut uiaainfo = UiaaInfo {
|
||||||
.uiaa
|
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
||||||
.authenticate_password(&body.auth, Some(Identity::from_user_id(sender_user)))
|
completed: Vec::new(),
|
||||||
.await?;
|
params: Box::default(),
|
||||||
|
session: None,
|
||||||
|
auth_error: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
match &body.auth {
|
||||||
|
| Some(auth) => {
|
||||||
|
let (worked, uiaainfo) = services
|
||||||
|
.uiaa
|
||||||
|
.try_auth(sender_user, sender_device, auth, &uiaainfo)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !worked {
|
||||||
|
return Err!(Uiaa(uiaainfo));
|
||||||
|
}
|
||||||
|
// Success!
|
||||||
|
},
|
||||||
|
| _ => match body.json_body {
|
||||||
|
| Some(ref json) => {
|
||||||
|
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||||
|
services
|
||||||
|
.uiaa
|
||||||
|
.create(sender_user, sender_device, &uiaainfo, json);
|
||||||
|
|
||||||
|
return Err!(Uiaa(uiaainfo));
|
||||||
|
},
|
||||||
|
| _ => {
|
||||||
|
return Err!(Request(NotJson("Not json.")));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
services
|
services
|
||||||
.users
|
.users
|
||||||
@@ -168,7 +200,7 @@ pub(crate) async fn delete_devices_route(
|
|||||||
State(services): State<crate::State>,
|
State(services): State<crate::State>,
|
||||||
body: Ruma<delete_devices::v3::Request>,
|
body: Ruma<delete_devices::v3::Request>,
|
||||||
) -> Result<delete_devices::v3::Response> {
|
) -> Result<delete_devices::v3::Response> {
|
||||||
let sender_user = body.sender_user();
|
let (sender_user, sender_device) = body.sender();
|
||||||
let appservice = body.appservice_info.as_ref();
|
let appservice = body.appservice_info.as_ref();
|
||||||
|
|
||||||
if appservice.is_some_and(|appservice| appservice.registration.device_management) {
|
if appservice.is_some_and(|appservice| appservice.registration.device_management) {
|
||||||
@@ -183,11 +215,41 @@ pub(crate) async fn delete_devices_route(
|
|||||||
return Ok(delete_devices::v3::Response {});
|
return Ok(delete_devices::v3::Response {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prompt the user to confirm with their password using UIAA
|
// UIAA
|
||||||
let _ = services
|
let mut uiaainfo = UiaaInfo {
|
||||||
.uiaa
|
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
||||||
.authenticate_password(&body.auth, Some(Identity::from_user_id(sender_user)))
|
completed: Vec::new(),
|
||||||
.await?;
|
params: Box::default(),
|
||||||
|
session: None,
|
||||||
|
auth_error: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
match &body.auth {
|
||||||
|
| Some(auth) => {
|
||||||
|
let (worked, uiaainfo) = services
|
||||||
|
.uiaa
|
||||||
|
.try_auth(sender_user, sender_device, auth, &uiaainfo)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !worked {
|
||||||
|
return Err(Error::Uiaa(uiaainfo));
|
||||||
|
}
|
||||||
|
// Success!
|
||||||
|
},
|
||||||
|
| _ => match body.json_body {
|
||||||
|
| Some(ref json) => {
|
||||||
|
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||||
|
services
|
||||||
|
.uiaa
|
||||||
|
.create(sender_user, sender_device, &uiaainfo, json);
|
||||||
|
|
||||||
|
return Err(Error::Uiaa(uiaainfo));
|
||||||
|
},
|
||||||
|
| _ => {
|
||||||
|
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
for device_id in &body.devices {
|
for device_id in &body.devices {
|
||||||
services.users.remove_device(sender_user, device_id).await;
|
services.users.remove_device(sender_user, device_id).await;
|
||||||
|
|||||||
+39
-6
@@ -7,6 +7,7 @@ use axum::extract::State;
|
|||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Error, Result, debug, debug_warn, err,
|
Err, Error, Result, debug, debug_warn, err,
|
||||||
result::NotFound,
|
result::NotFound,
|
||||||
|
utils,
|
||||||
utils::{IterStream, stream::WidebandExt},
|
utils::{IterStream, stream::WidebandExt},
|
||||||
};
|
};
|
||||||
use conduwuit_service::{Services, users::parse_master_key};
|
use conduwuit_service::{Services, users::parse_master_key};
|
||||||
@@ -21,6 +22,7 @@ use ruma::{
|
|||||||
upload_signatures::{self},
|
upload_signatures::{self},
|
||||||
upload_signing_keys,
|
upload_signing_keys,
|
||||||
},
|
},
|
||||||
|
uiaa::{AuthFlow, AuthType, UiaaInfo},
|
||||||
},
|
},
|
||||||
federation,
|
federation,
|
||||||
},
|
},
|
||||||
@@ -28,8 +30,8 @@ use ruma::{
|
|||||||
serde::Raw,
|
serde::Raw,
|
||||||
};
|
};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use service::uiaa::Identity;
|
|
||||||
|
|
||||||
|
use super::SESSION_ID_LENGTH;
|
||||||
use crate::Ruma;
|
use crate::Ruma;
|
||||||
|
|
||||||
/// # `POST /_matrix/client/r0/keys/upload`
|
/// # `POST /_matrix/client/r0/keys/upload`
|
||||||
@@ -172,7 +174,16 @@ pub(crate) async fn upload_signing_keys_route(
|
|||||||
State(services): State<crate::State>,
|
State(services): State<crate::State>,
|
||||||
body: Ruma<upload_signing_keys::v3::Request>,
|
body: Ruma<upload_signing_keys::v3::Request>,
|
||||||
) -> Result<upload_signing_keys::v3::Response> {
|
) -> Result<upload_signing_keys::v3::Response> {
|
||||||
let sender_user = body.sender_user();
|
let (sender_user, sender_device) = body.sender();
|
||||||
|
|
||||||
|
// UIAA
|
||||||
|
let mut uiaainfo = UiaaInfo {
|
||||||
|
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
||||||
|
completed: Vec::new(),
|
||||||
|
params: Box::default(),
|
||||||
|
session: None,
|
||||||
|
auth_error: None,
|
||||||
|
};
|
||||||
|
|
||||||
match check_for_new_keys(
|
match check_for_new_keys(
|
||||||
services,
|
services,
|
||||||
@@ -196,10 +207,32 @@ pub(crate) async fn upload_signing_keys_route(
|
|||||||
// Some of the keys weren't found, so we let them upload
|
// Some of the keys weren't found, so we let them upload
|
||||||
},
|
},
|
||||||
| _ => {
|
| _ => {
|
||||||
let _ = services
|
match &body.auth {
|
||||||
.uiaa
|
| Some(auth) => {
|
||||||
.authenticate_password(&body.auth, Some(Identity::from_user_id(sender_user)))
|
let (worked, uiaainfo) = services
|
||||||
.await?;
|
.uiaa
|
||||||
|
.try_auth(sender_user, sender_device, auth, &uiaainfo)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !worked {
|
||||||
|
return Err(Error::Uiaa(uiaainfo));
|
||||||
|
}
|
||||||
|
// Success!
|
||||||
|
},
|
||||||
|
| _ => match body.json_body.as_ref() {
|
||||||
|
| Some(json) => {
|
||||||
|
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||||
|
services
|
||||||
|
.uiaa
|
||||||
|
.create(sender_user, sender_device, &uiaainfo, json);
|
||||||
|
|
||||||
|
return Err(Error::Uiaa(uiaainfo));
|
||||||
|
},
|
||||||
|
| _ => {
|
||||||
|
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -584,7 +584,7 @@ async fn join_room_by_id_helper_remote(
|
|||||||
return state;
|
return state;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if !pdu_fits(&mut value.clone()) {
|
if !pdu_fits(&value) {
|
||||||
warn!(
|
warn!(
|
||||||
"dropping incoming PDU {event_id} in room {room_id} from room join because \
|
"dropping incoming PDU {event_id} in room {room_id} from room join because \
|
||||||
it exceeds 65535 bytes or is otherwise too large."
|
it exceeds 65535 bytes or is otherwise too large."
|
||||||
@@ -785,15 +785,6 @@ async fn join_room_by_id_helper_local(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if servers.is_empty() || servers.len() == 1 && services.globals.server_is_ours(&servers[0]) {
|
if servers.is_empty() || servers.len() == 1 && services.globals.server_is_ours(&servers[0]) {
|
||||||
if !services.rooms.metadata.exists(room_id).await {
|
|
||||||
return Err!(Request(
|
|
||||||
Unknown(
|
|
||||||
"Room was not found locally and no servers were found to help us discover it"
|
|
||||||
),
|
|
||||||
NOT_FOUND
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Err(error);
|
return Err(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -409,11 +409,6 @@ async fn knock_room_helper_local(
|
|||||||
room_id,
|
room_id,
|
||||||
&knock_event_stub,
|
&knock_event_stub,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
knock_event_stub.insert(
|
|
||||||
"origin".to_owned(),
|
|
||||||
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
|
|
||||||
);
|
|
||||||
knock_event_stub.insert(
|
knock_event_stub.insert(
|
||||||
"origin_server_ts".to_owned(),
|
"origin_server_ts".to_owned(),
|
||||||
CanonicalJsonValue::Integer(
|
CanonicalJsonValue::Integer(
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use conduwuit::{
|
|||||||
};
|
};
|
||||||
use futures::{FutureExt, StreamExt, pin_mut};
|
use futures::{FutureExt, StreamExt, pin_mut};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
CanonicalJsonObject, CanonicalJsonValue, OwnedServerName, RoomId, RoomVersionId, UserId,
|
CanonicalJsonObject, CanonicalJsonValue, OwnedServerName, RoomId, UserId,
|
||||||
api::{
|
api::{
|
||||||
client::membership::leave_room,
|
client::membership::leave_room,
|
||||||
federation::{self},
|
federation::{self},
|
||||||
@@ -337,6 +337,7 @@ pub async fn remote_leave_room<S: ::std::hash::BuildHasher>(
|
|||||||
"Invalid make_leave event json received from {remote_server} for {room_id}: {e:?}"
|
"Invalid make_leave event json received from {remote_server} for {room_id}: {e:?}"
|
||||||
)))
|
)))
|
||||||
})?;
|
})?;
|
||||||
|
leave_event_stub.remove("event_id");
|
||||||
|
|
||||||
validate_remote_member_event_stub(
|
validate_remote_member_event_stub(
|
||||||
&MembershipState::Leave,
|
&MembershipState::Leave,
|
||||||
@@ -345,11 +346,6 @@ pub async fn remote_leave_room<S: ::std::hash::BuildHasher>(
|
|||||||
&leave_event_stub,
|
&leave_event_stub,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// TODO: Is origin needed?
|
|
||||||
leave_event_stub.insert(
|
|
||||||
"origin".to_owned(),
|
|
||||||
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
|
|
||||||
);
|
|
||||||
leave_event_stub.insert(
|
leave_event_stub.insert(
|
||||||
"origin_server_ts".to_owned(),
|
"origin_server_ts".to_owned(),
|
||||||
CanonicalJsonValue::Integer(
|
CanonicalJsonValue::Integer(
|
||||||
@@ -365,14 +361,6 @@ pub async fn remote_leave_room<S: ::std::hash::BuildHasher>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// room v3 and above removed the "event_id" field from remote PDU format
|
|
||||||
match room_version_id {
|
|
||||||
| RoomVersionId::V1 | RoomVersionId::V2 => {},
|
|
||||||
| _ => {
|
|
||||||
leave_event_stub.remove("event_id");
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// In order to create a compatible ref hash (EventID) the `hashes` field needs
|
// In order to create a compatible ref hash (EventID) the `hashes` field needs
|
||||||
// to be present
|
// to be present
|
||||||
services
|
services
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ pub(super) use openid::*;
|
|||||||
pub(super) use presence::*;
|
pub(super) use presence::*;
|
||||||
pub(super) use profile::*;
|
pub(super) use profile::*;
|
||||||
pub use profile::{update_all_rooms, update_avatar_url, update_displayname};
|
pub use profile::{update_all_rooms, update_avatar_url, update_displayname};
|
||||||
pub use push::recreate_push_rules_and_return;
|
|
||||||
pub(super) use push::*;
|
pub(super) use push::*;
|
||||||
pub(super) use read_marker::*;
|
pub(super) use read_marker::*;
|
||||||
pub(super) use redact::*;
|
pub(super) use redact::*;
|
||||||
@@ -93,3 +92,6 @@ const DEVICE_ID_LENGTH: usize = 10;
|
|||||||
|
|
||||||
/// generated user access token length
|
/// generated user access token length
|
||||||
const TOKEN_LENGTH: usize = 32;
|
const TOKEN_LENGTH: usize = 32;
|
||||||
|
|
||||||
|
/// generated user session ID length
|
||||||
|
const SESSION_ID_LENGTH: usize = service::uiaa::SESSION_ID_LENGTH;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use conduwuit::{
|
|||||||
};
|
};
|
||||||
use conduwuit_service::Services;
|
use conduwuit_service::Services;
|
||||||
use futures::{
|
use futures::{
|
||||||
FutureExt, StreamExt, TryStreamExt,
|
StreamExt, TryStreamExt,
|
||||||
future::{join, join3, join4},
|
future::{join, join3, join4},
|
||||||
};
|
};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
@@ -51,7 +51,6 @@ pub(crate) async fn set_displayname_route(
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
update_displayname(&services, &body.user_id, body.displayname.clone(), &all_joined_rooms)
|
update_displayname(&services, &body.user_id, body.displayname.clone(), &all_joined_rooms)
|
||||||
.boxed()
|
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if services.config.allow_local_presence {
|
if services.config.allow_local_presence {
|
||||||
@@ -150,7 +149,6 @@ pub(crate) async fn set_avatar_url_route(
|
|||||||
body.blurhash.clone(),
|
body.blurhash.clone(),
|
||||||
&all_joined_rooms,
|
&all_joined_rooms,
|
||||||
)
|
)
|
||||||
.boxed()
|
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if services.config.allow_local_presence {
|
if services.config.allow_local_presence {
|
||||||
@@ -346,9 +344,7 @@ pub async fn update_displayname(
|
|||||||
.collect()
|
.collect()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
update_all_rooms(services, all_joined_rooms, user_id)
|
update_all_rooms(services, all_joined_rooms, user_id).await;
|
||||||
.boxed()
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_avatar_url(
|
pub async fn update_avatar_url(
|
||||||
@@ -398,9 +394,7 @@ pub async fn update_avatar_url(
|
|||||||
.collect()
|
.collect()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
update_all_rooms(services, all_joined_rooms, user_id)
|
update_all_rooms(services, all_joined_rooms, user_id).await;
|
||||||
.boxed()
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_all_rooms(
|
pub async fn update_all_rooms(
|
||||||
|
|||||||
@@ -489,7 +489,7 @@ pub(crate) async fn set_pushers_route(
|
|||||||
|
|
||||||
/// user somehow has bad push rules, these must always exist per spec.
|
/// user somehow has bad push rules, these must always exist per spec.
|
||||||
/// so recreate it and return server default silently
|
/// so recreate it and return server default silently
|
||||||
pub async fn recreate_push_rules_and_return(
|
async fn recreate_push_rules_and_return(
|
||||||
services: &Services,
|
services: &Services,
|
||||||
sender_user: &ruma::UserId,
|
sender_user: &ruma::UserId,
|
||||||
) -> Result<get_pushrules_all::v3::Response> {
|
) -> Result<get_pushrules_all::v3::Response> {
|
||||||
|
|||||||
@@ -137,7 +137,6 @@ pub(crate) async fn upgrade_room_route(
|
|||||||
Some(&body.room_id),
|
Some(&body.room_id),
|
||||||
&state_lock,
|
&state_lock,
|
||||||
)
|
)
|
||||||
.boxed()
|
|
||||||
.await?;
|
.await?;
|
||||||
// Change lock to replacement room
|
// Change lock to replacement room
|
||||||
drop(state_lock);
|
drop(state_lock);
|
||||||
|
|||||||
+62
-69
@@ -4,13 +4,12 @@ use axum::extract::State;
|
|||||||
use axum_client_ip::InsecureClientIp;
|
use axum_client_ip::InsecureClientIp;
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Error, Result, debug, err, info,
|
Err, Error, Result, debug, err, info,
|
||||||
utils::{self, ReadyExt, hash, stream::BroadbandExt},
|
utils::{self, ReadyExt, hash},
|
||||||
warn,
|
warn,
|
||||||
};
|
};
|
||||||
use conduwuit_core::{debug_error, debug_warn};
|
use conduwuit_core::{debug_error, debug_warn};
|
||||||
use conduwuit_service::Services;
|
use conduwuit_service::{Services, uiaa::SESSION_ID_LENGTH};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use lettre::Address;
|
|
||||||
use ruma::{
|
use ruma::{
|
||||||
OwnedUserId, UserId,
|
OwnedUserId, UserId,
|
||||||
api::client::{
|
api::client::{
|
||||||
@@ -27,10 +26,9 @@ use ruma::{
|
|||||||
},
|
},
|
||||||
logout, logout_all,
|
logout, logout_all,
|
||||||
},
|
},
|
||||||
uiaa::UserIdentifier,
|
uiaa,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use service::uiaa::Identity;
|
|
||||||
|
|
||||||
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
|
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
|
||||||
use crate::Ruma;
|
use crate::Ruma;
|
||||||
@@ -82,7 +80,7 @@ pub(crate) async fn password_login(
|
|||||||
.password_hash(lowercased_user_id)
|
.password_hash(lowercased_user_id)
|
||||||
.await
|
.await
|
||||||
.map(|hash| (hash, lowercased_user_id))
|
.map(|hash| (hash, lowercased_user_id))
|
||||||
.map_err(|_| err!(Request(Forbidden("Invalid identifier or password."))))?,
|
.map_err(|_| err!(Request(Forbidden("Wrong username or password."))))?,
|
||||||
};
|
};
|
||||||
|
|
||||||
if hash.is_empty() {
|
if hash.is_empty() {
|
||||||
@@ -91,7 +89,7 @@ pub(crate) async fn password_login(
|
|||||||
|
|
||||||
hash::verify_password(password, &hash)
|
hash::verify_password(password, &hash)
|
||||||
.inspect_err(|e| debug_error!("{e}"))
|
.inspect_err(|e| debug_error!("{e}"))
|
||||||
.map_err(|_| err!(Request(Forbidden("Invalid identifier or password."))))?;
|
.map_err(|_| err!(Request(Forbidden("Wrong username or password."))))?;
|
||||||
|
|
||||||
Ok(user_id.to_owned())
|
Ok(user_id.to_owned())
|
||||||
}
|
}
|
||||||
@@ -163,38 +161,28 @@ pub(super) async fn ldap_login(
|
|||||||
|
|
||||||
pub(crate) async fn handle_login(
|
pub(crate) async fn handle_login(
|
||||||
services: &Services,
|
services: &Services,
|
||||||
identifier: Option<&UserIdentifier>,
|
body: &Ruma<login::v3::Request>,
|
||||||
|
identifier: Option<&uiaa::UserIdentifier>,
|
||||||
password: &str,
|
password: &str,
|
||||||
user: Option<&String>,
|
user: Option<&String>,
|
||||||
) -> Result<OwnedUserId> {
|
) -> Result<OwnedUserId> {
|
||||||
debug!("Got password login type");
|
debug!("Got password login type");
|
||||||
let user_id_or_localpart = match (identifier, user) {
|
|
||||||
| (Some(UserIdentifier::UserIdOrLocalpart(localpart)), _) => localpart,
|
|
||||||
| (Some(UserIdentifier::Email { address }), _) => {
|
|
||||||
let email = Address::try_from(address.to_owned())
|
|
||||||
.map_err(|_| err!(Request(InvalidParam("Email is malformed"))))?;
|
|
||||||
|
|
||||||
&services
|
|
||||||
.threepid
|
|
||||||
.get_localpart_for_email(&email)
|
|
||||||
.await
|
|
||||||
.ok_or_else(|| err!(Request(Forbidden("Invalid identifier or password"))))?
|
|
||||||
},
|
|
||||||
| (None, Some(user)) => user,
|
|
||||||
| _ => {
|
|
||||||
return Err!(Request(InvalidParam("Identifier type not recognized")));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let user_id =
|
let user_id =
|
||||||
UserId::parse_with_server_name(user_id_or_localpart, &services.config.server_name)
|
if let Some(uiaa::UserIdentifier::UserIdOrLocalpart(user_id)) = identifier {
|
||||||
.map_err(|_| err!(Request(InvalidUsername("User ID is malformed"))))?;
|
UserId::parse_with_server_name(user_id, &services.config.server_name)
|
||||||
|
} else if let Some(user) = user {
|
||||||
|
UserId::parse_with_server_name(user, &services.config.server_name)
|
||||||
|
} else {
|
||||||
|
return Err!(Request(Unknown(
|
||||||
|
debug_warn!(?body.login_info, "Valid identifier or username was not provided (invalid or unsupported login type?)")
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
.map_err(|e| err!(Request(InvalidUsername(warn!("Username is invalid: {e}")))))?;
|
||||||
|
|
||||||
let lowercased_user_id = UserId::parse_with_server_name(
|
let lowercased_user_id = UserId::parse_with_server_name(
|
||||||
user_id.localpart().to_lowercase(),
|
user_id.localpart().to_lowercase(),
|
||||||
&services.config.server_name,
|
&services.config.server_name,
|
||||||
)
|
)?;
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if !services.globals.user_is_local(&user_id)
|
if !services.globals.user_is_local(&user_id)
|
||||||
|| !services.globals.user_is_local(&lowercased_user_id)
|
|| !services.globals.user_is_local(&lowercased_user_id)
|
||||||
@@ -256,7 +244,7 @@ pub(crate) async fn login_route(
|
|||||||
password,
|
password,
|
||||||
user,
|
user,
|
||||||
..
|
..
|
||||||
}) => handle_login(&services, identifier.as_ref(), password, user.as_ref()).await?,
|
}) => handle_login(&services, &body, identifier.as_ref(), password, user.as_ref()).await?,
|
||||||
| login::v3::LoginInfo::Token(login::v3::Token { token }) => {
|
| login::v3::LoginInfo::Token(login::v3::Token { token }) => {
|
||||||
debug!("Got token login type");
|
debug!("Got token login type");
|
||||||
if !services.server.config.login_via_existing_session {
|
if !services.server.config.login_via_existing_session {
|
||||||
@@ -276,7 +264,7 @@ pub(crate) async fn login_route(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let user_id =
|
let user_id =
|
||||||
if let Some(UserIdentifier::UserIdOrLocalpart(user_id)) = identifier {
|
if let Some(uiaa::UserIdentifier::UserIdOrLocalpart(user_id)) = identifier {
|
||||||
UserId::parse_with_server_name(user_id, &services.config.server_name)
|
UserId::parse_with_server_name(user_id, &services.config.server_name)
|
||||||
} else if let Some(user) = user {
|
} else if let Some(user) = user {
|
||||||
UserId::parse_with_server_name(user, &services.config.server_name)
|
UserId::parse_with_server_name(user, &services.config.server_name)
|
||||||
@@ -285,7 +273,7 @@ pub(crate) async fn login_route(
|
|||||||
debug_warn!(?body.login_info, "Valid identifier or username was not provided (invalid or unsupported login type?)")
|
debug_warn!(?body.login_info, "Valid identifier or username was not provided (invalid or unsupported login type?)")
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
.map_err(|_| err!(Request(InvalidUsername(warn!("User ID is malformed")))))?;
|
.map_err(|e| err!(Request(InvalidUsername(warn!("Username is invalid: {e}")))))?;
|
||||||
|
|
||||||
if !services.globals.user_is_local(&user_id) {
|
if !services.globals.user_is_local(&user_id) {
|
||||||
return Err!(Request(Unknown("User ID does not belong to this homeserver")));
|
return Err!(Request(Unknown("User ID does not belong to this homeserver")));
|
||||||
@@ -382,13 +370,45 @@ pub(crate) async fn login_token_route(
|
|||||||
return Err!(Request(Forbidden("Login via an existing session is not enabled")));
|
return Err!(Request(Forbidden("Login via an existing session is not enabled")));
|
||||||
}
|
}
|
||||||
|
|
||||||
let sender_user = body.sender_user();
|
// This route SHOULD have UIA
|
||||||
|
// TODO: How do we make only UIA sessions that have not been used before valid?
|
||||||
|
let (sender_user, sender_device) = body.sender();
|
||||||
|
|
||||||
// Prompt the user to confirm with their password using UIAA
|
let mut uiaainfo = uiaa::UiaaInfo {
|
||||||
let _ = services
|
flows: vec![uiaa::AuthFlow { stages: vec![uiaa::AuthType::Password] }],
|
||||||
.uiaa
|
completed: Vec::new(),
|
||||||
.authenticate_password(&body.auth, Some(Identity::from_user_id(sender_user)))
|
params: Box::default(),
|
||||||
.await?;
|
session: None,
|
||||||
|
auth_error: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
match &body.auth {
|
||||||
|
| Some(auth) => {
|
||||||
|
let (worked, uiaainfo) = services
|
||||||
|
.uiaa
|
||||||
|
.try_auth(sender_user, sender_device, auth, &uiaainfo)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !worked {
|
||||||
|
return Err(Error::Uiaa(uiaainfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success!
|
||||||
|
},
|
||||||
|
| _ => match body.json_body.as_ref() {
|
||||||
|
| Some(json) => {
|
||||||
|
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||||
|
services
|
||||||
|
.uiaa
|
||||||
|
.create(sender_user, sender_device, &uiaainfo, json);
|
||||||
|
|
||||||
|
return Err(Error::Uiaa(uiaainfo));
|
||||||
|
},
|
||||||
|
| _ => {
|
||||||
|
return Err!(Request(NotJson("No JSON body was sent when required.")));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
let login_token = utils::random_string(TOKEN_LENGTH);
|
let login_token = utils::random_string(TOKEN_LENGTH);
|
||||||
let expires_in = services.users.create_login_token(sender_user, &login_token);
|
let expires_in = services.users.create_login_token(sender_user, &login_token);
|
||||||
@@ -414,28 +434,9 @@ pub(crate) async fn logout_route(
|
|||||||
InsecureClientIp(client): InsecureClientIp,
|
InsecureClientIp(client): InsecureClientIp,
|
||||||
body: Ruma<logout::v3::Request>,
|
body: Ruma<logout::v3::Request>,
|
||||||
) -> Result<logout::v3::Response> {
|
) -> Result<logout::v3::Response> {
|
||||||
let (sender_user, sender_device) = body.sender();
|
|
||||||
services
|
services
|
||||||
.users
|
.users
|
||||||
.remove_device(sender_user, sender_device)
|
.remove_device(body.sender_user(), body.sender_device())
|
||||||
.await;
|
|
||||||
services
|
|
||||||
.pusher
|
|
||||||
.get_pushkeys(sender_user)
|
|
||||||
.map(ToOwned::to_owned)
|
|
||||||
.broad_filter_map(async |pushkey| {
|
|
||||||
services
|
|
||||||
.pusher
|
|
||||||
.get_pusher_device(&pushkey)
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|pusher_device| pusher_device == sender_device)
|
|
||||||
.then_some(pushkey)
|
|
||||||
})
|
|
||||||
.for_each(async |pushkey| {
|
|
||||||
services.pusher.delete_pusher(sender_user, &pushkey).await;
|
|
||||||
})
|
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
Ok(logout::v3::Response::new())
|
Ok(logout::v3::Response::new())
|
||||||
@@ -460,18 +461,10 @@ pub(crate) async fn logout_all_route(
|
|||||||
InsecureClientIp(client): InsecureClientIp,
|
InsecureClientIp(client): InsecureClientIp,
|
||||||
body: Ruma<logout_all::v3::Request>,
|
body: Ruma<logout_all::v3::Request>,
|
||||||
) -> Result<logout_all::v3::Response> {
|
) -> Result<logout_all::v3::Response> {
|
||||||
let sender_user = body.sender_user();
|
|
||||||
services
|
services
|
||||||
.users
|
.users
|
||||||
.all_device_ids(sender_user)
|
.all_device_ids(body.sender_user())
|
||||||
.for_each(|device_id| services.users.remove_device(sender_user, device_id))
|
.for_each(|device_id| services.users.remove_device(body.sender_user(), device_id))
|
||||||
.await;
|
|
||||||
services
|
|
||||||
.pusher
|
|
||||||
.get_pushkeys(sender_user)
|
|
||||||
.for_each(async |pushkey| {
|
|
||||||
services.pusher.delete_pusher(sender_user, pushkey).await;
|
|
||||||
})
|
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
Ok(logout_all::v3::Response::new())
|
Ok(logout_all::v3::Response::new())
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ pub(crate) async fn send_state_event_for_key_route(
|
|||||||
None
|
None
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.boxed()
|
|
||||||
.await?,
|
.await?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::collections::BTreeMap;
|
|||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum_client_ip::InsecureClientIp;
|
use axum_client_ip::InsecureClientIp;
|
||||||
use conduwuit::{Err, Result};
|
use conduwuit::{Err, Result};
|
||||||
use futures::{FutureExt, StreamExt};
|
use futures::StreamExt;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
OwnedRoomId,
|
OwnedRoomId,
|
||||||
api::{
|
api::{
|
||||||
@@ -112,7 +112,6 @@ pub(crate) async fn set_profile_key_route(
|
|||||||
Some(display_name.to_owned()),
|
Some(display_name.to_owned()),
|
||||||
&all_joined_rooms,
|
&all_joined_rooms,
|
||||||
)
|
)
|
||||||
.boxed()
|
|
||||||
.await;
|
.await;
|
||||||
} else if body.key_name == "avatar_url" {
|
} else if body.key_name == "avatar_url" {
|
||||||
let Some(avatar_url) = profile_key_value.as_str() else {
|
let Some(avatar_url) = profile_key_value.as_str() else {
|
||||||
@@ -128,9 +127,7 @@ pub(crate) async fn set_profile_key_route(
|
|||||||
.collect()
|
.collect()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
update_avatar_url(&services, &body.user_id, Some(mxc), None, &all_joined_rooms)
|
update_avatar_url(&services, &body.user_id, Some(mxc), None, &all_joined_rooms).await;
|
||||||
.boxed()
|
|
||||||
.await;
|
|
||||||
} else {
|
} else {
|
||||||
services.users.set_profile_key(
|
services.users.set_profile_key(
|
||||||
&body.user_id,
|
&body.user_id,
|
||||||
@@ -181,9 +178,7 @@ pub(crate) async fn delete_profile_key_route(
|
|||||||
.collect()
|
.collect()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
update_displayname(&services, &body.user_id, None, &all_joined_rooms)
|
update_displayname(&services, &body.user_id, None, &all_joined_rooms).await;
|
||||||
.boxed()
|
|
||||||
.await;
|
|
||||||
} else if body.key_name == "avatar_url" {
|
} else if body.key_name == "avatar_url" {
|
||||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||||
.rooms
|
.rooms
|
||||||
@@ -193,9 +188,7 @@ pub(crate) async fn delete_profile_key_route(
|
|||||||
.collect()
|
.collect()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
update_avatar_url(&services, &body.user_id, None, None, &all_joined_rooms)
|
update_avatar_url(&services, &body.user_id, None, None, &all_joined_rooms).await;
|
||||||
.boxed()
|
|
||||||
.await;
|
|
||||||
} else {
|
} else {
|
||||||
services
|
services
|
||||||
.users
|
.users
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use axum::{Json, extract::State, response::IntoResponse};
|
|||||||
use conduwuit::{Error, Result};
|
use conduwuit::{Error, Result};
|
||||||
use ruma::api::client::{
|
use ruma::api::client::{
|
||||||
discovery::{
|
discovery::{
|
||||||
discover_homeserver::{self, HomeserverInfo},
|
discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo},
|
||||||
discover_support::{self, Contact},
|
discover_support::{self, Contact},
|
||||||
},
|
},
|
||||||
error::ErrorKind,
|
error::ErrorKind,
|
||||||
@@ -23,9 +23,9 @@ pub(crate) async fn well_known_client(
|
|||||||
};
|
};
|
||||||
|
|
||||||
Ok(discover_homeserver::Response {
|
Ok(discover_homeserver::Response {
|
||||||
homeserver: HomeserverInfo { base_url: client_url },
|
homeserver: HomeserverInfo { base_url: client_url.clone() },
|
||||||
identity_server: None,
|
identity_server: None,
|
||||||
sliding_sync_proxy: None,
|
sliding_sync_proxy: Some(SlidingSyncProxyInfo { url: client_url }),
|
||||||
tile_server: None,
|
tile_server: None,
|
||||||
rtc_foci: services
|
rtc_foci: services
|
||||||
.config
|
.config
|
||||||
@@ -71,7 +71,6 @@ pub(crate) async fn well_known_support(
|
|||||||
|
|
||||||
let email_address = services.config.well_known.support_email.clone();
|
let email_address = services.config.well_known.support_email.clone();
|
||||||
let matrix_id = services.config.well_known.support_mxid.clone();
|
let matrix_id = services.config.well_known.support_mxid.clone();
|
||||||
let pgp_key = services.config.well_known.support_pgp_key.clone();
|
|
||||||
|
|
||||||
// TODO: support defining multiple contacts in the config
|
// TODO: support defining multiple contacts in the config
|
||||||
let mut contacts: Vec<Contact> = vec![];
|
let mut contacts: Vec<Contact> = vec![];
|
||||||
@@ -89,7 +88,6 @@ pub(crate) async fn well_known_support(
|
|||||||
role: role_value.clone(),
|
role: role_value.clone(),
|
||||||
email_address: email_address.clone(),
|
email_address: email_address.clone(),
|
||||||
matrix_id: matrix_id.clone(),
|
matrix_id: matrix_id.clone(),
|
||||||
pgp_key: pgp_key.clone(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +104,6 @@ pub(crate) async fn well_known_support(
|
|||||||
role: role_value.clone(),
|
role: role_value.clone(),
|
||||||
email_address: None,
|
email_address: None,
|
||||||
matrix_id: Some(user_id.to_owned()),
|
matrix_id: Some(user_id.to_owned()),
|
||||||
pgp_key: None,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,6 @@
|
|||||||
|
|
||||||
extern crate conduwuit_core as conduwuit;
|
extern crate conduwuit_core as conduwuit;
|
||||||
extern crate conduwuit_service as service;
|
extern crate conduwuit_service as service;
|
||||||
|
|
||||||
conduwuit_macros::introspect_crate! {}
|
|
||||||
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod router;
|
pub mod router;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|||||||
+4
-8
@@ -28,8 +28,7 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
|||||||
.ruma_route(&client::appservice_ping)
|
.ruma_route(&client::appservice_ping)
|
||||||
.ruma_route(&client::get_supported_versions_route)
|
.ruma_route(&client::get_supported_versions_route)
|
||||||
.ruma_route(&client::get_register_available_route)
|
.ruma_route(&client::get_register_available_route)
|
||||||
.ruma_route(&client::register::register_route)
|
.ruma_route(&client::register_route)
|
||||||
.ruma_route(&client::register::request_registration_token_via_email_route)
|
|
||||||
.ruma_route(&client::get_login_types_route)
|
.ruma_route(&client::get_login_types_route)
|
||||||
.ruma_route(&client::login_route)
|
.ruma_route(&client::login_route)
|
||||||
.ruma_route(&client::login_token_route)
|
.ruma_route(&client::login_token_route)
|
||||||
@@ -37,13 +36,10 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
|||||||
.ruma_route(&client::logout_route)
|
.ruma_route(&client::logout_route)
|
||||||
.ruma_route(&client::logout_all_route)
|
.ruma_route(&client::logout_all_route)
|
||||||
.ruma_route(&client::change_password_route)
|
.ruma_route(&client::change_password_route)
|
||||||
.ruma_route(&client::request_password_change_token_via_email_route)
|
|
||||||
.ruma_route(&client::deactivate_route)
|
.ruma_route(&client::deactivate_route)
|
||||||
.ruma_route(&client::threepid::third_party_route)
|
.ruma_route(&client::third_party_route)
|
||||||
.ruma_route(&client::threepid::request_3pid_management_token_via_email_route)
|
.ruma_route(&client::request_3pid_management_token_via_email_route)
|
||||||
.ruma_route(&client::threepid::request_3pid_management_token_via_msisdn_route)
|
.ruma_route(&client::request_3pid_management_token_via_msisdn_route)
|
||||||
.ruma_route(&client::threepid::add_3pid_route)
|
|
||||||
.ruma_route(&client::threepid::delete_3pid_route)
|
|
||||||
.ruma_route(&client::check_registration_token_validity)
|
.ruma_route(&client::check_registration_token_validity)
|
||||||
.ruma_route(&client::get_capabilities_route)
|
.ruma_route(&client::get_capabilities_route)
|
||||||
.ruma_route(&client::get_pushrules_all_route)
|
.ruma_route(&client::get_pushrules_all_route)
|
||||||
|
|||||||
+39
-6
@@ -2,13 +2,14 @@ use std::{mem, ops::Deref};
|
|||||||
|
|
||||||
use axum::{body::Body, extract::FromRequest};
|
use axum::{body::Body, extract::FromRequest};
|
||||||
use bytes::{BufMut, Bytes, BytesMut};
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
use conduwuit::{Error, Result, debug, debug_warn, err, trace};
|
use conduwuit::{Error, Result, debug, debug_warn, err, trace, utils::string::EMPTY};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
CanonicalJsonObject, CanonicalJsonValue, DeviceId, OwnedDeviceId, OwnedServerName,
|
CanonicalJsonObject, CanonicalJsonValue, DeviceId, OwnedDeviceId, OwnedServerName,
|
||||||
OwnedUserId, ServerName, UserId, api::IncomingRequest,
|
OwnedUserId, ServerName, UserId, api::IncomingRequest,
|
||||||
};
|
};
|
||||||
|
use service::Services;
|
||||||
|
|
||||||
use super::{auth, request, request::Request};
|
use super::{auth, auth::Auth, request, request::Request};
|
||||||
use crate::{State, service::appservice::RegistrationInfo};
|
use crate::{State, service::appservice::RegistrationInfo};
|
||||||
|
|
||||||
/// Extractor for Ruma request structs
|
/// Extractor for Ruma request structs
|
||||||
@@ -107,7 +108,7 @@ where
|
|||||||
}
|
}
|
||||||
let auth = auth::auth(services, &mut request, json_body.as_ref(), &T::METADATA).await?;
|
let auth = auth::auth(services, &mut request, json_body.as_ref(), &T::METADATA).await?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
body: make_body::<T>(&mut request, json_body.as_mut())?,
|
body: make_body::<T>(services, &mut request, json_body.as_mut(), &auth)?,
|
||||||
origin: auth.origin,
|
origin: auth.origin,
|
||||||
sender_user: auth.sender_user,
|
sender_user: auth.sender_user,
|
||||||
sender_device: auth.sender_device,
|
sender_device: auth.sender_device,
|
||||||
@@ -117,11 +118,16 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_body<T>(request: &mut Request, json_body: Option<&mut CanonicalJsonValue>) -> Result<T>
|
fn make_body<T>(
|
||||||
|
services: &Services,
|
||||||
|
request: &mut Request,
|
||||||
|
json_body: Option<&mut CanonicalJsonValue>,
|
||||||
|
auth: &Auth,
|
||||||
|
) -> Result<T>
|
||||||
where
|
where
|
||||||
T: IncomingRequest,
|
T: IncomingRequest,
|
||||||
{
|
{
|
||||||
let body = take_body(request, json_body);
|
let body = take_body(services, request, json_body, auth);
|
||||||
let http_request = into_http_request(request, body);
|
let http_request = into_http_request(request, body);
|
||||||
T::try_from_http_request(http_request, &request.path)
|
T::try_from_http_request(http_request, &request.path)
|
||||||
.map_err(|e| err!(Request(BadJson(debug_warn!("{e}")))))
|
.map_err(|e| err!(Request(BadJson(debug_warn!("{e}")))))
|
||||||
@@ -145,11 +151,38 @@ fn into_http_request(request: &Request, body: Bytes) -> hyper::Request<Bytes> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
fn take_body(request: &mut Request, json_body: Option<&mut CanonicalJsonValue>) -> Bytes {
|
fn take_body(
|
||||||
|
services: &Services,
|
||||||
|
request: &mut Request,
|
||||||
|
json_body: Option<&mut CanonicalJsonValue>,
|
||||||
|
auth: &Auth,
|
||||||
|
) -> Bytes {
|
||||||
let Some(CanonicalJsonValue::Object(json_body)) = json_body else {
|
let Some(CanonicalJsonValue::Object(json_body)) = json_body else {
|
||||||
return mem::take(&mut request.body);
|
return mem::take(&mut request.body);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let user_id = auth.sender_user.clone().unwrap_or_else(|| {
|
||||||
|
let server_name = services.globals.server_name();
|
||||||
|
UserId::parse_with_server_name(EMPTY, server_name).expect("valid user_id")
|
||||||
|
});
|
||||||
|
|
||||||
|
let uiaa_request = json_body
|
||||||
|
.get("auth")
|
||||||
|
.and_then(CanonicalJsonValue::as_object)
|
||||||
|
.and_then(|auth| auth.get("session"))
|
||||||
|
.and_then(CanonicalJsonValue::as_str)
|
||||||
|
.and_then(|session| {
|
||||||
|
services
|
||||||
|
.uiaa
|
||||||
|
.get_uiaa_request(&user_id, auth.sender_device.as_deref(), session)
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(CanonicalJsonValue::Object(initial_request)) = uiaa_request {
|
||||||
|
for (key, value) in initial_request {
|
||||||
|
json_body.entry(key).or_insert(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut buf = BytesMut::new().writer();
|
let mut buf = BytesMut::new().writer();
|
||||||
serde_json::to_writer(&mut buf, &json_body).expect("value serialization can't fail");
|
serde_json::to_writer(&mut buf, &json_body).expect("value serialization can't fail");
|
||||||
buf.into_inner().freeze()
|
buf.into_inner().freeze()
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
use std::borrow::ToOwned;
|
use std::borrow::ToOwned;
|
||||||
|
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduwuit::{
|
use conduwuit::{Err, Error, Result, debug, debug_info, info, matrix::pdu::PduBuilder, warn};
|
||||||
Err, Error, Result, debug, debug_info, info, matrix::pdu::PduBuilder, utils, warn,
|
|
||||||
};
|
|
||||||
use conduwuit_service::Services;
|
use conduwuit_service::Services;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
OwnedUserId, RoomId, RoomVersionId, UserId,
|
CanonicalJsonObject, OwnedUserId, RoomId, RoomVersionId, UserId,
|
||||||
api::{client::error::ErrorKind, federation::membership::prepare_join_event},
|
api::{client::error::ErrorKind, federation::membership::prepare_join_event},
|
||||||
events::{
|
events::{
|
||||||
StateEventType,
|
StateEventType,
|
||||||
@@ -42,7 +40,6 @@ pub(crate) async fn create_join_event_template_route(
|
|||||||
{
|
{
|
||||||
info!(
|
info!(
|
||||||
origin = body.origin().as_str(),
|
origin = body.origin().as_str(),
|
||||||
room_id = %body.room_id,
|
|
||||||
"Refusing to serve make_join for room we aren't participating in"
|
"Refusing to serve make_join for room we aren't participating in"
|
||||||
);
|
);
|
||||||
return Err!(Request(NotFound("This server is not participating in that room.")));
|
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||||
@@ -136,10 +133,10 @@ pub(crate) async fn create_join_event_template_route(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (pdu, _) = services
|
let (_pdu, mut pdu_json) = services
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.create_event(
|
.create_hash_and_sign_event(
|
||||||
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
|
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
|
||||||
join_authorized_via_users_server,
|
join_authorized_via_users_server,
|
||||||
..RoomMemberEventContent::new(MembershipState::Join)
|
..RoomMemberEventContent::new(MembershipState::Join)
|
||||||
@@ -150,8 +147,6 @@ pub(crate) async fn create_join_event_template_route(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
drop(state_lock);
|
drop(state_lock);
|
||||||
let mut pdu_json = utils::to_canonical_object(&pdu)
|
|
||||||
.expect("Barebones PDU should be convertible to canonical JSON");
|
|
||||||
pdu_json.remove("event_id");
|
pdu_json.remove("event_id");
|
||||||
|
|
||||||
Ok(prepare_join_event::v1::Response {
|
Ok(prepare_join_event::v1::Response {
|
||||||
@@ -302,3 +297,18 @@ pub(crate) async fn user_can_perform_restricted_join(
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn maybe_strip_event_id(
|
||||||
|
pdu_json: &mut CanonicalJsonObject,
|
||||||
|
room_version_id: &RoomVersionId,
|
||||||
|
) -> Result {
|
||||||
|
use RoomVersionId::*;
|
||||||
|
|
||||||
|
match room_version_id {
|
||||||
|
| V1 | V2 => Ok(()),
|
||||||
|
| _ => {
|
||||||
|
pdu_json.remove("event_id");
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use RoomVersionId::*;
|
use RoomVersionId::*;
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduwuit::{Err, Error, Result, debug_warn, info, matrix::pdu::PduBuilder, utils, 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},
|
||||||
@@ -28,7 +28,6 @@ pub(crate) async fn create_knock_event_template_route(
|
|||||||
{
|
{
|
||||||
info!(
|
info!(
|
||||||
origin = body.origin().as_str(),
|
origin = body.origin().as_str(),
|
||||||
room_id = %body.room_id,
|
|
||||||
"Refusing to serve make_knock for room we aren't participating in"
|
"Refusing to serve make_knock for room we aren't participating in"
|
||||||
);
|
);
|
||||||
return Err!(Request(NotFound("This server is not participating in that room.")));
|
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||||
@@ -99,10 +98,10 @@ pub(crate) async fn create_knock_event_template_route(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (pdu, _) = services
|
let (_pdu, mut pdu_json) = services
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.create_event(
|
.create_hash_and_sign_event(
|
||||||
PduBuilder::state(
|
PduBuilder::state(
|
||||||
body.user_id.to_string(),
|
body.user_id.to_string(),
|
||||||
&RoomMemberEventContent::new(MembershipState::Knock),
|
&RoomMemberEventContent::new(MembershipState::Knock),
|
||||||
@@ -114,9 +113,9 @@ pub(crate) async fn create_knock_event_template_route(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
drop(state_lock);
|
drop(state_lock);
|
||||||
let mut pdu_json = utils::to_canonical_object(&pdu)
|
|
||||||
.expect("Barebones PDU should be convertible to canonical JSON");
|
// room v3 and above removed the "event_id" field from remote PDU format
|
||||||
pdu_json.remove("event_id");
|
super::maybe_strip_event_id(&mut pdu_json, &room_version_id)?;
|
||||||
|
|
||||||
Ok(create_knock_event_template::v1::Response {
|
Ok(create_knock_event_template::v1::Response {
|
||||||
room_version: room_version_id,
|
room_version: room_version_id,
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduwuit::{Err, Result, info, matrix::pdu::PduBuilder, utils};
|
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},
|
||||||
};
|
};
|
||||||
use serde_json::value::to_raw_value;
|
use serde_json::value::to_raw_value;
|
||||||
|
|
||||||
|
use super::make_join::maybe_strip_event_id;
|
||||||
use crate::Ruma;
|
use crate::Ruma;
|
||||||
|
|
||||||
/// # `GET /_matrix/federation/v1/make_leave/{roomId}/{eventId}`
|
/// # `GET /_matrix/federation/v1/make_leave/{roomId}/{eventId}`
|
||||||
@@ -48,10 +49,10 @@ pub(crate) async fn create_leave_event_template_route(
|
|||||||
let room_version_id = services.rooms.state.get_room_version(&body.room_id).await?;
|
let room_version_id = services.rooms.state.get_room_version(&body.room_id).await?;
|
||||||
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
|
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
|
||||||
|
|
||||||
let (pdu, _) = services
|
let (_pdu, mut pdu_json) = services
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.create_event(
|
.create_hash_and_sign_event(
|
||||||
PduBuilder::state(
|
PduBuilder::state(
|
||||||
body.user_id.to_string(),
|
body.user_id.to_string(),
|
||||||
&RoomMemberEventContent::new(MembershipState::Leave),
|
&RoomMemberEventContent::new(MembershipState::Leave),
|
||||||
@@ -63,9 +64,9 @@ pub(crate) async fn create_leave_event_template_route(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
drop(state_lock);
|
drop(state_lock);
|
||||||
let mut pdu_json = utils::to_canonical_object(&pdu)
|
|
||||||
.expect("Barebones PDU should be convertible to canonical JSON");
|
// room v3 and above removed the "event_id" field from remote PDU format
|
||||||
pdu_json.remove("event_id");
|
maybe_strip_event_id(&mut pdu_json, &room_version_id)?;
|
||||||
|
|
||||||
Ok(prepare_leave_event::v1::Response {
|
Ok(prepare_leave_event::v1::Response {
|
||||||
room_version: Some(room_version_id),
|
room_version: Some(room_version_id),
|
||||||
|
|||||||
@@ -187,15 +187,14 @@ async fn create_join_event(
|
|||||||
"Joining user did not pass restricted room's rules."
|
"Joining user did not pass restricted room's rules."
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
services
|
|
||||||
.server_keys
|
|
||||||
.hash_and_sign_event(&mut value, &room_version_id)
|
|
||||||
.map_err(|e| {
|
|
||||||
err!(Request(InvalidParam(warn!("Failed to sign send_join event: {e}"))))
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trace!("Signing send_join event");
|
||||||
|
services
|
||||||
|
.server_keys
|
||||||
|
.hash_and_sign_event(&mut value, &room_version_id)
|
||||||
|
.map_err(|e| err!(Request(InvalidParam(warn!("Failed to sign send_join event: {e}")))))?;
|
||||||
|
|
||||||
let mutex_lock = services
|
let mutex_lock = services
|
||||||
.rooms
|
.rooms
|
||||||
.event_handler
|
.event_handler
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
name = "conduwuit_build_metadata"
|
name = "conduwuit_build_metadata"
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
homepage.workspace = true
|
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
readme.workspace = true
|
readme.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
@@ -28,6 +27,6 @@ crate-type = [
|
|||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
built = { version = "0.8", features = [] }
|
built = { version = "0.8", features = [] }
|
||||||
cargo_metadata = { version = "0.23.1" }
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
use std::{
|
use std::process::Command;
|
||||||
collections::BTreeMap, env, fmt::Write as FmtWrite, fs, io::Write, path::Path,
|
|
||||||
process::Command,
|
|
||||||
};
|
|
||||||
|
|
||||||
use cargo_metadata::MetadataCommand;
|
|
||||||
fn run_git_command(args: &[&str]) -> Option<String> {
|
fn run_git_command(args: &[&str]) -> Option<String> {
|
||||||
Command::new("git")
|
Command::new("git")
|
||||||
.args(args)
|
.args(args)
|
||||||
@@ -15,60 +11,12 @@ fn run_git_command(args: &[&str]) -> Option<String> {
|
|||||||
.filter(|s| !s.is_empty())
|
.filter(|s| !s.is_empty())
|
||||||
}
|
}
|
||||||
fn get_env(env_var: &str) -> Option<String> {
|
fn get_env(env_var: &str) -> Option<String> {
|
||||||
match env::var(env_var) {
|
match std::env::var(env_var) {
|
||||||
| Ok(val) if !val.is_empty() => Some(val),
|
| Ok(val) if !val.is_empty() => Some(val),
|
||||||
| _ => None,
|
| _ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("cargo:rerun-if-changed=Cargo.toml");
|
|
||||||
|
|
||||||
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); // Cargo.toml path
|
|
||||||
let manifest_path = Path::new(&manifest_dir).join("Cargo.toml");
|
|
||||||
|
|
||||||
let metadata = MetadataCommand::new()
|
|
||||||
.manifest_path(&manifest_path)
|
|
||||||
.no_deps()
|
|
||||||
.exec()
|
|
||||||
.expect("failed to parse `cargo metadata`");
|
|
||||||
|
|
||||||
let workspace_packages = metadata
|
|
||||||
.workspace_members
|
|
||||||
.iter()
|
|
||||||
.map(|package| {
|
|
||||||
let package = metadata.packages.iter().find(|p| p.id == *package).unwrap();
|
|
||||||
println!("cargo:rerun-if-changed={}", package.manifest_path.as_str());
|
|
||||||
package
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// Extract available features from workspace packages
|
|
||||||
let mut available_features: BTreeMap<String, Vec<String>> = BTreeMap::new();
|
|
||||||
for package in &workspace_packages {
|
|
||||||
let crate_name = package
|
|
||||||
.name
|
|
||||||
.trim_start_matches("conduwuit-")
|
|
||||||
.replace('-', "_");
|
|
||||||
let features: Vec<String> = package.features.keys().cloned().collect();
|
|
||||||
if !features.is_empty() {
|
|
||||||
available_features.insert(crate_name, features);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate Rust code for available features
|
|
||||||
let features_code = generate_features_code(&available_features);
|
|
||||||
let features_dst =
|
|
||||||
Path::new(&env::var("OUT_DIR").expect("OUT_DIR not set")).join("available_features.rs");
|
|
||||||
let mut features_file = fs::File::create(features_dst).unwrap();
|
|
||||||
features_file.write_all(features_code.as_bytes()).unwrap();
|
|
||||||
|
|
||||||
let dst = Path::new(&env::var("OUT_DIR").expect("OUT_DIR not set")).join("pkg.json");
|
|
||||||
|
|
||||||
let mut out_file = fs::File::create(dst).unwrap();
|
|
||||||
out_file
|
|
||||||
.write_all(format!("{workspace_packages:?}").as_bytes())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// built gets the default crate from the workspace. Not sure if this is intended
|
// built gets the default crate from the workspace. Not sure if this is intended
|
||||||
// behavior, but it's what we want.
|
// behavior, but it's what we want.
|
||||||
built::write_built_file().expect("Failed to acquire build-time information");
|
built::write_built_file().expect("Failed to acquire build-time information");
|
||||||
@@ -143,30 +91,3 @@ fn main() {
|
|||||||
println!("cargo:rerun-if-env-changed=GIT_REMOTE_URL");
|
println!("cargo:rerun-if-env-changed=GIT_REMOTE_URL");
|
||||||
println!("cargo:rerun-if-env-changed=GIT_REMOTE_COMMIT_URL");
|
println!("cargo:rerun-if-env-changed=GIT_REMOTE_COMMIT_URL");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_features_code(features: &BTreeMap<String, Vec<String>>) -> String {
|
|
||||||
let mut code = String::from(
|
|
||||||
r#"
|
|
||||||
/// All available features for workspace crates
|
|
||||||
pub const WORKSPACE_FEATURES: &[(&str, &[&str])] = &[
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
for (crate_name, feature_list) in features {
|
|
||||||
write!(code, " (\"{crate_name}\", &[").unwrap();
|
|
||||||
for (i, feature) in feature_list.iter().enumerate() {
|
|
||||||
if i > 0 {
|
|
||||||
code.push_str(", ");
|
|
||||||
}
|
|
||||||
write!(code, "\"{feature}\"").unwrap();
|
|
||||||
}
|
|
||||||
code.push_str("]),\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
code.push_str(
|
|
||||||
r#"];
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
code
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,10 +2,6 @@ pub mod built {
|
|||||||
include!(concat!(env!("OUT_DIR"), "/built.rs"));
|
include!(concat!(env!("OUT_DIR"), "/built.rs"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include generated available features
|
|
||||||
// This provides: pub const WORKSPACE_FEATURES: &[(&str, &[&str])]
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/available_features.rs"));
|
|
||||||
|
|
||||||
pub static GIT_COMMIT_HASH: Option<&str> = option_env!("GIT_COMMIT_HASH");
|
pub static GIT_COMMIT_HASH: Option<&str> = option_env!("GIT_COMMIT_HASH");
|
||||||
|
|
||||||
pub static GIT_COMMIT_HASH_SHORT: Option<&str> = option_env!("GIT_COMMIT_HASH_SHORT");
|
pub static GIT_COMMIT_HASH_SHORT: Option<&str> = option_env!("GIT_COMMIT_HASH_SHORT");
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
name = "conduwuit_core"
|
name = "conduwuit_core"
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
homepage.workspace = true
|
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
readme.workspace = true
|
readme.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
@@ -85,7 +84,6 @@ libc.workspace = true
|
|||||||
libloading.workspace = true
|
libloading.workspace = true
|
||||||
libloading.optional = true
|
libloading.optional = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
lettre.workspace = true
|
|
||||||
num-traits.workspace = true
|
num-traits.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
rand_core = { version = "0.6.4", features = ["getrandom"] }
|
rand_core = { version = "0.6.4", features = ["getrandom"] }
|
||||||
|
|||||||
+2
-77
@@ -4,7 +4,7 @@ pub mod manager;
|
|||||||
pub mod proxy;
|
pub mod proxy;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, BTreeSet, HashMap},
|
collections::{BTreeMap, BTreeSet},
|
||||||
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
@@ -16,13 +16,12 @@ use either::{
|
|||||||
};
|
};
|
||||||
use figment::providers::{Env, Format, Toml};
|
use figment::providers::{Env, Format, Toml};
|
||||||
pub use figment::{Figment, value::Value as FigmentValue};
|
pub use figment::{Figment, value::Value as FigmentValue};
|
||||||
use lettre::message::Mailbox;
|
|
||||||
use regex::RegexSet;
|
use regex::RegexSet;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, RoomVersionId,
|
OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, RoomVersionId,
|
||||||
api::client::discovery::{discover_homeserver::RtcFocusInfo, discover_support::ContactRole},
|
api::client::discovery::{discover_homeserver::RtcFocusInfo, discover_support::ContactRole},
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize, de::IgnoredAny};
|
use serde::{Deserialize, de::IgnoredAny};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use self::proxy::ProxyConfig;
|
use self::proxy::ProxyConfig;
|
||||||
@@ -655,20 +654,6 @@ pub struct Config {
|
|||||||
/// even if `recaptcha_site_key` is set.
|
/// even if `recaptcha_site_key` is set.
|
||||||
pub recaptcha_private_site_key: Option<String>,
|
pub recaptcha_private_site_key: Option<String>,
|
||||||
|
|
||||||
/// Policy documents, such as terms and conditions or a privacy policy,
|
|
||||||
/// which users must agree to when registering an account.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```ignore
|
|
||||||
/// [global.registration_terms.privacy_policy]
|
|
||||||
/// en = { name = "Privacy Policy", url = "https://homeserver.example/en/privacy_policy.html" }
|
|
||||||
/// es = { name = "Política de Privacidad", url = "https://homeserver.example/es/privacy_policy.html" }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// default: {}
|
|
||||||
#[serde(default)]
|
|
||||||
pub registration_terms: HashMap<String, HashMap<String, TermsDocument>>,
|
|
||||||
|
|
||||||
/// Controls whether encrypted rooms and events are allowed.
|
/// Controls whether encrypted rooms and events are allowed.
|
||||||
#[serde(default = "true_fn")]
|
#[serde(default = "true_fn")]
|
||||||
pub allow_encryption: bool,
|
pub allow_encryption: bool,
|
||||||
@@ -775,9 +760,6 @@ pub struct Config {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub well_known: WellKnownConfig,
|
pub well_known: WellKnownConfig,
|
||||||
|
|
||||||
/// display: nested
|
|
||||||
pub smtp: Option<SmtpConfig>,
|
|
||||||
|
|
||||||
/// Enable OpenTelemetry OTLP tracing export. This replaces the deprecated
|
/// Enable OpenTelemetry OTLP tracing export. This replaces the deprecated
|
||||||
/// Jaeger exporter. Traces will be sent via OTLP to a collector (such as
|
/// Jaeger exporter. Traces will be sent via OTLP to a collector (such as
|
||||||
/// Jaeger) that supports the OpenTelemetry Protocol.
|
/// Jaeger) that supports the OpenTelemetry Protocol.
|
||||||
@@ -2205,10 +2187,6 @@ pub struct WellKnownConfig {
|
|||||||
/// listed.
|
/// listed.
|
||||||
pub support_mxid: Option<OwnedUserId>,
|
pub support_mxid: Option<OwnedUserId>,
|
||||||
|
|
||||||
/// PGP key URI for server support contacts, to be served as part of the
|
|
||||||
/// MSC1929 server support endpoint.
|
|
||||||
pub support_pgp_key: Option<String>,
|
|
||||||
|
|
||||||
/// **DEPRECATED**: Use `[global.matrix_rtc].foci` instead.
|
/// **DEPRECATED**: Use `[global.matrix_rtc].foci` instead.
|
||||||
///
|
///
|
||||||
/// A list of MatrixRTC foci URLs which will be served as part of the
|
/// A list of MatrixRTC foci URLs which will be served as part of the
|
||||||
@@ -2466,59 +2444,6 @@ pub struct DraupnirConfig {
|
|||||||
pub secret: String,
|
pub secret: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[config_example_generator(
|
|
||||||
filename = "conduwuit-example.toml",
|
|
||||||
section = "global.smtp",
|
|
||||||
optional = "true"
|
|
||||||
)]
|
|
||||||
pub struct SmtpConfig {
|
|
||||||
/// A `smtp://`` URI which will be used to connect to a mail server.
|
|
||||||
/// Uncommenting the [global.smtp] group and setting this option enables
|
|
||||||
/// features which depend on the ability to send email,
|
|
||||||
/// such as self-service password resets.
|
|
||||||
///
|
|
||||||
/// For most modern mail servers, format the URI like this:
|
|
||||||
/// `smtps://username:password@hostname:port`
|
|
||||||
/// Note that you will need to URL-encode the username and password. If your
|
|
||||||
/// username _is_ your email address, you will need to replace the `@` with
|
|
||||||
/// `%40`.
|
|
||||||
///
|
|
||||||
/// For a guide on the accepted URI syntax, consult Lettre's documentation:
|
|
||||||
/// https://docs.rs/lettre/latest/lettre/transport/smtp/struct.AsyncSmtpTransport.html#method.from_url
|
|
||||||
pub connection_uri: String,
|
|
||||||
|
|
||||||
/// The outgoing address which will be used for sending emails.
|
|
||||||
///
|
|
||||||
/// For a syntax guide, see https://datatracker.ietf.org/doc/html/rfc2822#section-3.4
|
|
||||||
///
|
|
||||||
/// ...or if you don't want to read the RFC, for some reason:
|
|
||||||
/// - `Name <address@domain.org>` to specify a sender name
|
|
||||||
/// - `address@domain.org` to not use a name
|
|
||||||
pub sender: Mailbox,
|
|
||||||
|
|
||||||
/// Whether to require that users provide an email address when they
|
|
||||||
/// register.
|
|
||||||
///
|
|
||||||
/// default: false
|
|
||||||
#[serde(default)]
|
|
||||||
pub require_email_for_registration: bool,
|
|
||||||
|
|
||||||
/// Whether to require that users who register with a registration token
|
|
||||||
/// provide an email address.
|
|
||||||
///
|
|
||||||
/// default: false
|
|
||||||
#[serde(default)]
|
|
||||||
pub require_email_for_token_registration: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A policy document for use with a m.login.terms stage.
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
pub struct TermsDocument {
|
|
||||||
pub name: String,
|
|
||||||
pub url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEPRECATED_KEYS: &[&str] = &[
|
const DEPRECATED_KEYS: &[&str] = &[
|
||||||
"cache_capacity",
|
"cache_capacity",
|
||||||
"conduit_cache_capacity_modifier",
|
"conduit_cache_capacity_modifier",
|
||||||
|
|||||||
@@ -77,14 +77,6 @@ macro_rules! err {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
(Request($variant:ident($($args:tt)+), $status_code:ident)) => {
|
|
||||||
$crate::error::Error::Request(
|
|
||||||
$crate::ruma::api::client::error::ErrorKind::$variant,
|
|
||||||
$crate::format_maybe!($($args)+),
|
|
||||||
$crate::http::StatusCode::$status_code,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
(Config($item:literal, $($args:tt)+)) => {{
|
(Config($item:literal, $($args:tt)+)) => {{
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
$crate::error::Error::Config($item, $crate::err_log!(buf, error, config = %$item, $($args)+))
|
$crate::error::Error::Config($item, $crate::err_log!(buf, error, config = %$item, $($args)+))
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
//! Information about features the crates were compiled with.
|
|
||||||
//! Only available for crates that have called the `introspect_crate` macro
|
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
pub static ENABLED_FEATURES: std::sync::Mutex<BTreeMap<&str, &[&str]>> =
|
|
||||||
std::sync::Mutex::new(BTreeMap::new());
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
pub mod introspection;
|
|
||||||
pub mod room_version;
|
pub mod room_version;
|
||||||
pub mod version;
|
pub mod version;
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ pub use ::smallstr;
|
|||||||
pub use ::smallvec;
|
pub use ::smallvec;
|
||||||
pub use ::toml;
|
pub use ::toml;
|
||||||
pub use ::tracing;
|
pub use ::tracing;
|
||||||
pub use conduwuit_build_metadata as build_metadata;
|
|
||||||
pub use config::Config;
|
pub use config::Config;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
pub use info::{
|
pub use info::{
|
||||||
@@ -35,8 +34,6 @@ pub use utils::{implement, result, result::Result};
|
|||||||
|
|
||||||
pub use crate as conduwuit_core;
|
pub use crate as conduwuit_core;
|
||||||
|
|
||||||
conduwuit_macros::introspect_crate! {}
|
|
||||||
|
|
||||||
#[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]
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
name = "conduwuit_database"
|
name = "conduwuit_database"
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
homepage.workspace = true
|
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
readme.workspace = true
|
readme.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
@@ -55,9 +54,7 @@ bindgen-runtime = [
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
async-channel.workspace = true
|
async-channel.workspace = true
|
||||||
conduwuit-core.workspace = true
|
conduwuit-core.workspace = true
|
||||||
conduwuit-macros.workspace = true
|
|
||||||
const-str.workspace = true
|
const-str.workspace = true
|
||||||
ctor.workspace = true
|
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
minicbor.workspace = true
|
minicbor.workspace = true
|
||||||
|
|||||||
@@ -415,6 +415,13 @@ impl<'a, 'de: 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
|
|||||||
tracing::instrument(level = "trace", skip_all, fields(?self.buf))
|
tracing::instrument(level = "trace", skip_all, fields(?self.buf))
|
||||||
)]
|
)]
|
||||||
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
|
||||||
|
debug_assert_eq!(
|
||||||
|
conduwuit::debug::type_name::<V>(),
|
||||||
|
"serde_json::value::de::<impl serde_core::de::Deserialize for \
|
||||||
|
serde_json::value::Value>::deserialize::ValueVisitor",
|
||||||
|
"deserialize_any: type not expected"
|
||||||
|
);
|
||||||
|
|
||||||
match self.record_peek_byte() {
|
match self.record_peek_byte() {
|
||||||
| Some(b'{') => self.deserialize_map(visitor),
|
| Some(b'{') => self.deserialize_map(visitor),
|
||||||
| Some(b'[') => serde_json::Deserializer::from_slice(self.record_next())
|
| Some(b'[') => serde_json::Deserializer::from_slice(self.record_next())
|
||||||
|
|||||||
@@ -70,19 +70,17 @@ fn descriptor_cf_options(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let opts = opts
|
let mut opts = opts
|
||||||
.get_options_from_string("{{arena_block_size=2097152;}}")
|
.get_options_from_string("{{arena_block_size=2097152;}}")
|
||||||
.map_err(map_err)?;
|
.map_err(map_err)?;
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
let opts = {
|
let opts = opts
|
||||||
let mut opts = opts;
|
.get_options_from_string(
|
||||||
opts.get_options_from_string(
|
|
||||||
"{{paranoid_checks=true;paranoid_file_checks=true;force_consistency_checks=true;\
|
"{{paranoid_checks=true;paranoid_file_checks=true;force_consistency_checks=true;\
|
||||||
verify_sst_unique_id_in_manifest=true;}}",
|
verify_sst_unique_id_in_manifest=true;}}",
|
||||||
)
|
)
|
||||||
.map_err(map_err)?
|
.map_err(map_err)?;
|
||||||
};
|
|
||||||
|
|
||||||
Ok(opts)
|
Ok(opts)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,10 +53,6 @@ pub(super) static MAPS: &[Descriptor] = &[
|
|||||||
name: "disabledroomids",
|
name: "disabledroomids",
|
||||||
..descriptor::RANDOM_SMALL
|
..descriptor::RANDOM_SMALL
|
||||||
},
|
},
|
||||||
Descriptor {
|
|
||||||
name: "email_localpart",
|
|
||||||
..descriptor::RANDOM_SMALL
|
|
||||||
},
|
|
||||||
Descriptor {
|
Descriptor {
|
||||||
name: "eventid_outlierpdu",
|
name: "eventid_outlierpdu",
|
||||||
cache_disp: CacheDisp::SharedWith("pduid_pdu"),
|
cache_disp: CacheDisp::SharedWith("pduid_pdu"),
|
||||||
@@ -104,10 +100,6 @@ pub(super) static MAPS: &[Descriptor] = &[
|
|||||||
name: "lazyloadedids",
|
name: "lazyloadedids",
|
||||||
..descriptor::RANDOM_SMALL
|
..descriptor::RANDOM_SMALL
|
||||||
},
|
},
|
||||||
Descriptor {
|
|
||||||
name: "localpart_email",
|
|
||||||
..descriptor::RANDOM_SMALL
|
|
||||||
},
|
|
||||||
Descriptor {
|
Descriptor {
|
||||||
name: "mediaid_file",
|
name: "mediaid_file",
|
||||||
..descriptor::RANDOM_SMALL
|
..descriptor::RANDOM_SMALL
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
extern crate conduwuit_core as conduwuit;
|
extern crate conduwuit_core as conduwuit;
|
||||||
extern crate rust_rocksdb as rocksdb;
|
extern crate rust_rocksdb as rocksdb;
|
||||||
|
|
||||||
conduwuit_macros::introspect_crate! {}
|
|
||||||
|
|
||||||
conduwuit::mod_ctor! {}
|
conduwuit::mod_ctor! {}
|
||||||
conduwuit::mod_dtor! {}
|
conduwuit::mod_dtor! {}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user