mirror of
https://gitlab.com/famedly/conduit.git
synced 2025-06-27 16:35:59 +00:00
Merge branch 'next' into 'shekohex/fix-media-file-name'
# Conflicts: # Cargo.toml # src/database/mod.rs # src/service/globals/mod.rs
This commit is contained in:
commit
a691b263dc
130 changed files with 8761 additions and 5036 deletions
15
.editorconfig
Normal file
15
.editorconfig
Normal file
|
@ -0,0 +1,15 @@
|
|||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
tab_width = 4
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
max_line_length = 120
|
||||
|
||||
[*.nix]
|
||||
indent_size = 2
|
4
.envrc
4
.envrc
|
@ -1 +1,5 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
use flake
|
||||
|
||||
PATH_add bin
|
||||
|
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -68,3 +68,9 @@ cached_target
|
|||
|
||||
# Direnv cache
|
||||
/.direnv
|
||||
|
||||
# Gitlab CI cache
|
||||
/.gitlab-ci.d
|
||||
|
||||
# mdbook output
|
||||
public/
|
407
.gitlab-ci.yml
407
.gitlab-ci.yml
|
@ -1,241 +1,184 @@
|
|||
stages:
|
||||
- build
|
||||
- build docker image
|
||||
- test
|
||||
- upload artifacts
|
||||
- ci
|
||||
- artifacts
|
||||
- publish
|
||||
|
||||
variables:
|
||||
# Make GitLab CI go fast:
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
FF_USE_FASTZIP: 1
|
||||
CACHE_COMPRESSION_LEVEL: fastest
|
||||
|
||||
# --------------------------------------------------------------------- #
|
||||
# Create and publish docker image #
|
||||
# --------------------------------------------------------------------- #
|
||||
|
||||
.docker-shared-settings:
|
||||
stage: "build docker image"
|
||||
image:
|
||||
name: jdrouet/docker-with-buildx:20.10.21-0.9.1
|
||||
pull_policy: if-not-present
|
||||
needs: []
|
||||
tags: [ "docker" ]
|
||||
variables:
|
||||
# Docker in Docker:
|
||||
DOCKER_HOST: tcp://docker:2375/
|
||||
DOCKER_TLS_CERTDIR: ""
|
||||
# Famedly runners use BTRFS, overlayfs and overlay2 often break jobs
|
||||
DOCKER_DRIVER: btrfs
|
||||
services:
|
||||
- docker:dind
|
||||
script:
|
||||
- apk add openssh-client
|
||||
- eval $(ssh-agent -s)
|
||||
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
|
||||
- printf "Host *\n\tStrictHostKeyChecking no\n\n" >> ~/.ssh/config
|
||||
- sh .gitlab/setup-buildx-remote-builders.sh
|
||||
# Authorize against this project's own image registry:
|
||||
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
|
||||
# Build multiplatform image and push to temporary tag:
|
||||
- >
|
||||
docker buildx build
|
||||
--platform "linux/arm/v7,linux/arm64,linux/amd64"
|
||||
--pull
|
||||
--tag "$CI_REGISTRY_IMAGE/temporary-ci-images:$CI_JOB_ID"
|
||||
--push
|
||||
--file "Dockerfile" .
|
||||
# Build multiplatform image to deb stage and extract their .deb files:
|
||||
- >
|
||||
docker buildx build
|
||||
--platform "linux/arm/v7,linux/arm64,linux/amd64"
|
||||
--target "packager-result"
|
||||
--output="type=local,dest=/tmp/build-output"
|
||||
--file "Dockerfile" .
|
||||
# Build multiplatform image to binary stage and extract their binaries:
|
||||
- >
|
||||
docker buildx build
|
||||
--platform "linux/arm/v7,linux/arm64,linux/amd64"
|
||||
--target "builder-result"
|
||||
--output="type=local,dest=/tmp/build-output"
|
||||
--file "Dockerfile" .
|
||||
# Copy to GitLab container registry:
|
||||
- >
|
||||
docker buildx imagetools create
|
||||
--tag "$CI_REGISTRY_IMAGE/$TAG"
|
||||
--tag "$CI_REGISTRY_IMAGE/$TAG-bullseye"
|
||||
--tag "$CI_REGISTRY_IMAGE/$TAG-commit-$CI_COMMIT_SHORT_SHA"
|
||||
"$CI_REGISTRY_IMAGE/temporary-ci-images:$CI_JOB_ID"
|
||||
# if DockerHub credentials exist, also copy to dockerhub:
|
||||
- if [ -n "${DOCKER_HUB}" ]; then docker login -u "$DOCKER_HUB_USER" -p "$DOCKER_HUB_PASSWORD" "$DOCKER_HUB"; fi
|
||||
- >
|
||||
if [ -n "${DOCKER_HUB}" ]; then
|
||||
docker buildx imagetools create
|
||||
--tag "$DOCKER_HUB_IMAGE/$TAG"
|
||||
--tag "$DOCKER_HUB_IMAGE/$TAG-bullseye"
|
||||
--tag "$DOCKER_HUB_IMAGE/$TAG-commit-$CI_COMMIT_SHORT_SHA"
|
||||
"$CI_REGISTRY_IMAGE/temporary-ci-images:$CI_JOB_ID"
|
||||
; fi
|
||||
- mv /tmp/build-output ./
|
||||
artifacts:
|
||||
paths:
|
||||
- "./build-output/"
|
||||
|
||||
docker:next:
|
||||
extends: .docker-shared-settings
|
||||
rules:
|
||||
- if: '$BUILD_SERVER_SSH_PRIVATE_KEY && $CI_COMMIT_BRANCH == "next"'
|
||||
variables:
|
||||
TAG: "matrix-conduit:next"
|
||||
|
||||
docker:master:
|
||||
extends: .docker-shared-settings
|
||||
rules:
|
||||
- if: '$BUILD_SERVER_SSH_PRIVATE_KEY && $CI_COMMIT_BRANCH == "master"'
|
||||
variables:
|
||||
TAG: "matrix-conduit:latest"
|
||||
|
||||
docker:tags:
|
||||
extends: .docker-shared-settings
|
||||
rules:
|
||||
- if: "$BUILD_SERVER_SSH_PRIVATE_KEY && $CI_COMMIT_TAG"
|
||||
variables:
|
||||
TAG: "matrix-conduit:$CI_COMMIT_TAG"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------- #
|
||||
# Run tests #
|
||||
# --------------------------------------------------------------------- #
|
||||
|
||||
cargo check:
|
||||
stage: test
|
||||
image: docker.io/rust:1.64.0-bullseye
|
||||
needs: []
|
||||
interruptible: true
|
||||
before_script:
|
||||
- "rustup show && rustc --version && cargo --version" # Print version info for debugging
|
||||
- apt-get update && apt-get -y --no-install-recommends install libclang-dev # dependency for rocksdb
|
||||
script:
|
||||
- cargo check
|
||||
|
||||
|
||||
.test-shared-settings:
|
||||
stage: "test"
|
||||
needs: []
|
||||
image: "registry.gitlab.com/jfowl/conduit-containers/rust-with-tools:latest"
|
||||
tags: ["docker"]
|
||||
variables:
|
||||
CARGO_INCREMENTAL: "false" # https://matklad.github.io/2021/09/04/fast-rust-builds.html#ci-workflow
|
||||
interruptible: true
|
||||
|
||||
test:cargo:
|
||||
extends: .test-shared-settings
|
||||
before_script:
|
||||
- apt-get update && apt-get -y --no-install-recommends install libclang-dev # dependency for rocksdb
|
||||
script:
|
||||
- rustc --version && cargo --version # Print version info for debugging
|
||||
- "cargo test --color always --workspace --verbose --locked --no-fail-fast -- -Z unstable-options --format json | gitlab-report -p test > $CI_PROJECT_DIR/report.xml"
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
junit: report.xml
|
||||
|
||||
test:clippy:
|
||||
extends: .test-shared-settings
|
||||
allow_failure: true
|
||||
before_script:
|
||||
- rustup component add clippy
|
||||
- apt-get update && apt-get -y --no-install-recommends install libclang-dev # dependency for rocksdb
|
||||
script:
|
||||
- rustc --version && cargo --version # Print version info for debugging
|
||||
- "cargo clippy --color always --verbose --message-format=json | gitlab-report -p clippy > $CI_PROJECT_DIR/gl-code-quality-report.json"
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
codequality: gl-code-quality-report.json
|
||||
|
||||
test:format:
|
||||
extends: .test-shared-settings
|
||||
before_script:
|
||||
- rustup component add rustfmt
|
||||
script:
|
||||
- cargo fmt --all -- --check
|
||||
|
||||
test:audit:
|
||||
extends: .test-shared-settings
|
||||
allow_failure: true
|
||||
script:
|
||||
- cargo audit --color always || true
|
||||
- cargo audit --stale --json | gitlab-report -p audit > gl-sast-report.json
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
sast: gl-sast-report.json
|
||||
|
||||
test:dockerlint:
|
||||
stage: "test"
|
||||
needs: []
|
||||
image: "ghcr.io/hadolint/hadolint@sha256:6c4b7c23f96339489dd35f21a711996d7ce63047467a9a562287748a03ad5242" # 2.8.0-alpine
|
||||
interruptible: true
|
||||
script:
|
||||
- hadolint --version
|
||||
# First pass: Print for CI log:
|
||||
- >
|
||||
hadolint
|
||||
--no-fail --verbose
|
||||
./Dockerfile
|
||||
# Then output the results into a json for GitLab to pretty-print this in the MR:
|
||||
- >
|
||||
hadolint
|
||||
--format gitlab_codeclimate
|
||||
--failure-threshold error
|
||||
./Dockerfile > dockerlint.json
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
codequality: dockerlint.json
|
||||
paths:
|
||||
- dockerlint.json
|
||||
rules:
|
||||
- if: '$CI_COMMIT_REF_NAME != "master"'
|
||||
changes:
|
||||
- docker/*Dockerfile
|
||||
- Dockerfile
|
||||
- .gitlab-ci.yml
|
||||
- if: '$CI_COMMIT_REF_NAME == "master"'
|
||||
- if: '$CI_COMMIT_REF_NAME == "next"'
|
||||
|
||||
# --------------------------------------------------------------------- #
|
||||
# Store binaries as package so they have download urls #
|
||||
# --------------------------------------------------------------------- #
|
||||
|
||||
# DISABLED FOR NOW, NEEDS TO BE FIXED AT A LATER TIME:
|
||||
|
||||
#publish:package:
|
||||
# stage: "upload artifacts"
|
||||
# needs:
|
||||
# - "docker:tags"
|
||||
# rules:
|
||||
# - if: "$CI_COMMIT_TAG"
|
||||
# image: curlimages/curl:latest
|
||||
# tags: ["docker"]
|
||||
# variables:
|
||||
# GIT_STRATEGY: "none" # Don't need a clean copy of the code, we just operate on artifacts
|
||||
# script:
|
||||
# - 'BASE_URL="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/conduit-${CI_COMMIT_REF_SLUG}/build-${CI_PIPELINE_ID}"'
|
||||
# - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build-output/linux_amd64/conduit "${BASE_URL}/conduit-x86_64-unknown-linux-gnu"'
|
||||
# - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build-output/linux_arm_v7/conduit "${BASE_URL}/conduit-armv7-unknown-linux-gnu"'
|
||||
# - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build-output/linux_arm64/conduit "${BASE_URL}/conduit-aarch64-unknown-linux-gnu"'
|
||||
# - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build-output/linux_amd64/conduit.deb "${BASE_URL}/conduit-x86_64-unknown-linux-gnu.deb"'
|
||||
# - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build-output/linux_arm_v7/conduit.deb "${BASE_URL}/conduit-armv7-unknown-linux-gnu.deb"'
|
||||
# - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build-output/linux_arm64/conduit.deb "${BASE_URL}/conduit-aarch64-unknown-linux-gnu.deb"'
|
||||
# Makes some things print in color
|
||||
TERM: ansi
|
||||
|
||||
# Avoid duplicate pipelines
|
||||
# See: https://docs.gitlab.com/ee/ci/yaml/workflow.html#switch-between-branch-pipelines-and-merge-request-pipelines
|
||||
workflow:
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||
- if: "$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS"
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
|
||||
when: never
|
||||
- if: "$CI_COMMIT_BRANCH"
|
||||
- if: "$CI_COMMIT_TAG"
|
||||
- if: $CI
|
||||
|
||||
before_script:
|
||||
# Enable nix-command and flakes
|
||||
- if command -v nix > /dev/null; then echo "experimental-features = nix-command flakes" >> /etc/nix/nix.conf; fi
|
||||
|
||||
# Add our own binary cache
|
||||
- if command -v nix > /dev/null; then echo "extra-substituters = https://nix.computer.surgery/conduit" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = conduit:ZGAf6P6LhNvnoJJ3Me3PRg7tlLSrPxcQ2RiE5LIppjo=" >> /etc/nix/nix.conf; fi
|
||||
|
||||
# Add alternate binary cache
|
||||
- if command -v nix > /dev/null && [ -n "$ATTIC_ENDPOINT" ]; then echo "extra-substituters = $ATTIC_ENDPOINT" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null && [ -n "$ATTIC_PUBLIC_KEY" ]; then echo "extra-trusted-public-keys = $ATTIC_PUBLIC_KEY" >> /etc/nix/nix.conf; fi
|
||||
|
||||
# Add crane binary cache
|
||||
- if command -v nix > /dev/null; then echo "extra-substituters = https://crane.cachix.org" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = crane.cachix.org-1:8Scfpmn9w+hGdXH/Q9tTLiYAE/2dnJYRJP7kl80GuRk=" >> /etc/nix/nix.conf; fi
|
||||
|
||||
# Add nix-community binary cache
|
||||
- if command -v nix > /dev/null; then echo "extra-substituters = https://nix-community.cachix.org" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" >> /etc/nix/nix.conf; fi
|
||||
|
||||
# Install direnv and nix-direnv
|
||||
- if command -v nix > /dev/null; then nix-env -iA nixpkgs.direnv nixpkgs.nix-direnv; fi
|
||||
|
||||
# Allow .envrc
|
||||
- if command -v nix > /dev/null; then direnv allow; fi
|
||||
|
||||
# Set CARGO_HOME to a cacheable path
|
||||
- export CARGO_HOME="$(git rev-parse --show-toplevel)/.gitlab-ci.d/cargo"
|
||||
|
||||
ci:
|
||||
stage: ci
|
||||
image: nixos/nix:2.20.4
|
||||
script:
|
||||
# Cache the inputs required for the devShell
|
||||
- ./bin/nix-build-and-cache .#devShells.x86_64-linux.default.inputDerivation
|
||||
|
||||
- direnv exec . engage
|
||||
cache:
|
||||
key: nix
|
||||
paths:
|
||||
- target
|
||||
- .gitlab-ci.d
|
||||
rules:
|
||||
# CI on upstream runners (only available for maintainers)
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $IS_UPSTREAM_CI == "true"
|
||||
# Manual CI on unprotected branches that are not MRs
|
||||
- if: $CI_PIPELINE_SOURCE != "merge_request_event" && $CI_COMMIT_REF_PROTECTED == "false"
|
||||
when: manual
|
||||
# Manual CI on forks
|
||||
- if: $IS_UPSTREAM_CI != "true"
|
||||
when: manual
|
||||
- if: $CI
|
||||
interruptible: true
|
||||
|
||||
artifacts:
|
||||
stage: artifacts
|
||||
image: nixos/nix:2.20.4
|
||||
script:
|
||||
- ./bin/nix-build-and-cache .#static-x86_64-unknown-linux-musl
|
||||
- cp result/bin/conduit x86_64-unknown-linux-musl
|
||||
|
||||
- mkdir -p target/release
|
||||
- cp result/bin/conduit target/release
|
||||
- direnv exec . cargo deb --no-build
|
||||
- mv target/debian/*.deb x86_64-unknown-linux-musl.deb
|
||||
|
||||
# Since the OCI image package is based on the binary package, this has the
|
||||
# fun side effect of uploading the normal binary too. Conduit users who are
|
||||
# deploying with Nix can leverage this fact by adding our binary cache to
|
||||
# their systems.
|
||||
#
|
||||
# Note that although we have an `oci-image-x86_64-unknown-linux-musl`
|
||||
# output, we don't build it because it would be largely redundant to this
|
||||
# one since it's all containerized anyway.
|
||||
- ./bin/nix-build-and-cache .#oci-image
|
||||
- cp result oci-image-amd64.tar.gz
|
||||
|
||||
- ./bin/nix-build-and-cache .#static-aarch64-unknown-linux-musl
|
||||
- cp result/bin/conduit aarch64-unknown-linux-musl
|
||||
|
||||
- ./bin/nix-build-and-cache .#oci-image-aarch64-unknown-linux-musl
|
||||
- cp result oci-image-arm64v8.tar.gz
|
||||
|
||||
- ./bin/nix-build-and-cache .#book
|
||||
# We can't just copy the symlink, we need to dereference it https://gitlab.com/gitlab-org/gitlab/-/issues/19746
|
||||
- cp -r --dereference result public
|
||||
artifacts:
|
||||
paths:
|
||||
- x86_64-unknown-linux-musl
|
||||
- aarch64-unknown-linux-musl
|
||||
- x86_64-unknown-linux-musl.deb
|
||||
- oci-image-amd64.tar.gz
|
||||
- oci-image-arm64v8.tar.gz
|
||||
- public
|
||||
rules:
|
||||
# CI required for all MRs
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
# Optional CI on forks
|
||||
- if: $IS_UPSTREAM_CI != "true"
|
||||
when: manual
|
||||
allow_failure: true
|
||||
- if: $CI
|
||||
interruptible: true
|
||||
|
||||
.push-oci-image:
|
||||
stage: publish
|
||||
image: docker:25.0.0
|
||||
services:
|
||||
- docker:25.0.0-dind
|
||||
variables:
|
||||
IMAGE_SUFFIX_AMD64: amd64
|
||||
IMAGE_SUFFIX_ARM64V8: arm64v8
|
||||
script:
|
||||
- docker load -i oci-image-amd64.tar.gz
|
||||
- IMAGE_ID_AMD64=$(docker images -q conduit:next)
|
||||
- docker load -i oci-image-arm64v8.tar.gz
|
||||
- IMAGE_ID_ARM64V8=$(docker images -q conduit:next)
|
||||
# Tag and push the architecture specific images
|
||||
- docker tag $IMAGE_ID_AMD64 $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_AMD64
|
||||
- docker tag $IMAGE_ID_ARM64V8 $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_ARM64V8
|
||||
- docker push $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_AMD64
|
||||
- docker push $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_ARM64V8
|
||||
# Tag the multi-arch image
|
||||
- docker manifest create $IMAGE_NAME:$CI_COMMIT_SHA --amend $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_AMD64 --amend $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_ARM64V8
|
||||
- docker manifest push $IMAGE_NAME:$CI_COMMIT_SHA
|
||||
# Tag and push the git ref
|
||||
- docker manifest create $IMAGE_NAME:$CI_COMMIT_REF_NAME --amend $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_AMD64 --amend $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_ARM64V8
|
||||
- docker manifest push $IMAGE_NAME:$CI_COMMIT_REF_NAME
|
||||
# Tag git tags as 'latest'
|
||||
- |
|
||||
if [[ -n "$CI_COMMIT_TAG" ]]; then
|
||||
docker manifest create $IMAGE_NAME:latest --amend $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_AMD64 --amend $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_ARM64V8
|
||||
docker manifest push $IMAGE_NAME:latest
|
||||
fi
|
||||
dependencies:
|
||||
- artifacts
|
||||
only:
|
||||
- next
|
||||
- master
|
||||
- tags
|
||||
|
||||
oci-image:push-gitlab:
|
||||
extends: .push-oci-image
|
||||
variables:
|
||||
IMAGE_NAME: $CI_REGISTRY_IMAGE/matrix-conduit
|
||||
before_script:
|
||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||
|
||||
oci-image:push-dockerhub:
|
||||
extends: .push-oci-image
|
||||
variables:
|
||||
IMAGE_NAME: matrixconduit/matrix-conduit
|
||||
before_script:
|
||||
- docker login -u $DOCKER_HUB_USER -p $DOCKER_HUB_PASSWORD
|
||||
|
||||
pages:
|
||||
stage: publish
|
||||
dependencies:
|
||||
- artifacts
|
||||
only:
|
||||
- next
|
||||
script:
|
||||
- "true"
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
|
|
3
.gitlab/route-map.yml
Normal file
3
.gitlab/route-map.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Docs: Map markdown to html files
|
||||
- source: /docs/(.+)\.md/
|
||||
public: '\1.html'
|
2073
Cargo.lock
generated
2073
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
116
Cargo.toml
116
Cargo.toml
|
@ -1,3 +1,14 @@
|
|||
# Keep alphabetically sorted
|
||||
[workspace.lints.rust]
|
||||
explicit_outlives_requirements = "warn"
|
||||
unused_qualifications = "warn"
|
||||
|
||||
# Keep alphabetically sorted
|
||||
[workspace.lints.clippy]
|
||||
cloned_instead_of_copied = "warn"
|
||||
dbg_macro = "warn"
|
||||
str_to_string = "warn"
|
||||
|
||||
[package]
|
||||
name = "conduit"
|
||||
description = "A Matrix homeserver written in Rust"
|
||||
|
@ -6,108 +17,123 @@ authors = ["timokoesters <timo@koesters.xyz>"]
|
|||
homepage = "https://conduit.rs"
|
||||
repository = "https://gitlab.com/famedly/conduit"
|
||||
readme = "README.md"
|
||||
version = "0.6.0-alpha"
|
||||
version = "0.7.0-alpha"
|
||||
edition = "2021"
|
||||
|
||||
# When changing this, make sure to update the `flake.lock` file by running
|
||||
# `nix flake update`. If you don't have Nix installed or otherwise don't know
|
||||
# how to do this, ping `@charles:computer.surgery` or `@dusk:gaze.systems` in
|
||||
# the matrix room.
|
||||
rust-version = "1.64.0"
|
||||
# See also `rust-toolchain.toml`
|
||||
rust-version = "1.75.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
# Web framework
|
||||
axum = { version = "0.5.17", default-features = false, features = ["form", "headers", "http1", "http2", "json", "matched-path"], optional = true }
|
||||
axum-server = { version = "0.4.0", features = ["tls-rustls"] }
|
||||
tower = { version = "0.4.8", features = ["util"] }
|
||||
tower-http = { version = "0.3.4", features = ["add-extension", "cors", "compression-full", "sensitive-headers", "trace", "util"] }
|
||||
axum = { version = "0.6.18", default-features = false, features = ["form", "headers", "http1", "http2", "json", "matched-path"], optional = true }
|
||||
axum-server = { version = "0.5.1", features = ["tls-rustls"] }
|
||||
tower = { version = "0.4.13", features = ["util"] }
|
||||
tower-http = { version = "0.4.1", features = ["add-extension", "cors", "sensitive-headers", "trace", "util"] }
|
||||
|
||||
# Used for matrix spec type definitions and helpers
|
||||
#ruma = { version = "0.4.0", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-pre-spec", "unstable-exhaustive-types"] }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "67d0f3cc04a8d1dc4a8a1ec947519967ce11ce26", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-exhaustive-types", "ring-compat", "unstable-unspecified" ] }
|
||||
#ruma = { git = "https://github.com/timokoesters/ruma", rev = "50c1db7e0a3a21fc794b0cce3b64285a4c750c71", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-pre-spec", "unstable-exhaustive-types"] }
|
||||
#ruma = { path = "../ruma/crates/ruma", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-pre-spec", "unstable-exhaustive-types"] }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "5495b85aa311c2805302edb0a7de40399e22b397", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-msc3575", "unstable-exhaustive-types", "ring-compat", "unstable-unspecified" ] }
|
||||
#ruma = { git = "https://github.com/timokoesters/ruma", rev = "4ec9c69bb7e09391add2382b3ebac97b6e8f4c64", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-msc3575", "unstable-exhaustive-types", "ring-compat", "unstable-unspecified" ] }
|
||||
#ruma = { path = "../ruma/crates/ruma", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-msc3575", "unstable-exhaustive-types", "ring-compat", "unstable-unspecified" ] }
|
||||
|
||||
# Async runtime and utilities
|
||||
tokio = { version = "1.11.0", features = ["fs", "macros", "signal", "sync"] }
|
||||
tokio = { version = "1.28.1", features = ["fs", "macros", "signal", "sync"] }
|
||||
# Used for storing data permanently
|
||||
#sled = { version = "0.34.7", features = ["compression", "no_metrics"], optional = true }
|
||||
#sled = { git = "https://github.com/spacejam/sled.git", rev = "e4640e0773595229f398438886f19bca6f7326a2", features = ["compression"] }
|
||||
persy = { version = "1.0.0", optional = true, features = ["background_ops"] }
|
||||
persy = { version = "1.4.4", optional = true, features = ["background_ops"] }
|
||||
|
||||
# Used for the http request / response body type for Ruma endpoints used with reqwest
|
||||
bytes = "1.1.0"
|
||||
http = "0.2.4"
|
||||
bytes = "1.4.0"
|
||||
http = "0.2.9"
|
||||
# Used to find data directory for default db path
|
||||
directories = "4.0.0"
|
||||
directories = "4.0.1"
|
||||
# Used for ruma wrapper
|
||||
serde_json = { version = "1.0.68", features = ["raw_value"] }
|
||||
serde_json = { version = "1.0.96", features = ["raw_value"] }
|
||||
# Used for appservice registration files
|
||||
serde_yaml = "0.9.13"
|
||||
serde_yaml = "0.9.21"
|
||||
# Used for pdu definition
|
||||
serde = { version = "1.0.130", features = ["rc"] }
|
||||
serde = { version = "1.0.163", features = ["rc"] }
|
||||
# Used for secure identifiers
|
||||
rand = "0.8.4"
|
||||
rand = "0.8.5"
|
||||
# Used to hash passwords
|
||||
rust-argon2 = "1.0.0"
|
||||
# Used to send requests
|
||||
reqwest = { default-features = false, features = ["rustls-tls-native-roots", "socks"], git = "https://github.com/timokoesters/reqwest", rev = "57b7cf4feb921573dfafad7d34b9ac6e44ead0bd" }
|
||||
hyper = "0.14.26"
|
||||
reqwest = { version = "0.11.18", default-features = false, features = ["rustls-tls-native-roots", "socks"] }
|
||||
# Used for conduit::Error type
|
||||
thiserror = "1.0.29"
|
||||
thiserror = "1.0.40"
|
||||
# Used to generate thumbnails for images
|
||||
image = { version = "0.24.4", default-features = false, features = ["jpeg", "png", "gif"] }
|
||||
image = { version = "0.24.6", default-features = false, features = ["jpeg", "png", "gif"] }
|
||||
# Used to encode server public key
|
||||
base64 = "0.13.0"
|
||||
base64 = "0.21.2"
|
||||
# Used when hashing the state
|
||||
ring = "0.16.20"
|
||||
ring = "0.17.7"
|
||||
# Used when querying the SRV record of other servers
|
||||
trust-dns-resolver = "0.22.0"
|
||||
# Used to find matching events for appservices
|
||||
regex = "1.5.4"
|
||||
regex = "1.8.1"
|
||||
# jwt jsonwebtokens
|
||||
jsonwebtoken = "8.1.1"
|
||||
jsonwebtoken = "9.2.0"
|
||||
# Performance measurements
|
||||
tracing = { version = "0.1.27", features = [] }
|
||||
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
||||
tracing = { version = "0.1.37", features = [] }
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
tracing-flame = "0.2.0"
|
||||
opentelemetry = { version = "0.18.0", features = ["rt-tokio"] }
|
||||
opentelemetry-jaeger = { version = "0.17.0", features = ["rt-tokio"] }
|
||||
tracing-opentelemetry = "0.18.0"
|
||||
lru-cache = "0.1.2"
|
||||
rusqlite = { version = "0.28.0", optional = true, features = ["bundled"] }
|
||||
rusqlite = { version = "0.29.0", optional = true, features = ["bundled"] }
|
||||
parking_lot = { version = "0.12.1", optional = true }
|
||||
crossbeam = { version = "0.8.1", optional = true }
|
||||
num_cpus = "1.13.0"
|
||||
# crossbeam = { version = "0.8.2", optional = true }
|
||||
num_cpus = "1.15.0"
|
||||
threadpool = "1.8.1"
|
||||
heed = { git = "https://github.com/timokoesters/heed.git", rev = "f6f825da7fb2c758867e05ad973ef800a6fe1d5d", optional = true }
|
||||
rocksdb = { version = "0.17.0", default-features = true, features = ["multi-threaded-cf", "zstd"], optional = true }
|
||||
# heed = { git = "https://github.com/timokoesters/heed.git", rev = "f6f825da7fb2c758867e05ad973ef800a6fe1d5d", optional = true }
|
||||
# Used for ruma wrapper
|
||||
serde_html_form = "0.2.0"
|
||||
|
||||
thread_local = "1.1.3"
|
||||
thread_local = "1.1.7"
|
||||
# used for TURN server authentication
|
||||
hmac = "0.12.1"
|
||||
sha-1 = "0.10.0"
|
||||
sha-1 = "0.10.1"
|
||||
sha2 = "0.9"
|
||||
# used for conduit's CLI and admin room command parsing
|
||||
clap = { version = "4.0.11", default-features = false, features = ["std", "derive", "help", "usage", "error-context"] }
|
||||
futures-util = { version = "0.3.17", default-features = false }
|
||||
clap = { version = "4.3.0", default-features = false, features = ["std", "derive", "help", "usage", "error-context", "string"] }
|
||||
futures-util = { version = "0.3.28", default-features = false }
|
||||
# Used for reading the configuration from conduit.toml & environment variables
|
||||
figment = { version = "0.10.6", features = ["env", "toml"] }
|
||||
figment = { version = "0.10.8", features = ["env", "toml"] }
|
||||
|
||||
tikv-jemalloc-ctl = { version = "0.5.0", features = ["use_std"], optional = true }
|
||||
tikv-jemallocator = { version = "0.5.0", features = ["unprefixed_malloc_on_supported_platforms"], optional = true }
|
||||
lazy_static = "1.4.0"
|
||||
async-trait = "0.1.57"
|
||||
async-trait = "0.1.68"
|
||||
|
||||
sd-notify = { version = "0.4.1", optional = true }
|
||||
|
||||
[dependencies.rocksdb]
|
||||
package = "rust-rocksdb"
|
||||
version = "0.22.7"
|
||||
optional = true
|
||||
features = [
|
||||
"multi-threaded-cf",
|
||||
"zstd",
|
||||
"lz4",
|
||||
]
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = { version = "0.28", features = ["resource"] }
|
||||
|
||||
[features]
|
||||
default = ["conduit_bin", "backend_sqlite", "backend_rocksdb", "jemalloc", "systemd"]
|
||||
default = ["conduit_bin", "backend_sqlite", "backend_rocksdb", "systemd"]
|
||||
#backend_sled = ["sled"]
|
||||
backend_persy = ["persy", "parking_lot"]
|
||||
backend_sqlite = ["sqlite"]
|
||||
backend_heed = ["heed", "crossbeam"]
|
||||
#backend_heed = ["heed", "crossbeam"]
|
||||
backend_rocksdb = ["rocksdb"]
|
||||
jemalloc = ["tikv-jemalloc-ctl", "tikv-jemallocator"]
|
||||
sqlite = ["rusqlite", "parking_lot", "tokio/signal"]
|
||||
|
@ -135,7 +161,7 @@ instead of a server that has high scalability."""
|
|||
section = "net"
|
||||
priority = "optional"
|
||||
assets = [
|
||||
["debian/README.Debian", "usr/share/doc/matrix-conduit/", "644"],
|
||||
["debian/README.md", "usr/share/doc/matrix-conduit/README.Debian", "644"],
|
||||
["README.md", "usr/share/doc/matrix-conduit/", "644"],
|
||||
["target/release/conduit", "usr/sbin/matrix-conduit", "755"],
|
||||
]
|
||||
|
|
23
Cross.toml
23
Cross.toml
|
@ -1,23 +0,0 @@
|
|||
[build.env]
|
||||
# CI uses an S3 endpoint to store sccache artifacts, so their config needs to
|
||||
# be available in the cross container as well
|
||||
passthrough = [
|
||||
"RUSTC_WRAPPER",
|
||||
"AWS_ACCESS_KEY_ID",
|
||||
"AWS_SECRET_ACCESS_KEY",
|
||||
"SCCACHE_BUCKET",
|
||||
"SCCACHE_ENDPOINT",
|
||||
"SCCACHE_S3_USE_SSL",
|
||||
]
|
||||
|
||||
[target.aarch64-unknown-linux-musl]
|
||||
image = "registry.gitlab.com/jfowl/conduit-containers/rust-cross-aarch64-unknown-linux-musl:latest"
|
||||
|
||||
[target.arm-unknown-linux-musleabihf]
|
||||
image = "registry.gitlab.com/jfowl/conduit-containers/rust-cross-arm-unknown-linux-musleabihf:latest"
|
||||
|
||||
[target.armv7-unknown-linux-musleabihf]
|
||||
image = "registry.gitlab.com/jfowl/conduit-containers/rust-cross-armv7-unknown-linux-musleabihf:latest"
|
||||
|
||||
[target.x86_64-unknown-linux-musl]
|
||||
image = "registry.gitlab.com/jfowl/conduit-containers/rust-cross-x86_64-unknown-linux-musl@sha256:b6d689e42f0236c8a38b961bca2a12086018b85ed20e0826310421daf182e2bb"
|
130
Dockerfile
130
Dockerfile
|
@ -1,130 +0,0 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
FROM docker.io/rust:1.64-bullseye AS builder
|
||||
WORKDIR /usr/src/conduit
|
||||
|
||||
# Install required packages to build Conduit and it's dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get -y --no-install-recommends install libclang-dev=1:11.0-51+nmu5
|
||||
|
||||
# == Build dependencies without our own code separately for caching ==
|
||||
#
|
||||
# Need a fake main.rs since Cargo refuses to build anything otherwise.
|
||||
#
|
||||
# See https://github.com/rust-lang/cargo/issues/2644 for a Cargo feature
|
||||
# request that would allow just dependencies to be compiled, presumably
|
||||
# regardless of whether source files are available.
|
||||
RUN mkdir src && touch src/lib.rs && echo 'fn main() {}' > src/main.rs
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
RUN cargo build --release && rm -r src
|
||||
|
||||
# Copy over actual Conduit sources
|
||||
COPY src src
|
||||
|
||||
# main.rs and lib.rs need their timestamp updated for this to work correctly since
|
||||
# otherwise the build with the fake main.rs from above is newer than the
|
||||
# source files (COPY preserves timestamps).
|
||||
#
|
||||
# Builds conduit and places the binary at /usr/src/conduit/target/release/conduit
|
||||
RUN touch src/main.rs && touch src/lib.rs && cargo build --release
|
||||
|
||||
|
||||
# ONLY USEFUL FOR CI: target stage to extract build artifacts
|
||||
FROM scratch AS builder-result
|
||||
COPY --from=builder /usr/src/conduit/target/release/conduit /conduit
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------------------------------------------
|
||||
# Build cargo-deb, a tool to package up rust binaries into .deb packages for Debian/Ubuntu based systems:
|
||||
# ---------------------------------------------------------------------------------------------------------------
|
||||
FROM docker.io/rust:1.64-bullseye AS build-cargo-deb
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
dpkg \
|
||||
dpkg-dev \
|
||||
liblzma-dev
|
||||
|
||||
RUN cargo install cargo-deb
|
||||
# => binary is in /usr/local/cargo/bin/cargo-deb
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------------------------------------------
|
||||
# Package conduit build-result into a .deb package:
|
||||
# ---------------------------------------------------------------------------------------------------------------
|
||||
FROM builder AS packager
|
||||
WORKDIR /usr/src/conduit
|
||||
|
||||
COPY ./LICENSE ./LICENSE
|
||||
COPY ./README.md ./README.md
|
||||
COPY debian/README.Debian ./debian/
|
||||
COPY --from=build-cargo-deb /usr/local/cargo/bin/cargo-deb /usr/local/cargo/bin/cargo-deb
|
||||
|
||||
# --no-build makes cargo-deb reuse already compiled project
|
||||
RUN cargo deb --no-build
|
||||
# => Package is in /usr/src/conduit/target/debian/<project_name>_<version>_<arch>.deb
|
||||
|
||||
|
||||
# ONLY USEFUL FOR CI: target stage to extract build artifacts
|
||||
FROM scratch AS packager-result
|
||||
COPY --from=packager /usr/src/conduit/target/debian/*.deb /conduit.deb
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------------------------------------------
|
||||
# Stuff below this line actually ends up in the resulting docker image
|
||||
# ---------------------------------------------------------------------------------------------------------------
|
||||
FROM docker.io/debian:bullseye-slim AS runner
|
||||
|
||||
# Standard port on which Conduit launches.
|
||||
# You still need to map the port when using the docker command or docker-compose.
|
||||
EXPOSE 6167
|
||||
|
||||
ARG DEFAULT_DB_PATH=/var/lib/matrix-conduit
|
||||
|
||||
ENV CONDUIT_PORT=6167 \
|
||||
CONDUIT_ADDRESS="0.0.0.0" \
|
||||
CONDUIT_DATABASE_PATH=${DEFAULT_DB_PATH} \
|
||||
CONDUIT_CONFIG=''
|
||||
# └─> Set no config file to do all configuration with env vars
|
||||
|
||||
# Conduit needs:
|
||||
# dpkg: to install conduit.deb
|
||||
# ca-certificates: for https
|
||||
# iproute2 & wget: for the healthcheck script
|
||||
RUN apt-get update && apt-get -y --no-install-recommends install \
|
||||
dpkg \
|
||||
ca-certificates \
|
||||
iproute2 \
|
||||
wget \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Test if Conduit is still alive, uses the same endpoint as Element
|
||||
COPY ./docker/healthcheck.sh /srv/conduit/healthcheck.sh
|
||||
HEALTHCHECK --start-period=5s --interval=5s CMD ./healthcheck.sh
|
||||
|
||||
# Install conduit.deb:
|
||||
COPY --from=packager /usr/src/conduit/target/debian/*.deb /srv/conduit/
|
||||
RUN dpkg -i /srv/conduit/*.deb
|
||||
|
||||
# Improve security: Don't run stuff as root, that does not need to run as root
|
||||
# Most distros also use 1000:1000 for the first real user, so this should resolve volume mounting problems.
|
||||
ARG USER_ID=1000
|
||||
ARG GROUP_ID=1000
|
||||
RUN set -x ; \
|
||||
groupadd -r -g ${GROUP_ID} conduit ; \
|
||||
useradd -l -r -M -d /srv/conduit -o -u ${USER_ID} -g conduit conduit && exit 0 ; exit 1
|
||||
|
||||
# Create database directory, change ownership of Conduit files to conduit user and group and make the healthcheck executable:
|
||||
RUN chown -cR conduit:conduit /srv/conduit && \
|
||||
chmod +x /srv/conduit/healthcheck.sh && \
|
||||
mkdir -p ${DEFAULT_DB_PATH} && \
|
||||
chown -cR conduit:conduit ${DEFAULT_DB_PATH}
|
||||
|
||||
# Change user to conduit, no root permissions afterwards:
|
||||
USER conduit
|
||||
# Set container home directory
|
||||
WORKDIR /srv/conduit
|
||||
|
||||
# Run Conduit and print backtraces on panics
|
||||
ENV RUST_BACKTRACE=1
|
||||
ENTRYPOINT [ "/usr/sbin/matrix-conduit" ]
|
63
README.md
63
README.md
|
@ -1,7 +1,15 @@
|
|||
# Conduit
|
||||
### A Matrix homeserver written in Rust
|
||||
|
||||
<!-- ANCHOR: catchphrase -->
|
||||
### A Matrix homeserver written in Rust
|
||||
<!-- ANCHOR_END: catchphrase -->
|
||||
|
||||
Please visit the [Conduit documentation](https://famedly.gitlab.io/conduit) for more information.
|
||||
Alternatively you can open [docs/introduction.md](docs/introduction.md) in this repository.
|
||||
|
||||
<!-- ANCHOR: body -->
|
||||
#### What is Matrix?
|
||||
|
||||
[Matrix](https://matrix.org) is an open network for secure and decentralized
|
||||
communication. Users from every Matrix homeserver can chat with users from all
|
||||
other Matrix servers. You can even use bridges (also called Matrix appservices)
|
||||
|
@ -15,11 +23,7 @@ friends or company.
|
|||
|
||||
#### Can I try it out?
|
||||
|
||||
Yes! You can test our Conduit instance by opening a Matrix client (<https://app.element.io> or Element Android for
|
||||
example) and registering on the `conduit.rs` homeserver.
|
||||
|
||||
*Registration is currently disabled because of scammers. For an account please
|
||||
message us (see contact section below).*
|
||||
Yes! You can test our Conduit instance by opening a client that supports registration tokens such as [Element web](https://app.element.io/), [Nheko](https://matrix.org/ecosystem/clients/nheko/) or [SchildiChat web](https://app.schildi.chat/) and registering on the `conduit.rs` homeserver. The registration token is "for_testing_only". Don't share personal information. Once you have registered, you can use any other [Matrix client](https://matrix.org/ecosystem/clients) to login.
|
||||
|
||||
Server hosting for conduit.rs is donated by the Matrix.org Foundation.
|
||||
|
||||
|
@ -33,27 +37,25 @@ There are still a few important features missing:
|
|||
|
||||
- E2EE emoji comparison over federation (E2EE chat works)
|
||||
- Outgoing read receipts, typing, presence over federation (incoming works)
|
||||
<!-- ANCHOR_END: body -->
|
||||
|
||||
Check out the [Conduit 1.0 Release Milestone](https://gitlab.com/famedly/conduit/-/milestones/3).
|
||||
|
||||
#### How can I deploy my own?
|
||||
|
||||
- Simple install (this was tested the most): [DEPLOY.md](DEPLOY.md)
|
||||
- Debian package: [debian/README.Debian](debian/README.Debian)
|
||||
- Nix/NixOS: [nix/README.md](nix/README.md)
|
||||
- Docker: [docker/README.md](docker/README.md)
|
||||
|
||||
If you want to connect an Appservice to Conduit, take a look at [APPSERVICES.md](APPSERVICES.md).
|
||||
|
||||
<!-- ANCHOR: footer -->
|
||||
#### How can I contribute?
|
||||
|
||||
1. Look for an issue you would like to work on and make sure it's not assigned
|
||||
to other users
|
||||
2. Ask someone to assign the issue to you (comment on the issue or chat in
|
||||
[#conduit:fachschaften.org](https://matrix.to/#/#conduit:fachschaften.org))
|
||||
3. Fork the repo and work on the issue.[#conduit:fachschaften.org](https://matrix.to/#/#conduit:fachschaften.org) is happy to help :)
|
||||
1. Look for an issue you would like to work on and make sure no one else is currently working on it.
|
||||
2. Tell us that you are working on the issue (comment on the issue or chat in
|
||||
[#conduit:fachschaften.org](https://matrix.to/#/#conduit:fachschaften.org)). If it is more complicated, please explain your approach and ask questions.
|
||||
3. Fork the repo, create a new branch and push commits.
|
||||
4. Submit a MR
|
||||
|
||||
#### Contact
|
||||
|
||||
If you have any questions, feel free to
|
||||
- Ask in `#conduit:fachschaften.org` on Matrix
|
||||
- Write an E-Mail to `conduit@koesters.xyz`
|
||||
- Send an direct message to `@timokoesters:fachschaften.org` on Matrix
|
||||
- [Open an issue on GitLab](https://gitlab.com/famedly/conduit/-/issues/new)
|
||||
|
||||
#### Thanks to
|
||||
|
||||
Thanks to FUTO, Famedly, Prototype Fund (DLR and German BMBF) and all individuals for financially supporting this project.
|
||||
|
@ -63,20 +65,13 @@ Thanks to the contributors to Conduit and all libraries we use, for example:
|
|||
- Ruma: A clean library for the Matrix Spec in Rust
|
||||
- axum: A modular web framework
|
||||
|
||||
#### Contact
|
||||
|
||||
If you run into any question, feel free to
|
||||
- Ask us in `#conduit:fachschaften.org` on Matrix
|
||||
- Write an E-Mail to `conduit@koesters.xyz`
|
||||
- Send an direct message to `timo@fachschaften.org` on Matrix
|
||||
- [Open an issue on GitLab](https://gitlab.com/famedly/conduit/-/issues/new)
|
||||
|
||||
#### Donate
|
||||
|
||||
Liberapay: <https://liberapay.com/timokoesters/>\
|
||||
Bitcoin: `bc1qnnykf986tw49ur7wx9rpw2tevpsztvar5x8w4n`
|
||||
- Liberapay: <https://liberapay.com/timokoesters/>
|
||||
- Bitcoin: `bc1qnnykf986tw49ur7wx9rpw2tevpsztvar5x8w4n`
|
||||
|
||||
#### Logo
|
||||
|
||||
Lightning Bolt Logo: https://github.com/mozilla/fxemoji/blob/gh-pages/svgs/nature/u26A1-bolt.svg \
|
||||
Logo License: https://github.com/mozilla/fxemoji/blob/gh-pages/LICENSE.md
|
||||
- Lightning Bolt Logo: <https://github.com/mozilla/fxemoji/blob/gh-pages/svgs/nature/u26A1-bolt.svg>
|
||||
- Logo License: <https://github.com/mozilla/fxemoji/blob/gh-pages/LICENSE.md>
|
||||
<!-- ANCHOR_END: footer -->
|
||||
|
|
37
bin/complement
Executable file
37
bin/complement
Executable file
|
@ -0,0 +1,37 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Path to Complement's source code
|
||||
COMPLEMENT_SRC="$1"
|
||||
|
||||
# A `.jsonl` file to write test logs to
|
||||
LOG_FILE="$2"
|
||||
|
||||
# A `.jsonl` file to write test results to
|
||||
RESULTS_FILE="$3"
|
||||
|
||||
OCI_IMAGE="complement-conduit:dev"
|
||||
|
||||
env \
|
||||
-C "$(git rev-parse --show-toplevel)" \
|
||||
docker build \
|
||||
--tag "$OCI_IMAGE" \
|
||||
--file complement/Dockerfile \
|
||||
.
|
||||
|
||||
# It's okay (likely, even) that `go test` exits nonzero
|
||||
set +o pipefail
|
||||
env \
|
||||
-C "$COMPLEMENT_SRC" \
|
||||
COMPLEMENT_BASE_IMAGE="$OCI_IMAGE" \
|
||||
go test -json ./tests | tee "$LOG_FILE"
|
||||
set -o pipefail
|
||||
|
||||
# Post-process the results into an easy-to-compare format
|
||||
cat "$LOG_FILE" | jq -c '
|
||||
select(
|
||||
(.Action == "pass" or .Action == "fail" or .Action == "skip")
|
||||
and .Test != null
|
||||
) | {Action: .Action, Test: .Test}
|
||||
' | sort > "$RESULTS_FILE"
|
26
bin/nix-build-and-cache
Executable file
26
bin/nix-build-and-cache
Executable file
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# The first argument must be the desired installable
|
||||
INSTALLABLE="$1"
|
||||
|
||||
# Build the installable and forward any other arguments too
|
||||
nix build "$@"
|
||||
|
||||
if [ ! -z ${ATTIC_TOKEN+x} ]; then
|
||||
nix run --inputs-from . attic -- \
|
||||
login \
|
||||
conduit \
|
||||
"${ATTIC_ENDPOINT:-https://nix.computer.surgery/conduit}" \
|
||||
"$ATTIC_TOKEN"
|
||||
|
||||
# Push the target installable and its build dependencies
|
||||
nix run --inputs-from . attic -- \
|
||||
push \
|
||||
conduit \
|
||||
"$(nix path-info "$INSTALLABLE" --derivation)" \
|
||||
"$(nix path-info "$INSTALLABLE")"
|
||||
else
|
||||
echo "\$ATTIC_TOKEN is unset, skipping uploading to the binary cache"
|
||||
fi
|
18
book.toml
Normal file
18
book.toml
Normal file
|
@ -0,0 +1,18 @@
|
|||
[book]
|
||||
title = "Conduit"
|
||||
description = "Conduit is a simple, fast and reliable chat server for the Matrix protocol"
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "docs"
|
||||
|
||||
[build]
|
||||
build-dir = "public"
|
||||
create-missing = true
|
||||
|
||||
[output.html]
|
||||
git-repository-url = "https://gitlab.com/famedly/conduit"
|
||||
edit-url-template = "https://gitlab.com/famedly/conduit/-/edit/next/{path}"
|
||||
git-repository-icon = "fa-git-square"
|
||||
|
||||
[output.html.search]
|
||||
limit-results = 15
|
|
@ -1,26 +1,30 @@
|
|||
# For use in our CI only. This requires a build artifact created by a previous run pipline stage to be placed in cached_target/release/conduit
|
||||
FROM registry.gitlab.com/jfowl/conduit-containers/rust-with-tools:commit-16a08e9b as builder
|
||||
#FROM rust:latest as builder
|
||||
FROM rust:1.75.0
|
||||
|
||||
WORKDIR /workdir
|
||||
|
||||
ARG RUSTC_WRAPPER
|
||||
ARG AWS_ACCESS_KEY_ID
|
||||
ARG AWS_SECRET_ACCESS_KEY
|
||||
ARG SCCACHE_BUCKET
|
||||
ARG SCCACHE_ENDPOINT
|
||||
ARG SCCACHE_S3_USE_SSL
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libclang-dev
|
||||
|
||||
COPY . .
|
||||
RUN mkdir -p target/release
|
||||
RUN test -e cached_target/release/conduit && cp cached_target/release/conduit target/release/conduit || cargo build --release
|
||||
|
||||
## Actual image
|
||||
FROM debian:bullseye
|
||||
WORKDIR /workdir
|
||||
COPY Cargo.toml Cargo.toml
|
||||
COPY Cargo.lock Cargo.lock
|
||||
COPY src src
|
||||
RUN cargo build --release \
|
||||
&& mv target/release/conduit conduit \
|
||||
&& rm -rf target
|
||||
|
||||
# Install caddy
|
||||
RUN apt-get update && apt-get install -y debian-keyring debian-archive-keyring apt-transport-https curl && curl -1sLf 'https://dl.cloudsmith.io/public/caddy/testing/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-testing-archive-keyring.gpg && curl -1sLf 'https://dl.cloudsmith.io/public/caddy/testing/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-testing.list && apt-get update && apt-get install -y caddy
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y \
|
||||
debian-keyring \
|
||||
debian-archive-keyring \
|
||||
apt-transport-https \
|
||||
curl \
|
||||
&& curl -1sLf 'https://dl.cloudsmith.io/public/caddy/testing/gpg.key' \
|
||||
| gpg --dearmor -o /usr/share/keyrings/caddy-testing-archive-keyring.gpg \
|
||||
&& curl -1sLf 'https://dl.cloudsmith.io/public/caddy/testing/debian.deb.txt' \
|
||||
| tee /etc/apt/sources.list.d/caddy-testing.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y caddy
|
||||
|
||||
COPY conduit-example.toml conduit.toml
|
||||
COPY complement/caddy.json caddy.json
|
||||
|
@ -29,15 +33,9 @@ ENV SERVER_NAME=localhost
|
|||
ENV CONDUIT_CONFIG=/workdir/conduit.toml
|
||||
|
||||
RUN sed -i "s/port = 6167/port = 8008/g" conduit.toml
|
||||
RUN echo "allow_federation = true" >> conduit.toml
|
||||
RUN echo "allow_encryption = true" >> conduit.toml
|
||||
RUN echo "allow_registration = true" >> conduit.toml
|
||||
RUN echo "log = \"warn,_=off,sled=off\"" >> conduit.toml
|
||||
RUN sed -i "s/address = \"127.0.0.1\"/address = \"0.0.0.0\"/g" conduit.toml
|
||||
|
||||
COPY --from=builder /workdir/target/release/conduit /workdir/conduit
|
||||
RUN chmod +x /workdir/conduit
|
||||
|
||||
EXPOSE 8008 8448
|
||||
|
||||
CMD uname -a && \
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
# Running Conduit on Complement
|
||||
# Complement
|
||||
|
||||
This assumes that you're familiar with complement, if not, please readme
|
||||
[their readme](https://github.com/matrix-org/complement#running).
|
||||
## What's that?
|
||||
|
||||
Complement works with "base images", this directory (and Dockerfile) helps build the conduit complement-ready docker
|
||||
image.
|
||||
Have a look at [its repository](https://github.com/matrix-org/complement).
|
||||
|
||||
To build, `cd` to the base directory of the workspace, and run this:
|
||||
## How do I use it with Conduit?
|
||||
|
||||
`docker build -t complement-conduit:dev -f complement/Dockerfile .`
|
||||
|
||||
Then use `complement-conduit:dev` as a base image for running complement tests.
|
||||
The script at [`../bin/complement`](../bin/complement) has automation for this.
|
||||
It takes a few command line arguments, you can read the script to find out what
|
||||
those are.
|
||||
|
|
|
@ -39,6 +39,7 @@ max_request_size = 20_000_000 # in bytes
|
|||
allow_registration = true
|
||||
|
||||
allow_federation = true
|
||||
allow_check_for_updates = true
|
||||
|
||||
# Enable the display name lightning bolt on registration.
|
||||
enable_lightning_bolt = true
|
||||
|
@ -50,7 +51,11 @@ enable_lightning_bolt = true
|
|||
trusted_servers = ["matrix.org"]
|
||||
|
||||
#max_concurrent_requests = 100 # How many requests Conduit sends to other servers at the same time
|
||||
#log = "warn,state_res=warn,rocket=off,_=off,sled=off"
|
||||
|
||||
# Controls the log verbosity. See also [here][0].
|
||||
#
|
||||
# [0]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
|
||||
#log = "..."
|
||||
|
||||
address = "127.0.0.1" # This makes sure Conduit can only be reached using the reverse proxy
|
||||
#address = "0.0.0.0" # If Conduit is running in a container, make sure the reverse proxy (ie. Traefik) can reach it.
|
||||
|
|
18
debian/README.Debian → debian/README.md
vendored
18
debian/README.Debian → debian/README.md
vendored
|
@ -1,28 +1,36 @@
|
|||
Conduit for Debian
|
||||
==================
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Information about downloading, building and deploying the Debian package, see
|
||||
the "Installing Conduit" section in the Deploying docs.
|
||||
All following sections until "Setting up the Reverse Proxy" be ignored because
|
||||
this is handled automatically by the packaging.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
When installed, Debconf generates the configuration of the homeserver
|
||||
(host)name, the address and port it listens on. This configuration ends up in
|
||||
/etc/matrix-conduit/conduit.toml.
|
||||
`/etc/matrix-conduit/conduit.toml`.
|
||||
|
||||
You can tweak more detailed settings by uncommenting and setting the variables
|
||||
in /etc/matrix-conduit/conduit.toml. This involves settings such as the maximum
|
||||
in `/etc/matrix-conduit/conduit.toml`. This involves settings such as the maximum
|
||||
file size for download/upload, enabling federation, etc.
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
The package uses the matrix-conduit.service systemd unit file to start and
|
||||
The package uses the `matrix-conduit.service` systemd unit file to start and
|
||||
stop Conduit. It loads the configuration file mentioned above to set up the
|
||||
environment before running the server.
|
||||
|
||||
This package assumes by default that Conduit will be placed behind a reverse
|
||||
proxy such as Apache or nginx. This default deployment entails just listening
|
||||
on 127.0.0.1 and the free port 6167 and is reachable via a client using the URL
|
||||
http://localhost:6167.
|
||||
on `127.0.0.1` and the free port `6167` and is reachable via a client using the URL
|
||||
<http://localhost:6167>.
|
||||
|
||||
At a later stage this packaging may support also setting up TLS and running
|
||||
stand-alone. In this case, however, you need to set up some certificates and
|
12
debian/postinst
vendored
12
debian/postinst
vendored
|
@ -19,11 +19,11 @@ case "$1" in
|
|||
_matrix-conduit
|
||||
fi
|
||||
|
||||
# Create the database path if it does not exist yet.
|
||||
if [ ! -d "$CONDUIT_DATABASE_PATH" ]; then
|
||||
mkdir -p "$CONDUIT_DATABASE_PATH"
|
||||
chown _matrix-conduit "$CONDUIT_DATABASE_PATH"
|
||||
fi
|
||||
# Create the database path if it does not exist yet and fix up ownership
|
||||
# and permissions.
|
||||
mkdir -p "$CONDUIT_DATABASE_PATH"
|
||||
chown _matrix-conduit "$CONDUIT_DATABASE_PATH"
|
||||
chmod 700 "$CONDUIT_DATABASE_PATH"
|
||||
|
||||
if [ ! -e "$CONDUIT_CONFIG_FILE" ]; then
|
||||
# Write the debconf values in the config.
|
||||
|
@ -73,11 +73,11 @@ max_request_size = 20_000_000 # in bytes
|
|||
allow_registration = true
|
||||
|
||||
allow_federation = true
|
||||
allow_check_for_updates = true
|
||||
|
||||
trusted_servers = ["matrix.org"]
|
||||
|
||||
#max_concurrent_requests = 100 # How many requests Conduit sends to other servers at the same time
|
||||
#log = "warn,state_res=warn,rocket=off,_=off,sled=off"
|
||||
EOF
|
||||
fi
|
||||
;;
|
||||
|
|
10
default.nix
Normal file
10
default.nix
Normal file
|
@ -0,0 +1,10 @@
|
|||
(import
|
||||
(
|
||||
let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in
|
||||
fetchTarball {
|
||||
url = lock.nodes.flake-compat.locked.url or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
|
||||
sha256 = lock.nodes.flake-compat.locked.narHash;
|
||||
}
|
||||
)
|
||||
{ src = ./.; }
|
||||
).defaultNix
|
12
docs/SUMMARY.md
Normal file
12
docs/SUMMARY.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Summary
|
||||
|
||||
- [Introduction](introduction.md)
|
||||
|
||||
- [Example configuration](configuration.md)
|
||||
- [Deploying](deploying.md)
|
||||
- [Generic](deploying/generic.md)
|
||||
- [Debian](deploying/debian.md)
|
||||
- [Docker](deploying/docker.md)
|
||||
- [NixOS](deploying/nixos.md)
|
||||
- [TURN](turn.md)
|
||||
- [Appservices](appservices.md)
|
5
docs/configuration.md
Normal file
5
docs/configuration.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Example configuration
|
||||
|
||||
``` toml
|
||||
{{#include ../conduit-example.toml}}
|
||||
```
|
3
docs/deploying.md
Normal file
3
docs/deploying.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Deploying
|
||||
|
||||
This chapter describes various ways to deploy Conduit.
|
1
docs/deploying/debian.md
Normal file
1
docs/deploying/debian.md
Normal file
|
@ -0,0 +1 @@
|
|||
{{#include ../../debian/README.md}}
|
|
@ -29,9 +29,9 @@ services:
|
|||
CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
CONDUIT_ALLOW_REGISTRATION: 'true'
|
||||
CONDUIT_ALLOW_FEDERATION: 'true'
|
||||
CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
#CONDUIT_MAX_CONCURRENT_REQUESTS: 100
|
||||
#CONDUIT_LOG: warn,rocket=off,_=off,sled=off
|
||||
CONDUIT_ADDRESS: 0.0.0.0
|
||||
CONDUIT_CONFIG: '' # Ignore this
|
||||
|
|
@ -33,10 +33,10 @@ services:
|
|||
# CONDUIT_PORT: 6167
|
||||
# CONDUIT_CONFIG: '/srv/conduit/conduit.toml' # if you want to configure purely by env vars, set this to an empty string ''
|
||||
# Available levels are: error, warn, info, debug, trace - more info at: https://docs.rs/env_logger/*/env_logger/#enabling-logging
|
||||
# CONDUIT_LOG: info # default is: "warn,_=off,sled=off"
|
||||
# CONDUIT_ALLOW_JAEGER: 'false'
|
||||
# CONDUIT_ALLOW_ENCRYPTION: 'false'
|
||||
# CONDUIT_ALLOW_FEDERATION: 'false'
|
||||
# CONDUIT_ALLOW_ENCRYPTION: 'true'
|
||||
# CONDUIT_ALLOW_FEDERATION: 'true'
|
||||
# CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
# CONDUIT_DATABASE_PATH: /srv/conduit/.local/share/conduit
|
||||
# CONDUIT_WORKERS: 10
|
||||
# CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
|
@ -29,9 +29,9 @@ services:
|
|||
CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
CONDUIT_ALLOW_REGISTRATION: 'true'
|
||||
CONDUIT_ALLOW_FEDERATION: 'true'
|
||||
CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
#CONDUIT_MAX_CONCURRENT_REQUESTS: 100
|
||||
#CONDUIT_LOG: warn,rocket=off,_=off,sled=off
|
||||
CONDUIT_ADDRESS: 0.0.0.0
|
||||
CONDUIT_CONFIG: '' # Ignore this
|
||||
#
|
|
@ -1,10 +1,39 @@
|
|||
# Deploy using Docker
|
||||
# Conduit for Docker
|
||||
|
||||
> **Note:** To run and use Conduit you should probably use it with a Domain or Subdomain behind a reverse proxy (like Nginx, Traefik, Apache, ...) with a Lets Encrypt certificate.
|
||||
|
||||
## Docker
|
||||
|
||||
### Build & Dockerfile
|
||||
To run Conduit with Docker you can either build the image yourself or pull it from a registry.
|
||||
|
||||
|
||||
### Use a registry
|
||||
|
||||
OCI images for Conduit are available in the registries listed below. We recommend using the image tagged as `latest` from GitLab's own registry.
|
||||
|
||||
| Registry | Image | Size | Notes |
|
||||
| --------------- | --------------------------------------------------------------- | ----------------------------- | ---------------------- |
|
||||
| GitLab Registry | [registry.gitlab.com/famedly/conduit/matrix-conduit:latest][gl] | ![Image Size][shield-latest] | Stable image. |
|
||||
| Docker Hub | [docker.io/matrixconduit/matrix-conduit:latest][dh] | ![Image Size][shield-latest] | Stable image. |
|
||||
| GitLab Registry | [registry.gitlab.com/famedly/conduit/matrix-conduit:next][gl] | ![Image Size][shield-next] | Development version. |
|
||||
| Docker Hub | [docker.io/matrixconduit/matrix-conduit:next][dh] | ![Image Size][shield-next] | Development version. |
|
||||
|
||||
|
||||
[dh]: https://hub.docker.com/r/matrixconduit/matrix-conduit
|
||||
[gl]: https://gitlab.com/famedly/conduit/container_registry/2497937
|
||||
[shield-latest]: https://img.shields.io/docker/image-size/matrixconduit/matrix-conduit/latest
|
||||
[shield-next]: https://img.shields.io/docker/image-size/matrixconduit/matrix-conduit/next
|
||||
|
||||
|
||||
Use
|
||||
```bash
|
||||
docker image pull <link>
|
||||
```
|
||||
to pull it to your machine.
|
||||
|
||||
|
||||
|
||||
### Build using a dockerfile
|
||||
|
||||
The Dockerfile provided by Conduit has two stages, each of which creates an image.
|
||||
|
||||
|
@ -19,9 +48,11 @@ docker build --tag matrixconduit/matrix-conduit:latest .
|
|||
|
||||
which also will tag the resulting image as `matrixconduit/matrix-conduit:latest`.
|
||||
|
||||
|
||||
|
||||
### Run
|
||||
|
||||
After building the image you can simply run it with
|
||||
When you have the image you can simply run it with
|
||||
|
||||
```bash
|
||||
docker run -d -p 8448:6167 \
|
||||
|
@ -33,28 +64,18 @@ docker run -d -p 8448:6167 \
|
|||
-e CONDUIT_MAX_REQUEST_SIZE="20_000_000" \
|
||||
-e CONDUIT_TRUSTED_SERVERS="[\"matrix.org\"]" \
|
||||
-e CONDUIT_MAX_CONCURRENT_REQUESTS="100" \
|
||||
-e CONDUIT_LOG="warn,rocket=off,_=off,sled=off" \
|
||||
--name conduit matrixconduit/matrix-conduit:latest
|
||||
--name conduit <link>
|
||||
```
|
||||
|
||||
or you can skip the build step and pull the image from one of the following registries:
|
||||
or you can use [docker-compose](#docker-compose).
|
||||
|
||||
| Registry | Image | Size |
|
||||
| --------------- | --------------------------------------------------------------- | --------------------- |
|
||||
| Docker Hub | [matrixconduit/matrix-conduit:latest][dh] | ![Image Size][shield] |
|
||||
| GitLab Registry | [registry.gitlab.com/famedly/conduit/matrix-conduit:latest][gl] | ![Image Size][shield] |
|
||||
|
||||
[dh]: https://hub.docker.com/r/matrixconduit/matrix-conduit
|
||||
[gl]: https://gitlab.com/famedly/conduit/container_registry/2497937
|
||||
[shield]: https://img.shields.io/docker/image-size/matrixconduit/matrix-conduit/latest
|
||||
|
||||
The `-d` flag lets the container run in detached mode. You now need to supply a `conduit.toml` config file, an example can be found [here](../conduit-example.toml).
|
||||
The `-d` flag lets the container run in detached mode. You now need to supply a `conduit.toml` config file, an example can be found [here](../configuration.md).
|
||||
You can pass in different env vars to change config values on the fly. You can even configure Conduit completely by using env vars, but for that you need
|
||||
to pass `-e CONDUIT_CONFIG=""` into your container. For an overview of possible values, please take a look at the `docker-compose.yml` file.
|
||||
|
||||
If you just want to test Conduit for a short time, you can use the `--rm` flag, which will clean up everything related to your container after you stop it.
|
||||
|
||||
## Docker-compose
|
||||
### Docker-compose
|
||||
|
||||
If the `docker run` command is not for you or your setup, you can also use one of the provided `docker-compose` files.
|
||||
|
||||
|
@ -66,8 +87,7 @@ Depending on your proxy setup, you can use one of the following files;
|
|||
When picking the traefik-related compose file, rename it so it matches `docker-compose.yml`, and
|
||||
rename the override file to `docker-compose.override.yml`. Edit the latter with the values you want
|
||||
for your server.
|
||||
|
||||
Additional info about deploying Conduit can be found [here](../DEPLOY.md).
|
||||
Additional info about deploying Conduit can be found [here](generic.md).
|
||||
|
||||
### Build
|
||||
|
||||
|
@ -95,7 +115,7 @@ As a container user, you probably know about Traefik. It is a easy to use revers
|
|||
containerized app and services available through the web. With the two provided files,
|
||||
[`docker-compose.for-traefik.yml`](docker-compose.for-traefik.yml) (or
|
||||
[`docker-compose.with-traefik.yml`](docker-compose.with-traefik.yml)) and
|
||||
[`docker-compose.override.yml`](docker-compose.override.traefik.yml), it is equally easy to deploy
|
||||
[`docker-compose.override.yml`](docker-compose.override.yml), it is equally easy to deploy
|
||||
and use Conduit, with a little caveat. If you already took a look at the files, then you should have
|
||||
seen the `well-known` service, and that is the little caveat. Traefik is simply a proxy and
|
||||
loadbalancer and is not able to serve any kind of content, but for Conduit to federate, we need to
|
||||
|
@ -106,9 +126,10 @@ With the service `well-known` we use a single `nginx` container that will serve
|
|||
|
||||
So...step by step:
|
||||
|
||||
1. Copy [`docker-compose.traefik.yml`](docker-compose.traefik.yml) and [`docker-compose.override.traefik.yml`](docker-compose.override.traefik.yml) from the repository and remove `.traefik` from the filenames.
|
||||
1. Copy [`docker-compose.for-traefik.yml`](docker-compose.for-traefik.yml) (or
|
||||
[`docker-compose.with-traefik.yml`](docker-compose.with-traefik.yml)) and [`docker-compose.override.yml`](docker-compose.override.yml) from the repository and remove `.for-traefik` (or `.with-traefik`) from the filename.
|
||||
2. Open both files and modify/adjust them to your needs. Meaning, change the `CONDUIT_SERVER_NAME` and the volume host mappings according to your needs.
|
||||
3. Create the `conduit.toml` config file, an example can be found [here](../conduit-example.toml), or set `CONDUIT_CONFIG=""` and configure Conduit per env vars.
|
||||
3. Create the `conduit.toml` config file, an example can be found [here](../configuration.md), or set `CONDUIT_CONFIG=""` and configure Conduit per env vars.
|
||||
4. Uncomment the `element-web` service if you want to host your own Element Web Client and create a `element_config.json`.
|
||||
5. Create the files needed by the `well-known` service.
|
||||
|
||||
|
@ -138,3 +159,58 @@ So...step by step:
|
|||
|
||||
6. Run `docker-compose up -d`
|
||||
7. Connect to your homeserver with your preferred client and create a user. You should do this immediately after starting Conduit, because the first created user is the admin.
|
||||
|
||||
|
||||
|
||||
|
||||
## Voice communication
|
||||
|
||||
In order to make or receive calls, a TURN server is required. Conduit suggests using [Coturn](https://github.com/coturn/coturn) for this purpose, which is also available as a Docker image. Before proceeding with the software installation, it is essential to have the necessary configurations in place.
|
||||
|
||||
### Configuration
|
||||
|
||||
Create a configuration file called `coturn.conf` containing:
|
||||
|
||||
```conf
|
||||
use-auth-secret
|
||||
static-auth-secret=<a secret key>
|
||||
realm=<your server domain>
|
||||
```
|
||||
A common way to generate a suitable alphanumeric secret key is by using `pwgen -s 64 1`.
|
||||
|
||||
These same values need to be set in conduit. You can either modify conduit.toml to include these lines:
|
||||
```
|
||||
turn_uris = ["turn:<your server domain>?transport=udp", "turn:<your server domain>?transport=tcp"]
|
||||
turn_secret = "<secret key from coturn configuration>"
|
||||
```
|
||||
or append the following to the docker environment variables dependig on which configuration method you used earlier:
|
||||
```yml
|
||||
CONDUIT_TURN_URIS: '["turn:<your server domain>?transport=udp", "turn:<your server domain>?transport=tcp"]'
|
||||
CONDUIT_TURN_SECRET: "<secret key from coturn configuration>"
|
||||
```
|
||||
Restart Conduit to apply these changes.
|
||||
|
||||
### Run
|
||||
Run the [Coturn](https://hub.docker.com/r/coturn/coturn) image using
|
||||
```bash
|
||||
docker run -d --network=host -v $(pwd)/coturn.conf:/etc/coturn/turnserver.conf coturn/coturn
|
||||
```
|
||||
|
||||
or docker-compose. For the latter, paste the following section into a file called `docker-compose.yml`
|
||||
and run `docker-compose up -d` in the same directory.
|
||||
|
||||
```yml
|
||||
version: 3
|
||||
services:
|
||||
turn:
|
||||
container_name: coturn-server
|
||||
image: docker.io/coturn/coturn
|
||||
restart: unless-stopped
|
||||
network_mode: "host"
|
||||
volumes:
|
||||
- ./coturn.conf:/etc/coturn/turnserver.conf
|
||||
```
|
||||
|
||||
To understand why the host networking mode is used and explore alternative configuration options, please visit the following link: https://github.com/coturn/coturn/blob/master/docker/coturn/README.md.
|
||||
For security recommendations see Synapse's [Coturn documentation](https://github.com/matrix-org/synapse/blob/develop/docs/setup/turn/coturn.md#configuration).
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# Deploying Conduit
|
||||
# Generic deployment documentation
|
||||
|
||||
> ## Getting help
|
||||
>
|
||||
|
@ -7,77 +7,67 @@
|
|||
|
||||
## Installing Conduit
|
||||
|
||||
Although you might be able to compile Conduit for Windows, we do recommend running it on a linux server. We therefore
|
||||
Although you might be able to compile Conduit for Windows, we do recommend running it on a Linux server. We therefore
|
||||
only offer Linux binaries.
|
||||
|
||||
You may simply download the binary that fits your machine. Run `uname -m` to see what you need. Now copy the right url:
|
||||
You may simply download the binary that fits your machine. Run `uname -m` to see what you need. Now copy the appropriate url:
|
||||
|
||||
| CPU Architecture | Download stable version | Download development version |
|
||||
| ------------------------------------------- | --------------------------------------------------------------- | ----------------------------------------------------------- |
|
||||
| x84_64 / amd64 (Most servers and computers) | [Binary][x84_64-glibc-master] / [.deb][x84_64-glibc-master-deb] | [Binary][x84_64-glibc-next] / [.deb][x84_64-glibc-next-deb] |
|
||||
| armv7 (e.g. Raspberry Pi by default) | [Binary][armv7-glibc-master] / [.deb][armv7-glibc-master-deb] | [Binary][armv7-glibc-next] / [.deb][armv7-glibc-next-deb] |
|
||||
| armv8 / aarch64 | [Binary][armv8-glibc-master] / [.deb][armv8-glibc-master-deb] | [Binary][armv8-glibc-next] / [.deb][armv8-glibc-next-deb] |
|
||||
**Stable versions:**
|
||||
|
||||
| CPU Architecture | Download stable version |
|
||||
| ------------------------------------------- | --------------------------------------------------------------- |
|
||||
| x84_64 / amd64 (Most servers and computers) | [Binary][x84_64-glibc-master] / [.deb][x84_64-glibc-master-deb] |
|
||||
| armv7 (e.g. Raspberry Pi by default) | [Binary][armv7-glibc-master] / [.deb][armv7-glibc-master-deb] |
|
||||
| armv8 / aarch64 | [Binary][armv8-glibc-master] / [.deb][armv8-glibc-master-deb] |
|
||||
|
||||
These builds were created on and linked against the glibc version shipped with Debian bullseye.
|
||||
If you use a system with an older glibc version, you might need to compile Conduit yourself.
|
||||
If you use a system with an older glibc version (e.g. RHEL8), you might need to compile Conduit yourself.
|
||||
|
||||
[x84_64-glibc-master]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_amd64/conduit?job=docker:master
|
||||
[armv7-glibc-master]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_arm_v7/conduit?job=docker:master
|
||||
[armv8-glibc-master]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_arm64/conduit?job=docker:master
|
||||
[x84_64-glibc-next]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/next/raw/build-output/linux_amd64/conduit?job=docker:next
|
||||
[armv7-glibc-next]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/next/raw/build-output/linux_arm_v7/conduit?job=docker:next
|
||||
[armv8-glibc-next]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/next/raw/build-output/linux_arm64/conduit?job=docker:next
|
||||
[x84_64-glibc-master-deb]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_amd64/conduit.deb?job=docker:master
|
||||
[armv7-glibc-master-deb]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_arm_v7/conduit.deb?job=docker:master
|
||||
[armv8-glibc-master-deb]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_arm64/conduit.deb?job=docker:master
|
||||
[x84_64-glibc-next-deb]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/next/raw/build-output/linux_amd64/conduit.deb?job=docker:next
|
||||
[armv7-glibc-next-deb]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/next/raw/build-output/linux_arm_v7/conduit.deb?job=docker:next
|
||||
[armv8-glibc-next-deb]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/next/raw/build-output/linux_arm64/conduit.deb?job=docker:next
|
||||
|
||||
**Latest versions:**
|
||||
|
||||
| Target | Type | Download |
|
||||
|-|-|-|
|
||||
| `x86_64-unknown-linux-musl` | Statically linked Debian package | [link](https://gitlab.com/api/v4/projects/famedly%2Fconduit/jobs/artifacts/next/raw/x86_64-unknown-linux-musl.deb?job=artifacts) |
|
||||
| `x86_64-unknown-linux-musl` | Statically linked binary | [link](https://gitlab.com/api/v4/projects/famedly%2Fconduit/jobs/artifacts/next/raw/x86_64-unknown-linux-musl?job=artifacts) |
|
||||
| `aarch64-unknown-linux-musl` | Statically linked binary | [link](https://gitlab.com/api/v4/projects/famedly%2Fconduit/jobs/artifacts/next/raw/aarch64-unknown-linux-musl?job=artifacts) |
|
||||
| `x86_64-unknown-linux-gnu` | OCI image | [link](https://gitlab.com/api/v4/projects/famedly%2Fconduit/jobs/artifacts/next/raw/oci-image-amd64.tar.gz?job=artifacts) |
|
||||
| `aarch64-unknown-linux-musl` | OCI image | [link](https://gitlab.com/api/v4/projects/famedly%2Fconduit/jobs/artifacts/next/raw/oci-image-arm64v8.tar.gz?job=artifacts) |
|
||||
|
||||
```bash
|
||||
$ sudo wget -O /usr/local/bin/matrix-conduit <url>
|
||||
$ sudo chmod +x /usr/local/bin/matrix-conduit
|
||||
```
|
||||
|
||||
Alternatively, you may compile the binary yourself
|
||||
Alternatively, you may compile the binary yourself. First, install any dependencies:
|
||||
|
||||
```bash
|
||||
# Debian
|
||||
$ sudo apt install libclang-dev build-essential
|
||||
```
|
||||
|
||||
# RHEL
|
||||
$ sudo dnf install clang
|
||||
```
|
||||
Then, `cd` into the source tree of conduit-next and run:
|
||||
```bash
|
||||
$ cargo build --release
|
||||
```
|
||||
|
||||
If you want to cross compile Conduit to another architecture, read the guide below.
|
||||
|
||||
<details>
|
||||
<summary>Cross compilation</summary>
|
||||
|
||||
As easiest way to compile conduit for another platform [cross-rs](https://github.com/cross-rs/cross) is recommended, so install it first.
|
||||
|
||||
In order to use RockDB as storage backend append `-latomic` to linker flags.
|
||||
|
||||
For example, to build a binary for Raspberry Pi Zero W (ARMv6) you need `arm-unknown-linux-gnueabihf` as compilation
|
||||
target.
|
||||
|
||||
```bash
|
||||
git clone https://gitlab.com/famedly/conduit.git
|
||||
cd conduit
|
||||
export RUSTFLAGS='-C link-arg=-lgcc -Clink-arg=-latomic -Clink-arg=-static-libgcc'
|
||||
cross build --release --no-default-features --features conduit_bin,backend_rocksdb,jemalloc --target=arm-unknown-linux-gnueabihf
|
||||
```
|
||||
</details>
|
||||
|
||||
## Adding a Conduit user
|
||||
|
||||
While Conduit can run as any user it is usually better to use dedicated users for different services. This also allows
|
||||
you to make sure that the file permissions are correctly set up.
|
||||
|
||||
In Debian you can use this command to create a Conduit user:
|
||||
In Debian or RHEL, you can use this command to create a Conduit user:
|
||||
|
||||
```bash
|
||||
sudo adduser --system conduit --no-create-home
|
||||
sudo adduser --system conduit --group --disabled-login --no-create-home
|
||||
```
|
||||
|
||||
## Forwarding ports in the firewall or the router
|
||||
|
@ -86,6 +76,19 @@ Conduit uses the ports 443 and 8448 both of which need to be open in the firewal
|
|||
|
||||
If Conduit runs behind a router or in a container and has a different public IP address than the host system these public ports need to be forwarded directly or indirectly to the port mentioned in the config.
|
||||
|
||||
## Optional: Avoid port 8448
|
||||
|
||||
If Conduit runs behind Cloudflare reverse proxy, which doesn't support port 8448 on free plans, [delegation](https://matrix-org.github.io/synapse/latest/delegate.html) can be set up to have federation traffic routed to port 443:
|
||||
```apache
|
||||
# .well-known delegation on Apache
|
||||
<Files "/.well-known/matrix/server">
|
||||
ErrorDocument 200 '{"m.server": "your.server.name:443"}'
|
||||
Header always set Content-Type application/json
|
||||
Header always set Access-Control-Allow-Origin *
|
||||
</Files>
|
||||
```
|
||||
[SRV DNS record](https://spec.matrix.org/latest/server-server-api/#resolving-server-names) delegation is also [possible](https://www.cloudflare.com/en-gb/learning/dns/dns-records/dns-srv-record/).
|
||||
|
||||
## Setting up a systemd service
|
||||
|
||||
Now we'll set up a systemd service for Conduit, so it's easy to start/stop Conduit and set it to autostart when your
|
||||
|
@ -100,7 +103,7 @@ After=network.target
|
|||
[Service]
|
||||
Environment="CONDUIT_CONFIG=/etc/matrix-conduit/conduit.toml"
|
||||
User=conduit
|
||||
Group=nogroup
|
||||
Group=conduit
|
||||
Restart=always
|
||||
ExecStart=/usr/local/bin/matrix-conduit
|
||||
|
||||
|
@ -116,59 +119,16 @@ $ sudo systemctl daemon-reload
|
|||
|
||||
## Creating the Conduit configuration file
|
||||
|
||||
Now we need to create the Conduit's config file in `/etc/matrix-conduit/conduit.toml`. Paste this in **and take a moment
|
||||
to read it. You need to change at least the server name.**
|
||||
Now we need to create the Conduit's config file in
|
||||
`/etc/matrix-conduit/conduit.toml`. Paste in the contents of
|
||||
[`conduit-example.toml`](../configuration.md) **and take a moment to read it.
|
||||
You need to change at least the server name.**
|
||||
You can also choose to use a different database backend, but right now only `rocksdb` and `sqlite` are recommended.
|
||||
|
||||
```toml
|
||||
[global]
|
||||
# The server_name is the pretty name of this server. It is used as a suffix for user
|
||||
# and room ids. Examples: matrix.org, conduit.rs
|
||||
|
||||
# The Conduit server needs all /_matrix/ requests to be reachable at
|
||||
# https://your.server.name/ on port 443 (client-server) and 8448 (federation).
|
||||
|
||||
# If that's not possible for you, you can create /.well-known files to redirect
|
||||
# requests. See
|
||||
# https://matrix.org/docs/spec/client_server/latest#get-well-known-matrix-client
|
||||
# and
|
||||
# https://matrix.org/docs/spec/server_server/r0.1.4#get-well-known-matrix-server
|
||||
# for more information
|
||||
|
||||
# YOU NEED TO EDIT THIS
|
||||
#server_name = "your.server.name"
|
||||
|
||||
# This is the only directory where Conduit will save its data
|
||||
database_path = "/var/lib/matrix-conduit/"
|
||||
database_backend = "rocksdb"
|
||||
|
||||
# The port Conduit will be running on. You need to set up a reverse proxy in
|
||||
# your web server (e.g. apache or nginx), so all requests to /_matrix on port
|
||||
# 443 and 8448 will be forwarded to the Conduit instance running on this port
|
||||
# Docker users: Don't change this, you'll need to map an external port to this.
|
||||
port = 6167
|
||||
|
||||
# Max size for uploads
|
||||
max_request_size = 20_000_000 # in bytes
|
||||
|
||||
# Enables registration. If set to false, no users can register on this server.
|
||||
allow_registration = true
|
||||
|
||||
allow_federation = true
|
||||
|
||||
trusted_servers = ["matrix.org"]
|
||||
|
||||
#max_concurrent_requests = 100 # How many requests Conduit sends to other servers at the same time
|
||||
#log = "warn,state_res=warn,rocket=off,_=off,sled=off"
|
||||
|
||||
address = "127.0.0.1" # This makes sure Conduit can only be reached using the reverse proxy
|
||||
#address = "0.0.0.0" # If Conduit is running in a container, make sure the reverse proxy (ie. Traefik) can reach it.
|
||||
```
|
||||
|
||||
## Setting the correct file permissions
|
||||
|
||||
As we are using a Conduit specific user we need to allow it to read the config. To do that you can run this command on
|
||||
Debian:
|
||||
Debian or RHEL:
|
||||
|
||||
```bash
|
||||
sudo chown -R root:root /etc/matrix-conduit
|
||||
|
@ -179,7 +139,7 @@ If you use the default database path you also need to run this:
|
|||
|
||||
```bash
|
||||
sudo mkdir -p /var/lib/matrix-conduit/
|
||||
sudo chown -R conduit:nogroup /var/lib/matrix-conduit/
|
||||
sudo chown -R conduit:conduit /var/lib/matrix-conduit/
|
||||
sudo chmod 700 /var/lib/matrix-conduit/
|
||||
```
|
||||
|
||||
|
@ -192,6 +152,11 @@ This depends on whether you use Apache, Caddy, Nginx or another web server.
|
|||
Create `/etc/apache2/sites-enabled/050-conduit.conf` and copy-and-paste this:
|
||||
|
||||
```apache
|
||||
# Requires mod_proxy and mod_proxy_http
|
||||
#
|
||||
# On Apache instance compiled from source,
|
||||
# paste into httpd-ssl.conf or httpd.conf
|
||||
|
||||
Listen 8448
|
||||
|
||||
<VirtualHost *:443 *:8448>
|
||||
|
@ -199,7 +164,7 @@ Listen 8448
|
|||
ServerName your.server.name # EDIT THIS
|
||||
|
||||
AllowEncodedSlashes NoDecode
|
||||
ProxyPass /_matrix/ http://127.0.0.1:6167/_matrix/ nocanon
|
||||
ProxyPass /_matrix/ http://127.0.0.1:6167/_matrix/ timeout=300 nocanon
|
||||
ProxyPassReverse /_matrix/ http://127.0.0.1:6167/_matrix/
|
||||
|
||||
</VirtualHost>
|
||||
|
@ -208,7 +173,11 @@ ProxyPassReverse /_matrix/ http://127.0.0.1:6167/_matrix/
|
|||
**You need to make some edits again.** When you are done, run
|
||||
|
||||
```bash
|
||||
# Debian
|
||||
$ sudo systemctl reload apache2
|
||||
|
||||
# Installed from source
|
||||
$ sudo apachectl -k graceful
|
||||
```
|
||||
|
||||
### Caddy
|
||||
|
@ -241,12 +210,14 @@ server {
|
|||
merge_slashes off;
|
||||
|
||||
# Nginx defaults to only allow 1MB uploads
|
||||
# Increase this to allow posting large files such as videos
|
||||
client_max_body_size 20M;
|
||||
|
||||
location /_matrix/ {
|
||||
proxy_pass http://127.0.0.1:6167$request_uri;
|
||||
proxy_pass http://127.0.0.1:6167;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 5m;
|
||||
}
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/your.server.name/fullchain.pem; # EDIT THIS
|
||||
|
@ -266,11 +237,19 @@ $ sudo systemctl reload nginx
|
|||
|
||||
If you chose Caddy as your web proxy SSL certificates are handled automatically and you can skip this step.
|
||||
|
||||
The easiest way to get an SSL certificate, if you don't have one already, is to install `certbot` and run this:
|
||||
The easiest way to get an SSL certificate, if you don't have one already, is to [install](https://certbot.eff.org/instructions) `certbot` and run this:
|
||||
|
||||
```bash
|
||||
# To use ECC for the private key,
|
||||
# paste into /etc/letsencrypt/cli.ini:
|
||||
# key-type = ecdsa
|
||||
# elliptic-curve = secp384r1
|
||||
|
||||
$ sudo certbot -d your.server.name
|
||||
```
|
||||
[Automated renewal](https://eff-certbot.readthedocs.io/en/stable/using.html#automated-renewals) is usually preconfigured.
|
||||
|
||||
If using Cloudflare, configure instead the edge and origin certificates in dashboard. In case you’re already running a website on the same Apache server, you can just copy-and-paste the SSL configuration from your main virtual host on port 443 into the above-mentioned vhost.
|
||||
|
||||
## You're done!
|
||||
|
||||
|
@ -288,12 +267,14 @@ $ sudo systemctl enable conduit
|
|||
|
||||
## How do I know it works?
|
||||
|
||||
You can open <https://app.element.io>, enter your homeserver and try to register.
|
||||
You can open [a Matrix client](https://matrix.org/ecosystem/clients), enter your homeserver and try to register. If you are using a registration token, use [Element web](https://app.element.io/), [Nheko](https://matrix.org/ecosystem/clients/nheko/) or [SchildiChat web](https://app.schildi.chat/), as they support this feature.
|
||||
|
||||
You can also use these commands as a quick health check.
|
||||
|
||||
```bash
|
||||
$ curl https://your.server.name/_matrix/client/versions
|
||||
|
||||
# If using port 8448
|
||||
$ curl https://your.server.name:8448/_matrix/client/versions
|
||||
```
|
||||
|
||||
|
@ -304,8 +285,8 @@ $ curl https://your.server.name:8448/_matrix/client/versions
|
|||
|
||||
## Audio/Video calls
|
||||
|
||||
For Audio/Video call functionality see the [TURN Guide](TURN.md).
|
||||
For Audio/Video call functionality see the [TURN Guide](../turn.md).
|
||||
|
||||
## Appservices
|
||||
|
||||
If you want to set up an appservice, take a look at the [Appservice Guide](APPSERVICES.md).
|
||||
If you want to set up an appservice, take a look at the [Appservice Guide](../appservices.md).
|
18
docs/deploying/nixos.md
Normal file
18
docs/deploying/nixos.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Conduit for NixOS
|
||||
|
||||
Conduit can be acquired by Nix from various places:
|
||||
|
||||
* The `flake.nix` at the root of the repo
|
||||
* The `default.nix` at the root of the repo
|
||||
* From Nixpkgs
|
||||
|
||||
The `flake.nix` and `default.nix` do not (currently) provide a NixOS module, so
|
||||
(for now) [`services.matrix-conduit`][module] from Nixpkgs should be used to
|
||||
configure Conduit.
|
||||
|
||||
If you want to run the latest code, you should get Conduit from the `flake.nix`
|
||||
or `default.nix` and set [`services.matrix-conduit.package`][package]
|
||||
appropriately.
|
||||
|
||||
[module]: https://search.nixos.org/options?channel=unstable&query=services.matrix-conduit
|
||||
[package]: https://search.nixos.org/options?channel=unstable&query=services.matrix-conduit.package
|
13
docs/introduction.md
Normal file
13
docs/introduction.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Conduit
|
||||
|
||||
{{#include ../README.md:catchphrase}}
|
||||
|
||||
{{#include ../README.md:body}}
|
||||
|
||||
#### How can I deploy my own?
|
||||
|
||||
- [Deployment options](deploying.md)
|
||||
|
||||
If you want to connect an Appservice to Conduit, take a look at the [appservices documentation](appservices.md).
|
||||
|
||||
{{#include ../README.md:footer}}
|
74
engage.toml
Normal file
74
engage.toml
Normal file
|
@ -0,0 +1,74 @@
|
|||
interpreter = ["bash", "-euo", "pipefail", "-c"]
|
||||
|
||||
[[task]]
|
||||
name = "engage"
|
||||
group = "versions"
|
||||
script = "engage --version"
|
||||
|
||||
[[task]]
|
||||
name = "rustc"
|
||||
group = "versions"
|
||||
script = "rustc --version"
|
||||
|
||||
[[task]]
|
||||
name = "cargo"
|
||||
group = "versions"
|
||||
script = "cargo --version"
|
||||
|
||||
[[task]]
|
||||
name = "cargo-fmt"
|
||||
group = "versions"
|
||||
script = "cargo fmt --version"
|
||||
|
||||
[[task]]
|
||||
name = "rustdoc"
|
||||
group = "versions"
|
||||
script = "rustdoc --version"
|
||||
|
||||
[[task]]
|
||||
name = "cargo-clippy"
|
||||
group = "versions"
|
||||
script = "cargo clippy -- --version"
|
||||
|
||||
[[task]]
|
||||
name = "lychee"
|
||||
group = "versions"
|
||||
script = "lychee --version"
|
||||
|
||||
[[task]]
|
||||
name = "cargo-fmt"
|
||||
group = "lints"
|
||||
script = "cargo fmt --check -- --color=always"
|
||||
|
||||
[[task]]
|
||||
name = "cargo-doc"
|
||||
group = "lints"
|
||||
script = """
|
||||
RUSTDOCFLAGS="-D warnings" cargo doc \
|
||||
--workspace \
|
||||
--no-deps \
|
||||
--document-private-items \
|
||||
--color always
|
||||
"""
|
||||
|
||||
[[task]]
|
||||
name = "cargo-clippy"
|
||||
group = "lints"
|
||||
script = "cargo clippy --workspace --all-targets --color=always -- -D warnings"
|
||||
|
||||
[[task]]
|
||||
name = "lychee"
|
||||
group = "lints"
|
||||
script = "lychee --offline docs"
|
||||
|
||||
[[task]]
|
||||
name = "cargo"
|
||||
group = "tests"
|
||||
script = """
|
||||
cargo test \
|
||||
--workspace \
|
||||
--all-targets \
|
||||
--color=always \
|
||||
-- \
|
||||
--color=always
|
||||
"""
|
203
flake.lock
generated
203
flake.lock
generated
|
@ -1,22 +1,41 @@
|
|||
{
|
||||
"nodes": {
|
||||
"crane": {
|
||||
"attic": {
|
||||
"inputs": {
|
||||
"crane": "crane",
|
||||
"flake-compat": "flake-compat",
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-overlay": "rust-overlay"
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1680584903,
|
||||
"narHash": "sha256-uraq+D3jcLzw/UVk0xMHcnfILfIMa0DLrtAEq2nNlxU=",
|
||||
"lastModified": 1707922053,
|
||||
"narHash": "sha256-wSZjK+rOXn+UQiP1NbdNn5/UW6UcBxjvlqr2wh++MbM=",
|
||||
"owner": "zhaofengli",
|
||||
"repo": "attic",
|
||||
"rev": "6eabc3f02fae3683bffab483e614bebfcd476b21",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "zhaofengli",
|
||||
"ref": "main",
|
||||
"repo": "attic",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"crane": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"attic",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1702918879,
|
||||
"narHash": "sha256-tWJqzajIvYcaRWxn+cLUB9L9Pv4dQ3Bfit/YjU5ze3g=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "65d3f6a3970cd46bef5eedfd458300f72c56b3c5",
|
||||
"rev": "7195c00c272fdd92fc74e7d5a0a2844b9fadb2fb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -25,6 +44,27 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"crane_2": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1707685877,
|
||||
"narHash": "sha256-XoXRS+5whotelr1rHiZle5t5hDg9kpguS5yk8c8qzOc=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "2c653e4478476a52c6aa3ac0495e4dea7449ea0e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "2c653e4478476a52c6aa3ac0495e4dea7449ea0e",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
|
@ -33,11 +73,11 @@
|
|||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1680607374,
|
||||
"narHash": "sha256-U5iiPqbAanr+sQCCZ7zxYhwCXdcDpish8Uy4ELZeXM0=",
|
||||
"lastModified": 1709619709,
|
||||
"narHash": "sha256-l6EPVJfwfelWST7qWQeP6t/TDK3HHv5uUB1b2vw4mOQ=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "e70d498e97017daa59363eafa054619d4fa160c3",
|
||||
"rev": "c8943ea9e98d41325ff57d4ec14736d330b321b2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -62,13 +102,29 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat_2": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1678901627,
|
||||
"narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=",
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6",
|
||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -77,37 +133,106 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1709126324,
|
||||
"narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "d465f4819400de7c8d874d50b982301f28a84605",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-filter": {
|
||||
"locked": {
|
||||
"lastModified": 1705332318,
|
||||
"narHash": "sha256-kcw1yFeJe9N4PjQji9ZeX47jg0p9A0DuU4djKvg1a7I=",
|
||||
"owner": "numtide",
|
||||
"repo": "nix-filter",
|
||||
"rev": "3449dc925982ad46246cfc36469baf66e1b64f17",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "nix-filter",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1680652733,
|
||||
"narHash": "sha256-FFG6Nai9M71C0Uc+D8TxyHoAjTplM0/9uWKsl7ALfUs=",
|
||||
"lastModified": 1702539185,
|
||||
"narHash": "sha256-KnIRG5NMdLIpEkZTnN5zovNYc0hhXjAgv6pfd5Z4c7U=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "cc5bde408572508efd1273852862d418bb313443",
|
||||
"rev": "aa9d4729cbc99dabacb50e3994dcefb3ea0f7447",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1702780907,
|
||||
"narHash": "sha256-blbrBBXjjZt6OKTcYX1jpe9SRof2P9ZYWPzq22tzXAA=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "1e2e384c5b7c50dbf8e9c441a9e58d85f408b01f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-23.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1709479366,
|
||||
"narHash": "sha256-n6F0n8UV6lnTZbYPl1A9q1BS0p4hduAv1mGAP17CVd0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b8697e57f10292a6165a20f03d2f42920dfaf973",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"crane": "crane",
|
||||
"attic": "attic",
|
||||
"crane": "crane_2",
|
||||
"fenix": "fenix",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
"flake-compat": "flake-compat_2",
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nix-filter": "nix-filter",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1680435407,
|
||||
"narHash": "sha256-IPBtZCOh3BdrR+V77cL7r6WQnclWcZ/85BDYnmq/GnQ=",
|
||||
"lastModified": 1709571018,
|
||||
"narHash": "sha256-ISFrxHxE0J5g7lDAscbK88hwaT5uewvWoma9TlFmRzM=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "236576227a299fd19ba836b1834ab50c948af994",
|
||||
"rev": "9f14343f9ee24f53f17492c5f9b653427e2ad15e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -117,28 +242,18 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"crane",
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"crane",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1680488274,
|
||||
"narHash": "sha256-0vYMrZDdokVmPQQXtFpnqA2wEgCCUXf5a3dDuDVshn0=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "7ec2ff598a172c6e8584457167575b3a1a5d80d8",
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
|
|
324
flake.nix
324
flake.nix
|
@ -1,93 +1,317 @@
|
|||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
nix-filter.url = "github:numtide/nix-filter";
|
||||
flake-compat = {
|
||||
url = "github:edolstra/flake-compat";
|
||||
flake = false;
|
||||
};
|
||||
|
||||
fenix = {
|
||||
url = "github:nix-community/fenix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
crane = {
|
||||
url = "github:ipetkov/crane";
|
||||
# Pin latest crane that's not affected by the following bugs:
|
||||
#
|
||||
# * <https://github.com/ipetkov/crane/issues/527#issuecomment-1978079140>
|
||||
# * <https://github.com/toml-rs/toml/issues/691>
|
||||
# * <https://github.com/toml-rs/toml/issues/267>
|
||||
url = "github:ipetkov/crane?rev=2c653e4478476a52c6aa3ac0495e4dea7449ea0e";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.flake-utils.follows = "flake-utils";
|
||||
};
|
||||
attic.url = "github:zhaofengli/attic?ref=main";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{ self
|
||||
, nixpkgs
|
||||
, flake-utils
|
||||
, nix-filter
|
||||
|
||||
, fenix
|
||||
, crane
|
||||
, ...
|
||||
}: flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
|
||||
# Use mold on Linux
|
||||
stdenv = if pkgs.stdenv.isLinux then
|
||||
pkgs.stdenvAdapters.useMoldLinker pkgs.stdenv
|
||||
else
|
||||
pkgs.stdenv;
|
||||
pkgsHost = nixpkgs.legacyPackages.${system};
|
||||
|
||||
# Nix-accessible `Cargo.toml`
|
||||
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
|
||||
|
||||
# The Rust toolchain to use
|
||||
toolchain = fenix.packages.${system}.toolchainOf {
|
||||
# Use the Rust version defined in `Cargo.toml`
|
||||
channel = cargoToml.package.rust-version;
|
||||
toolchain = fenix.packages.${system}.fromToolchainFile {
|
||||
file = ./rust-toolchain.toml;
|
||||
|
||||
# THE rust-version HASH
|
||||
sha256 = "sha256-8len3i8oTwJSOJZMosGGXHBL5BVuGQnWOT2St5YAUFU=";
|
||||
# See also `rust-toolchain.toml`
|
||||
sha256 = "sha256-SXRtAuO4IqNOQq+nLbrsDFbVk+3aVA8NNpSZsKlVH/8=";
|
||||
};
|
||||
|
||||
# The system's RocksDB
|
||||
ROCKSDB_INCLUDE_DIR = "${pkgs.rocksdb_6_23}/include";
|
||||
ROCKSDB_LIB_DIR = "${pkgs.rocksdb_6_23}/lib";
|
||||
builder = pkgs:
|
||||
((crane.mkLib pkgs).overrideToolchain toolchain).buildPackage;
|
||||
|
||||
# Shared between the package and the devShell
|
||||
nativeBuildInputs = (with pkgs.rustPlatform; [
|
||||
bindgenHook
|
||||
]);
|
||||
nativeBuildInputs = pkgs: [
|
||||
# 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.pkgsBuildHost.rustPlatform.bindgenHook
|
||||
];
|
||||
|
||||
builder =
|
||||
((crane.mkLib pkgs).overrideToolchain toolchain.toolchain).buildPackage;
|
||||
rocksdb' = pkgs:
|
||||
let
|
||||
version = "8.11.3";
|
||||
in
|
||||
pkgs.rocksdb.overrideAttrs (old: {
|
||||
inherit version;
|
||||
src = pkgs.fetchFromGitHub {
|
||||
owner = "facebook";
|
||||
repo = "rocksdb";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-OpEiMwGxZuxb9o3RQuSrwZMQGLhe9xLT1aa3HpI4KPs=";
|
||||
};
|
||||
});
|
||||
|
||||
env = pkgs: {
|
||||
CONDUIT_VERSION_EXTRA = self.shortRev or self.dirtyShortRev;
|
||||
ROCKSDB_INCLUDE_DIR = "${rocksdb' pkgs}/include";
|
||||
ROCKSDB_LIB_DIR = "${rocksdb' pkgs}/lib";
|
||||
}
|
||||
// pkgs.lib.optionalAttrs pkgs.stdenv.hostPlatform.isStatic {
|
||||
ROCKSDB_STATIC = "";
|
||||
}
|
||||
// {
|
||||
CARGO_BUILD_RUSTFLAGS = let inherit (pkgs) lib stdenv; in
|
||||
lib.concatStringsSep " " ([]
|
||||
++ lib.optionals
|
||||
# This disables PIE for static builds, which isn't great in terms
|
||||
# of security. Unfortunately, my hand is forced because nixpkgs'
|
||||
# `libstdc++.a` is built without `-fPIE`, which precludes us from
|
||||
# leaving PIE enabled.
|
||||
stdenv.hostPlatform.isStatic
|
||||
["-C" "relocation-model=static"]
|
||||
++ lib.optionals
|
||||
(stdenv.buildPlatform.config != stdenv.hostPlatform.config)
|
||||
["-l" "c"]
|
||||
++ lib.optionals
|
||||
# This check has to match the one [here][0]. We only need to set
|
||||
# these flags when using a different linker. Don't ask me why,
|
||||
# though, because I don't know. All I know is it breaks otherwise.
|
||||
#
|
||||
# [0]: https://github.com/NixOS/nixpkgs/blob/5cdb38bb16c6d0a38779db14fcc766bc1b2394d6/pkgs/build-support/rust/lib/default.nix#L37-L40
|
||||
(
|
||||
# Nixpkgs doesn't check for x86_64 here but we do, because I
|
||||
# observed a failure building statically for x86_64 without
|
||||
# including it here. Linkers are weird.
|
||||
(stdenv.hostPlatform.isAarch64 || stdenv.hostPlatform.isx86_64)
|
||||
&& stdenv.hostPlatform.isStatic
|
||||
&& !stdenv.isDarwin
|
||||
&& !stdenv.cc.bintools.isLLVM
|
||||
)
|
||||
[
|
||||
"-l"
|
||||
"stdc++"
|
||||
"-L"
|
||||
"${stdenv.cc.cc.lib}/${stdenv.hostPlatform.config}/lib"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
# What follows is stolen from [here][0]. Its purpose is to properly
|
||||
# configure compilers and linkers for various stages of the build, and
|
||||
# even covers the case of build scripts that need native code compiled and
|
||||
# run on the build platform (I think).
|
||||
#
|
||||
# [0]: https://github.com/NixOS/nixpkgs/blob/5cdb38bb16c6d0a38779db14fcc766bc1b2394d6/pkgs/build-support/rust/lib/default.nix#L57-L80
|
||||
// (
|
||||
let
|
||||
inherit (pkgs.rust.lib) envVars;
|
||||
in
|
||||
pkgs.lib.optionalAttrs
|
||||
(pkgs.stdenv.targetPlatform.rust.rustcTarget
|
||||
!= pkgs.stdenv.hostPlatform.rust.rustcTarget)
|
||||
(
|
||||
let
|
||||
inherit (pkgs.stdenv.targetPlatform.rust) cargoEnvVarTarget;
|
||||
in
|
||||
{
|
||||
"CC_${cargoEnvVarTarget}" = envVars.ccForTarget;
|
||||
"CXX_${cargoEnvVarTarget}" = envVars.cxxForTarget;
|
||||
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" =
|
||||
envVars.linkerForTarget;
|
||||
}
|
||||
)
|
||||
// (
|
||||
let
|
||||
inherit (pkgs.stdenv.hostPlatform.rust) cargoEnvVarTarget rustcTarget;
|
||||
in
|
||||
{
|
||||
"CC_${cargoEnvVarTarget}" = envVars.ccForHost;
|
||||
"CXX_${cargoEnvVarTarget}" = envVars.cxxForHost;
|
||||
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.linkerForHost;
|
||||
CARGO_BUILD_TARGET = rustcTarget;
|
||||
}
|
||||
)
|
||||
// (
|
||||
let
|
||||
inherit (pkgs.stdenv.buildPlatform.rust) cargoEnvVarTarget;
|
||||
in
|
||||
{
|
||||
"CC_${cargoEnvVarTarget}" = envVars.ccForBuild;
|
||||
"CXX_${cargoEnvVarTarget}" = envVars.cxxForBuild;
|
||||
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.linkerForBuild;
|
||||
HOST_CC = "${pkgs.pkgsBuildHost.stdenv.cc}/bin/cc";
|
||||
HOST_CXX = "${pkgs.pkgsBuildHost.stdenv.cc}/bin/c++";
|
||||
}
|
||||
));
|
||||
|
||||
package = pkgs: builder pkgs {
|
||||
src = nix-filter {
|
||||
root = ./.;
|
||||
include = [
|
||||
"src"
|
||||
"Cargo.toml"
|
||||
"Cargo.lock"
|
||||
];
|
||||
};
|
||||
|
||||
# This is redundant with CI
|
||||
doCheck = false;
|
||||
|
||||
env = env pkgs;
|
||||
nativeBuildInputs = nativeBuildInputs pkgs;
|
||||
|
||||
meta.mainProgram = cargoToml.package.name;
|
||||
};
|
||||
|
||||
mkOciImage = pkgs: package:
|
||||
pkgs.dockerTools.buildImage {
|
||||
name = package.pname;
|
||||
tag = "next";
|
||||
copyToRoot = [
|
||||
pkgs.dockerTools.caCertificates
|
||||
];
|
||||
config = {
|
||||
# Use the `tini` init system so that signals (e.g. ctrl+c/SIGINT)
|
||||
# are handled as expected
|
||||
Entrypoint = [
|
||||
"${pkgs.lib.getExe' pkgs.tini "tini"}"
|
||||
"--"
|
||||
];
|
||||
Cmd = [
|
||||
"${pkgs.lib.getExe package}"
|
||||
];
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
packages.default = builder {
|
||||
src = ./.;
|
||||
packages = {
|
||||
default = package pkgsHost;
|
||||
oci-image = mkOciImage pkgsHost self.packages.${system}.default;
|
||||
|
||||
inherit
|
||||
stdenv
|
||||
nativeBuildInputs
|
||||
ROCKSDB_INCLUDE_DIR
|
||||
ROCKSDB_LIB_DIR;
|
||||
};
|
||||
book =
|
||||
let
|
||||
package = self.packages.${system}.default;
|
||||
in
|
||||
pkgsHost.stdenv.mkDerivation {
|
||||
pname = "${package.pname}-book";
|
||||
version = package.version;
|
||||
|
||||
devShells.default = (pkgs.mkShell.override { inherit stdenv; }) {
|
||||
# Rust Analyzer needs to be able to find the path to default crate
|
||||
# sources, and it can read this environment variable to do so
|
||||
RUST_SRC_PATH = "${toolchain.rust-src}/lib/rustlib/src/rust/library";
|
||||
src = nix-filter {
|
||||
root = ./.;
|
||||
include = [
|
||||
"book.toml"
|
||||
"conduit-example.toml"
|
||||
"README.md"
|
||||
"debian/README.md"
|
||||
"docs"
|
||||
];
|
||||
};
|
||||
|
||||
inherit
|
||||
ROCKSDB_INCLUDE_DIR
|
||||
ROCKSDB_LIB_DIR;
|
||||
nativeBuildInputs = (with pkgsHost; [
|
||||
mdbook
|
||||
]);
|
||||
|
||||
buildPhase = ''
|
||||
mdbook build
|
||||
mv public $out
|
||||
'';
|
||||
};
|
||||
}
|
||||
//
|
||||
builtins.listToAttrs
|
||||
(builtins.concatLists
|
||||
(builtins.map
|
||||
(crossSystem:
|
||||
let
|
||||
binaryName = "static-${crossSystem}";
|
||||
pkgsCrossStatic =
|
||||
(import nixpkgs {
|
||||
inherit system;
|
||||
crossSystem = {
|
||||
config = crossSystem;
|
||||
};
|
||||
}).pkgsStatic;
|
||||
in
|
||||
[
|
||||
# An output for a statically-linked binary
|
||||
{
|
||||
name = binaryName;
|
||||
value = package pkgsCrossStatic;
|
||||
}
|
||||
|
||||
# An output for an OCI image based on that binary
|
||||
{
|
||||
name = "oci-image-${crossSystem}";
|
||||
value = mkOciImage
|
||||
pkgsCrossStatic
|
||||
self.packages.${system}.${binaryName};
|
||||
}
|
||||
]
|
||||
)
|
||||
[
|
||||
"x86_64-unknown-linux-musl"
|
||||
"aarch64-unknown-linux-musl"
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
devShells.default = pkgsHost.mkShell {
|
||||
env = env pkgsHost // {
|
||||
# Rust Analyzer needs to be able to find the path to default crate
|
||||
# sources, and it can read this environment variable to do so. The
|
||||
# `rust-src` component is required in order for this to work.
|
||||
RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library";
|
||||
};
|
||||
|
||||
# Development tools
|
||||
nativeBuildInputs = nativeBuildInputs ++ (with toolchain; [
|
||||
cargo
|
||||
clippy
|
||||
rust-src
|
||||
rustc
|
||||
rustfmt
|
||||
]);
|
||||
};
|
||||
nativeBuildInputs = nativeBuildInputs pkgsHost ++ [
|
||||
# Always use nightly rustfmt because most of its options are unstable
|
||||
#
|
||||
# This needs to come before `toolchain` in this list, otherwise
|
||||
# `$PATH` will have stable rustfmt instead.
|
||||
fenix.packages.${system}.latest.rustfmt
|
||||
|
||||
checks = {
|
||||
packagesDefault = self.packages.${system}.default;
|
||||
devShellsDefault = self.devShells.${system}.default;
|
||||
toolchain
|
||||
] ++ (with pkgsHost; [
|
||||
engage
|
||||
|
||||
# Needed for producing Debian packages
|
||||
cargo-deb
|
||||
|
||||
# Needed for Complement
|
||||
go
|
||||
olm
|
||||
|
||||
# Needed for our script for Complement
|
||||
jq
|
||||
|
||||
# Needed for finding broken markdown links
|
||||
lychee
|
||||
|
||||
# Useful for editing the book locally
|
||||
mdbook
|
||||
]);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
189
nix/README.md
189
nix/README.md
|
@ -1,189 +0,0 @@
|
|||
# Conduit for Nix/NixOS
|
||||
|
||||
This guide assumes you have a recent version of Nix (^2.4) installed.
|
||||
|
||||
Since Conduit ships as a Nix flake, you'll first need to [enable
|
||||
flakes][enable_flakes].
|
||||
|
||||
You can now use the usual Nix commands to interact with Conduit's flake. For
|
||||
example, `nix run gitlab:famedly/conduit` will run Conduit (though you'll need
|
||||
to provide configuration and such manually as usual).
|
||||
|
||||
If your NixOS configuration is defined as a flake, you can depend on this flake
|
||||
to provide a more up-to-date version than provided by `nixpkgs`. In your flake,
|
||||
add the following to your `inputs`:
|
||||
|
||||
```nix
|
||||
conduit = {
|
||||
url = "gitlab:famedly/conduit";
|
||||
|
||||
# Assuming you have an input for nixpkgs called `nixpkgs`. If you experience
|
||||
# build failures while using this, try commenting/deleting this line. This
|
||||
# will probably also require you to always build from source.
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
```
|
||||
|
||||
Next, make sure you're passing your flake inputs to the `specialArgs` argument
|
||||
of `nixpkgs.lib.nixosSystem` [as explained here][specialargs]. This guide will
|
||||
assume you've named the group `flake-inputs`.
|
||||
|
||||
Now you can configure Conduit and a reverse proxy for it. Add the following to
|
||||
a new Nix file and include it in your configuration:
|
||||
|
||||
```nix
|
||||
{ config
|
||||
, pkgs
|
||||
, flake-inputs
|
||||
, ...
|
||||
}:
|
||||
|
||||
let
|
||||
# You'll need to edit these values
|
||||
|
||||
# The hostname that will appear in your user and room IDs
|
||||
server_name = "example.com";
|
||||
|
||||
# The hostname that Conduit actually runs on
|
||||
#
|
||||
# This can be the same as `server_name` if you want. This is only necessary
|
||||
# when Conduit is running on a different machine than the one hosting your
|
||||
# root domain. This configuration also assumes this is all running on a single
|
||||
# machine, some tweaks will need to be made if this is not the case.
|
||||
matrix_hostname = "matrix.${server_name}";
|
||||
|
||||
# An admin email for TLS certificate notifications
|
||||
admin_email = "admin@${server_name}";
|
||||
|
||||
# These ones you can leave alone
|
||||
|
||||
# Build a dervation that stores the content of `${server_name}/.well-known/matrix/server`
|
||||
well_known_server = pkgs.writeText "well-known-matrix-server" ''
|
||||
{
|
||||
"m.server": "${matrix_hostname}"
|
||||
}
|
||||
'';
|
||||
|
||||
# Build a dervation that stores the content of `${server_name}/.well-known/matrix/client`
|
||||
well_known_client = pkgs.writeText "well-known-matrix-client" ''
|
||||
{
|
||||
"m.homeserver": {
|
||||
"base_url": "https://${matrix_hostname}"
|
||||
}
|
||||
}
|
||||
'';
|
||||
in
|
||||
|
||||
{
|
||||
# Configure Conduit itself
|
||||
services.matrix-conduit = {
|
||||
enable = true;
|
||||
|
||||
# This causes NixOS to use the flake defined in this repository instead of
|
||||
# the build of Conduit built into nixpkgs.
|
||||
package = flake-inputs.conduit.packages.${pkgs.system}.default;
|
||||
|
||||
settings.global = {
|
||||
inherit server_name;
|
||||
};
|
||||
};
|
||||
|
||||
# Configure automated TLS acquisition/renewal
|
||||
security.acme = {
|
||||
acceptTerms = true;
|
||||
defaults = {
|
||||
email = admin_email;
|
||||
};
|
||||
};
|
||||
|
||||
# ACME data must be readable by the NGINX user
|
||||
users.users.nginx.extraGroups = [
|
||||
"acme"
|
||||
];
|
||||
|
||||
# Configure NGINX as a reverse proxy
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
recommendedProxySettings = true;
|
||||
|
||||
virtualHosts = {
|
||||
"${matrix_hostname}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
|
||||
listen = [
|
||||
{
|
||||
addr = "0.0.0.0";
|
||||
port = 443;
|
||||
ssl = true;
|
||||
}
|
||||
{
|
||||
addr = "0.0.0.0";
|
||||
port = 8448;
|
||||
ssl = true;
|
||||
}
|
||||
];
|
||||
|
||||
locations."/_matrix/" = {
|
||||
proxyPass = "http://backend_conduit$request_uri";
|
||||
proxyWebsockets = true;
|
||||
extraConfig = ''
|
||||
proxy_set_header Host $host;
|
||||
proxy_buffering off;
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = ''
|
||||
merge_slashes off;
|
||||
'';
|
||||
};
|
||||
|
||||
"${server_name}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
|
||||
locations."=/.well-known/matrix/server" = {
|
||||
# Use the contents of the derivation built previously
|
||||
alias = "${well_known_server}";
|
||||
|
||||
extraConfig = ''
|
||||
# Set the header since by default NGINX thinks it's just bytes
|
||||
default_type application/json;
|
||||
'';
|
||||
};
|
||||
|
||||
locations."=/.well-known/matrix/client" = {
|
||||
# Use the contents of the derivation built previously
|
||||
alias = "${well_known_client}";
|
||||
|
||||
extraConfig = ''
|
||||
# Set the header since by default NGINX thinks it's just bytes
|
||||
default_type application/json;
|
||||
|
||||
# https://matrix.org/docs/spec/client_server/r0.4.0#web-browser-clients
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
upstreams = {
|
||||
"backend_conduit" = {
|
||||
servers = {
|
||||
"localhost:${toString config.services.matrix-conduit.settings.global.port}" = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Open firewall ports for HTTP, HTTPS, and Matrix federation
|
||||
networking.firewall.allowedTCPPorts = [ 80 443 8448 ];
|
||||
networking.firewall.allowedUDPPorts = [ 80 443 8448 ];
|
||||
}
|
||||
```
|
||||
|
||||
Now you can rebuild your system configuration and you should be good to go!
|
||||
|
||||
[enable_flakes]: https://nixos.wiki/wiki/Flakes#Enable_flakes
|
||||
|
||||
[specialargs]: https://nixos.wiki/wiki/Flakes#Using_nix_flakes_with_NixOS
|
22
rust-toolchain.toml
Normal file
22
rust-toolchain.toml
Normal file
|
@ -0,0 +1,22 @@
|
|||
# This is the authoritiative configuration of this project's Rust toolchain.
|
||||
#
|
||||
# Other files that need upkeep when this changes:
|
||||
#
|
||||
# * `.gitlab-ci.yml`
|
||||
# * `Cargo.toml`
|
||||
# * `flake.nix`
|
||||
#
|
||||
# Search in those files for `rust-toolchain.toml` to find the relevant places.
|
||||
# If you're having trouble making the relevant changes, bug a maintainer.
|
||||
|
||||
[toolchain]
|
||||
channel = "1.75.0"
|
||||
components = [
|
||||
# For rust-analyzer
|
||||
"rust-src",
|
||||
]
|
||||
targets = [
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"x86_64-unknown-linux-musl",
|
||||
"aarch64-unknown-linux-musl",
|
||||
]
|
|
@ -1,24 +1,35 @@
|
|||
use crate::{services, utils, Error, Result};
|
||||
use bytes::BytesMut;
|
||||
use ruma::api::{IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken};
|
||||
use ruma::api::{
|
||||
appservice::Registration, IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken,
|
||||
};
|
||||
use std::{fmt::Debug, mem, time::Duration};
|
||||
use tracing::warn;
|
||||
|
||||
/// Sends a request to an appservice
|
||||
///
|
||||
/// Only returns None if there is no url specified in the appservice registration file
|
||||
#[tracing::instrument(skip(request))]
|
||||
pub(crate) async fn send_request<T: OutgoingRequest>(
|
||||
registration: serde_yaml::Value,
|
||||
registration: Registration,
|
||||
request: T,
|
||||
) -> Result<T::IncomingResponse>
|
||||
) -> Result<Option<T::IncomingResponse>>
|
||||
where
|
||||
T: Debug,
|
||||
{
|
||||
let destination = registration.get("url").unwrap().as_str().unwrap();
|
||||
let hs_token = registration.get("hs_token").unwrap().as_str().unwrap();
|
||||
let destination = match registration.url {
|
||||
Some(url) => url,
|
||||
None => {
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
let hs_token = registration.hs_token.as_str();
|
||||
|
||||
let mut http_request = request
|
||||
.try_into_http_request::<BytesMut>(
|
||||
destination,
|
||||
SendAccessToken::IfRequired(""),
|
||||
&destination,
|
||||
SendAccessToken::IfRequired(hs_token),
|
||||
&[MatrixVersion::V1_0],
|
||||
)
|
||||
.unwrap()
|
||||
|
@ -39,8 +50,7 @@ where
|
|||
);
|
||||
*http_request.uri_mut() = parts.try_into().expect("our manipulation is always valid");
|
||||
|
||||
let mut reqwest_request = reqwest::Request::try_from(http_request)
|
||||
.expect("all http requests are valid reqwest requests");
|
||||
let mut reqwest_request = reqwest::Request::try_from(http_request)?;
|
||||
|
||||
*reqwest_request.timeout_mut() = Some(Duration::from_secs(30));
|
||||
|
||||
|
@ -55,9 +65,7 @@ where
|
|||
Err(e) => {
|
||||
warn!(
|
||||
"Could not send request to appservice {:?} at {}: {}",
|
||||
registration.get("id"),
|
||||
destination,
|
||||
e
|
||||
registration.id, destination, e
|
||||
);
|
||||
return Err(e.into());
|
||||
}
|
||||
|
@ -95,7 +103,8 @@ where
|
|||
.body(body)
|
||||
.expect("reqwest body is valid http body"),
|
||||
);
|
||||
response.map_err(|_| {
|
||||
|
||||
response.map(Some).map_err(|_| {
|
||||
warn!(
|
||||
"Appservice returned invalid response bytes {}\n{}",
|
||||
destination, url
|
||||
|
|
|
@ -3,7 +3,8 @@ use crate::{api::client_server, services, utils, Error, Result, Ruma};
|
|||
use ruma::{
|
||||
api::client::{
|
||||
account::{
|
||||
change_password, deactivate, get_3pids, get_username_availability, register,
|
||||
change_password, deactivate, get_3pids, get_username_availability,
|
||||
register::{self, LoginType},
|
||||
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn,
|
||||
whoami, ThirdPartyIdRemovalStatus,
|
||||
},
|
||||
|
@ -74,13 +75,23 @@ pub async fn get_register_available_route(
|
|||
/// - 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
|
||||
pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<register::v3::Response> {
|
||||
if !services().globals.allow_registration() && !body.from_appservice {
|
||||
if !services().globals.allow_registration()
|
||||
&& !body.from_appservice
|
||||
&& services().globals.config.registration_token.is_none()
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Registration has been disabled.",
|
||||
));
|
||||
}
|
||||
|
||||
if body.body.login_type == Some(LoginType::ApplicationService) && !body.from_appservice {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::MissingToken,
|
||||
"Missing appservice token.",
|
||||
));
|
||||
}
|
||||
|
||||
let is_guest = body.kind == RegistrationKind::Guest;
|
||||
|
||||
let user_id = match (&body.username, is_guest) {
|
||||
|
@ -121,7 +132,11 @@ pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<registe
|
|||
// UIAA
|
||||
let mut uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow {
|
||||
stages: vec![AuthType::Dummy],
|
||||
stages: if services().globals.config.registration_token.is_some() {
|
||||
vec![AuthType::RegistrationToken]
|
||||
} else {
|
||||
vec![AuthType::Dummy]
|
||||
},
|
||||
}],
|
||||
completed: Vec::new(),
|
||||
params: Default::default(),
|
||||
|
@ -222,21 +237,32 @@ pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<registe
|
|||
)?;
|
||||
|
||||
info!("New user {} registered on this server.", user_id);
|
||||
services()
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
"New user {user_id} registered on this server."
|
||||
)));
|
||||
if !body.from_appservice && !is_guest {
|
||||
services()
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
"New user {user_id} registered on this server."
|
||||
)));
|
||||
}
|
||||
|
||||
// If this is the first real user, grant them admin privileges
|
||||
// Note: the server user, @conduit:servername, is generated first
|
||||
if services().users.count()? == 2 {
|
||||
services()
|
||||
.admin
|
||||
.make_user_admin(&user_id, displayname)
|
||||
.await?;
|
||||
if !is_guest {
|
||||
if let Some(admin_room) = services().admin.get_admin_room()? {
|
||||
if services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_joined_count(&admin_room)?
|
||||
== Some(1)
|
||||
{
|
||||
services()
|
||||
.admin
|
||||
.make_user_admin(&user_id, displayname)
|
||||
.await?;
|
||||
|
||||
warn!("Granting {} admin privileges as the first user", user_id);
|
||||
warn!("Granting {} admin privileges as the first user", user_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(register::v3::Response {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{services, Error, Result, Ruma};
|
||||
use regex::Regex;
|
||||
use rand::seq::SliceRandom;
|
||||
use ruma::{
|
||||
api::{
|
||||
appservice,
|
||||
|
@ -90,41 +90,30 @@ pub(crate) async fn get_alias_helper(
|
|||
)
|
||||
.await?;
|
||||
|
||||
return Ok(get_alias::v3::Response::new(
|
||||
response.room_id,
|
||||
response.servers,
|
||||
));
|
||||
let mut servers = response.servers;
|
||||
servers.shuffle(&mut rand::thread_rng());
|
||||
|
||||
return Ok(get_alias::v3::Response::new(response.room_id, servers));
|
||||
}
|
||||
|
||||
let mut room_id = None;
|
||||
match services().rooms.alias.resolve_local_alias(&room_alias)? {
|
||||
Some(r) => room_id = Some(r),
|
||||
None => {
|
||||
for (_id, registration) in services().appservice.all()? {
|
||||
let aliases = registration
|
||||
.get("namespaces")
|
||||
.and_then(|ns| ns.get("aliases"))
|
||||
.and_then(|aliases| aliases.as_sequence())
|
||||
.map_or_else(Vec::new, |aliases| {
|
||||
aliases
|
||||
.iter()
|
||||
.filter_map(|aliases| Regex::new(aliases.get("regex")?.as_str()?).ok())
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
if aliases
|
||||
.iter()
|
||||
.any(|aliases| aliases.is_match(room_alias.as_str()))
|
||||
&& services()
|
||||
.sending
|
||||
.send_appservice_request(
|
||||
registration,
|
||||
appservice::query::query_room_alias::v1::Request {
|
||||
room_alias: room_alias.clone(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
for appservice in services().appservice.read().await.values() {
|
||||
if appservice.aliases.is_match(room_alias.as_str())
|
||||
&& matches!(
|
||||
services()
|
||||
.sending
|
||||
.send_appservice_request(
|
||||
appservice.registration.clone(),
|
||||
appservice::query::query_room_alias::v1::Request {
|
||||
room_alias: room_alias.clone(),
|
||||
},
|
||||
)
|
||||
.await,
|
||||
Ok(Some(_opt_result))
|
||||
)
|
||||
{
|
||||
room_id = Some(
|
||||
services()
|
||||
|
|
|
@ -75,7 +75,7 @@ pub async fn get_global_account_data_route(
|
|||
|
||||
let event: Box<RawJsonValue> = services()
|
||||
.account_data
|
||||
.get(None, sender_user, body.event_type.clone().into())?
|
||||
.get(None, sender_user, body.event_type.to_string().into())?
|
||||
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
|
||||
|
||||
let account_data = serde_json::from_str::<ExtractGlobalEventContent>(event.get())
|
||||
|
@ -95,11 +95,7 @@ pub async fn get_room_account_data_route(
|
|||
|
||||
let event: Box<RawJsonValue> = services()
|
||||
.account_data
|
||||
.get(
|
||||
Some(&body.room_id),
|
||||
sender_user,
|
||||
body.event_type.clone().into(),
|
||||
)?
|
||||
.get(Some(&body.room_id), sender_user, body.event_type.clone())?
|
||||
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
|
||||
|
||||
let account_data = serde_json::from_str::<ExtractRoomEventContent>(event.get())
|
||||
|
|
|
@ -3,7 +3,7 @@ use ruma::{
|
|||
api::client::{context::get_context, error::ErrorKind, filter::LazyLoadOptions},
|
||||
events::StateEventType,
|
||||
};
|
||||
use std::{collections::HashSet, convert::TryFrom};
|
||||
use std::collections::HashSet;
|
||||
use tracing::error;
|
||||
|
||||
/// # `GET /_matrix/client/r0/rooms/{roomId}/context`
|
||||
|
@ -69,18 +69,16 @@ pub async fn get_context_route(
|
|||
lazy_loaded.insert(base_event.sender.as_str().to_owned());
|
||||
}
|
||||
|
||||
// Use limit with maximum 100
|
||||
let limit = u64::from(body.limit).min(100) as usize;
|
||||
|
||||
let base_event = base_event.to_room_event();
|
||||
|
||||
let events_before: Vec<_> = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_until(sender_user, &room_id, base_token)?
|
||||
.take(
|
||||
u32::try_from(body.limit).map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::InvalidParam, "Limit value is invalid.")
|
||||
})? as usize
|
||||
/ 2,
|
||||
)
|
||||
.take(limit / 2)
|
||||
.filter_map(|r| r.ok()) // Remove buggy events
|
||||
.filter(|(_, pdu)| {
|
||||
services()
|
||||
|
@ -103,7 +101,10 @@ pub async fn get_context_route(
|
|||
}
|
||||
}
|
||||
|
||||
let start_token = events_before.last().map(|(count, _)| count.stringify());
|
||||
let start_token = events_before
|
||||
.last()
|
||||
.map(|(count, _)| count.stringify())
|
||||
.unwrap_or_else(|| base_token.stringify());
|
||||
|
||||
let events_before: Vec<_> = events_before
|
||||
.into_iter()
|
||||
|
@ -114,12 +115,7 @@ pub async fn get_context_route(
|
|||
.rooms
|
||||
.timeline
|
||||
.pdus_after(sender_user, &room_id, base_token)?
|
||||
.take(
|
||||
u32::try_from(body.limit).map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::InvalidParam, "Limit value is invalid.")
|
||||
})? as usize
|
||||
/ 2,
|
||||
)
|
||||
.take(limit / 2)
|
||||
.filter_map(|r| r.ok()) // Remove buggy events
|
||||
.filter(|(_, pdu)| {
|
||||
services()
|
||||
|
@ -161,7 +157,10 @@ pub async fn get_context_route(
|
|||
.state_full_ids(shortstatehash)
|
||||
.await?;
|
||||
|
||||
let end_token = events_after.last().map(|(count, _)| count.stringify());
|
||||
let end_token = events_after
|
||||
.last()
|
||||
.map(|(count, _)| count.stringify())
|
||||
.unwrap_or_else(|| base_token.stringify());
|
||||
|
||||
let events_after: Vec<_> = events_after
|
||||
.into_iter()
|
||||
|
@ -198,8 +197,8 @@ pub async fn get_context_route(
|
|||
}
|
||||
|
||||
let resp = get_context::v3::Response {
|
||||
start: start_token,
|
||||
end: end_token,
|
||||
start: Some(start_token),
|
||||
end: Some(end_token),
|
||||
events_before,
|
||||
event: Some(base_event),
|
||||
events_after,
|
||||
|
|
|
@ -20,7 +20,6 @@ use ruma::{
|
|||
guest_access::{GuestAccess, RoomGuestAccessEventContent},
|
||||
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
||||
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||
name::RoomNameEventContent,
|
||||
topic::RoomTopicEventContent,
|
||||
},
|
||||
StateEventType,
|
||||
|
@ -203,17 +202,7 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
|||
Error::bad_database("Invalid canonical alias event in database.")
|
||||
})
|
||||
})?,
|
||||
name: services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(&room_id, &StateEventType::RoomName, "")?
|
||||
.map_or(Ok(None), |s| {
|
||||
serde_json::from_str(s.content.get())
|
||||
.map(|c: RoomNameEventContent| c.name)
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid room name event in database.")
|
||||
})
|
||||
})?,
|
||||
name: services().rooms.state_accessor.get_name(&room_id)?,
|
||||
num_joined_members: services()
|
||||
.rooms
|
||||
.state_cache
|
||||
|
@ -232,6 +221,7 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
|||
serde_json::from_str(s.content.get())
|
||||
.map(|c: RoomTopicEventContent| Some(c.topic))
|
||||
.map_err(|_| {
|
||||
error!("Invalid room topic event in database for room {}", room_id);
|
||||
Error::bad_database("Invalid room topic event in database.")
|
||||
})
|
||||
})?,
|
||||
|
|
|
@ -17,7 +17,11 @@ use ruma::{
|
|||
DeviceKeyAlgorithm, OwnedDeviceId, OwnedUserId, UserId,
|
||||
};
|
||||
use serde_json::json;
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::{
|
||||
collections::{hash_map, BTreeMap, HashMap, HashSet},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tracing::debug;
|
||||
|
||||
/// # `POST /_matrix/client/r0/keys/upload`
|
||||
///
|
||||
|
@ -132,6 +136,7 @@ pub async fn upload_signing_keys_route(
|
|||
master_key,
|
||||
&body.self_signing_key,
|
||||
&body.user_signing_key,
|
||||
true, // notify so that other users see the new keys
|
||||
)?;
|
||||
}
|
||||
|
||||
|
@ -151,18 +156,6 @@ pub async fn upload_signatures_route(
|
|||
let key = serde_json::to_value(key)
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid key JSON"))?;
|
||||
|
||||
let is_signed_key = match key.get("usage") {
|
||||
Some(usage) => usage
|
||||
.as_array()
|
||||
.map(|usage| !usage.contains(&json!("master")))
|
||||
.unwrap_or(false),
|
||||
None => true,
|
||||
};
|
||||
|
||||
if !is_signed_key {
|
||||
continue;
|
||||
}
|
||||
|
||||
for signature in key
|
||||
.get("signatures")
|
||||
.ok_or(Error::BadRequest(
|
||||
|
@ -323,15 +316,17 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(master_key) = services()
|
||||
.users
|
||||
.get_master_key(user_id, &allowed_signatures)?
|
||||
if let Some(master_key) =
|
||||
services()
|
||||
.users
|
||||
.get_master_key(sender_user, user_id, &allowed_signatures)?
|
||||
{
|
||||
master_keys.insert(user_id.to_owned(), master_key);
|
||||
}
|
||||
if let Some(self_signing_key) = services()
|
||||
.users
|
||||
.get_self_signing_key(user_id, &allowed_signatures)?
|
||||
if let Some(self_signing_key) =
|
||||
services()
|
||||
.users
|
||||
.get_self_signing_key(sender_user, user_id, &allowed_signatures)?
|
||||
{
|
||||
self_signing_keys.insert(user_id.to_owned(), self_signing_key);
|
||||
}
|
||||
|
@ -344,36 +339,99 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
|
|||
|
||||
let mut failures = BTreeMap::new();
|
||||
|
||||
let back_off = |id| async {
|
||||
match services()
|
||||
.globals
|
||||
.bad_query_ratelimiter
|
||||
.write()
|
||||
.await
|
||||
.entry(id)
|
||||
{
|
||||
hash_map::Entry::Vacant(e) => {
|
||||
e.insert((Instant::now(), 1));
|
||||
}
|
||||
hash_map::Entry::Occupied(mut e) => *e.get_mut() = (Instant::now(), e.get().1 + 1),
|
||||
}
|
||||
};
|
||||
|
||||
let mut futures: FuturesUnordered<_> = get_over_federation
|
||||
.into_iter()
|
||||
.map(|(server, vec)| async move {
|
||||
if let Some((time, tries)) = services()
|
||||
.globals
|
||||
.bad_query_ratelimiter
|
||||
.read()
|
||||
.await
|
||||
.get(server)
|
||||
{
|
||||
// Exponential backoff
|
||||
let mut min_elapsed_duration = Duration::from_secs(30) * (*tries) * (*tries);
|
||||
if min_elapsed_duration > Duration::from_secs(60 * 60 * 24) {
|
||||
min_elapsed_duration = Duration::from_secs(60 * 60 * 24);
|
||||
}
|
||||
|
||||
if time.elapsed() < min_elapsed_duration {
|
||||
debug!("Backing off query from {:?}", server);
|
||||
return (
|
||||
server,
|
||||
Err(Error::BadServerResponse("bad query, still backing off")),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut device_keys_input_fed = BTreeMap::new();
|
||||
for (user_id, keys) in vec {
|
||||
device_keys_input_fed.insert(user_id.to_owned(), keys.clone());
|
||||
}
|
||||
(
|
||||
server,
|
||||
services()
|
||||
.sending
|
||||
.send_federation_request(
|
||||
tokio::time::timeout(
|
||||
Duration::from_secs(25),
|
||||
services().sending.send_federation_request(
|
||||
server,
|
||||
federation::keys::get_keys::v1::Request {
|
||||
device_keys: device_keys_input_fed,
|
||||
},
|
||||
)
|
||||
.await,
|
||||
),
|
||||
)
|
||||
.await
|
||||
.map_err(|_e| Error::BadServerResponse("Query took too long")),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
while let Some((server, response)) = futures.next().await {
|
||||
match response {
|
||||
Ok(response) => {
|
||||
master_keys.extend(response.master_keys);
|
||||
Ok(Ok(response)) => {
|
||||
for (user, masterkey) in response.master_keys {
|
||||
let (master_key_id, mut master_key) =
|
||||
services().users.parse_master_key(&user, &masterkey)?;
|
||||
|
||||
if let Some(our_master_key) = services().users.get_key(
|
||||
&master_key_id,
|
||||
sender_user,
|
||||
&user,
|
||||
&allowed_signatures,
|
||||
)? {
|
||||
let (_, our_master_key) =
|
||||
services().users.parse_master_key(&user, &our_master_key)?;
|
||||
master_key.signatures.extend(our_master_key.signatures);
|
||||
}
|
||||
let json = serde_json::to_value(master_key).expect("to_value always works");
|
||||
let raw = serde_json::from_value(json).expect("Raw::from_value always works");
|
||||
services().users.add_cross_signing_keys(
|
||||
&user, &raw, &None, &None,
|
||||
false, // Dont notify. A notification would trigger another key request resulting in an endless loop
|
||||
)?;
|
||||
master_keys.insert(user, raw);
|
||||
}
|
||||
|
||||
self_signing_keys.extend(response.self_signing_keys);
|
||||
device_keys.extend(response.device_keys);
|
||||
}
|
||||
Err(_e) => {
|
||||
_ => {
|
||||
back_off(server.to_owned()).await;
|
||||
|
||||
failures.insert(server.to_string(), json!({}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use crate::{service::media::FileMeta, services, utils, Error, Result, Ruma};
|
||||
use ruma::api::client::{
|
||||
error::ErrorKind,
|
||||
|
@ -49,7 +51,7 @@ pub async fn create_content_route(
|
|||
.await?;
|
||||
|
||||
Ok(create_content::v3::Response {
|
||||
content_uri: mxc.try_into().expect("Invalid mxc:// URI"),
|
||||
content_uri: mxc.into(),
|
||||
blurhash: None,
|
||||
})
|
||||
}
|
||||
|
@ -67,6 +69,8 @@ pub async fn get_remote_content(
|
|||
allow_remote: false,
|
||||
server_name: server_name.to_owned(),
|
||||
media_id,
|
||||
timeout_ms: Duration::from_secs(20),
|
||||
allow_redirect: false,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
@ -194,6 +198,8 @@ pub async fn get_content_thumbnail_route(
|
|||
method: body.method.clone(),
|
||||
server_name: body.server_name.clone(),
|
||||
media_id: body.media_id.clone(),
|
||||
timeout_ms: Duration::from_secs(20),
|
||||
allow_redirect: false,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
|
|
@ -17,7 +17,7 @@ use ruma::{
|
|||
member::{MembershipState, RoomMemberEventContent},
|
||||
power_levels::RoomPowerLevelsEventContent,
|
||||
},
|
||||
RoomEventType, StateEventType,
|
||||
StateEventType, TimelineEventType,
|
||||
},
|
||||
serde::Base64,
|
||||
state_res, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId,
|
||||
|
@ -26,9 +26,10 @@ use ruma::{
|
|||
use serde_json::value::{to_raw_value, RawValue as RawJsonValue};
|
||||
use std::{
|
||||
collections::{hash_map::Entry, BTreeMap, HashMap, HashSet},
|
||||
sync::{Arc, RwLock},
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use crate::{
|
||||
|
@ -64,7 +65,12 @@ pub async fn join_room_by_id_route(
|
|||
.map(|user| user.server_name().to_owned()),
|
||||
);
|
||||
|
||||
servers.push(body.room_id.server_name().to_owned());
|
||||
servers.push(
|
||||
body.room_id
|
||||
.server_name()
|
||||
.expect("Room IDs should always have a server name")
|
||||
.into(),
|
||||
);
|
||||
|
||||
join_room_by_id_helper(
|
||||
body.sender_user.as_deref(),
|
||||
|
@ -105,13 +111,19 @@ pub async fn join_room_by_id_or_alias_route(
|
|||
.map(|user| user.server_name().to_owned()),
|
||||
);
|
||||
|
||||
servers.push(room_id.server_name().to_owned());
|
||||
servers.push(
|
||||
room_id
|
||||
.server_name()
|
||||
.expect("Room IDs should always have a server name")
|
||||
.into(),
|
||||
);
|
||||
|
||||
(servers, room_id)
|
||||
}
|
||||
Err(room_alias) => {
|
||||
let response = get_alias_helper(room_alias).await?;
|
||||
|
||||
(response.servers.into_iter().collect(), response.room_id)
|
||||
(response.servers, response.room_id)
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -201,24 +213,28 @@ pub async fn kick_user_route(
|
|||
.globals
|
||||
.roomid_mutex_state
|
||||
.write()
|
||||
.unwrap()
|
||||
.await
|
||||
.entry(body.room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let state_lock = mutex_state.lock().await;
|
||||
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomMember,
|
||||
content: to_raw_value(&event).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(body.user_id.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&state_lock,
|
||||
)?;
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
content: to_raw_value(&event).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(body.user_id.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
|
@ -254,6 +270,7 @@ pub async fn ban_user_route(body: Ruma<ban_user::v3::Request>) -> Result<ban_use
|
|||
serde_json::from_str(event.content.get())
|
||||
.map(|event: RoomMemberEventContent| RoomMemberEventContent {
|
||||
membership: MembershipState::Ban,
|
||||
join_authorized_via_users_server: None,
|
||||
..event
|
||||
})
|
||||
.map_err(|_| Error::bad_database("Invalid member event in database."))
|
||||
|
@ -265,24 +282,28 @@ pub async fn ban_user_route(body: Ruma<ban_user::v3::Request>) -> Result<ban_use
|
|||
.globals
|
||||
.roomid_mutex_state
|
||||
.write()
|
||||
.unwrap()
|
||||
.await
|
||||
.entry(body.room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let state_lock = mutex_state.lock().await;
|
||||
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomMember,
|
||||
content: to_raw_value(&event).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(body.user_id.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&state_lock,
|
||||
)?;
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
content: to_raw_value(&event).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(body.user_id.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
|
@ -323,24 +344,28 @@ pub async fn unban_user_route(
|
|||
.globals
|
||||
.roomid_mutex_state
|
||||
.write()
|
||||
.unwrap()
|
||||
.await
|
||||
.entry(body.room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let state_lock = mutex_state.lock().await;
|
||||
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomMember,
|
||||
content: to_raw_value(&event).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(body.user_id.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&state_lock,
|
||||
)?;
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
content: to_raw_value(&event).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(body.user_id.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
|
@ -399,7 +424,7 @@ pub async fn get_member_events_route(
|
|||
if !services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_state_events(&sender_user, &body.room_id)?
|
||||
.user_can_see_state_events(sender_user, &body.room_id)?
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
|
@ -434,7 +459,7 @@ pub async fn joined_members_route(
|
|||
if !services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_state_events(&sender_user, &body.room_id)?
|
||||
.user_can_see_state_events(sender_user, &body.room_id)?
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
|
@ -478,7 +503,7 @@ async fn join_room_by_id_helper(
|
|||
.globals
|
||||
.roomid_mutex_state
|
||||
.write()
|
||||
.unwrap()
|
||||
.await
|
||||
.entry(room_id.to_owned())
|
||||
.or_default(),
|
||||
);
|
||||
|
@ -590,6 +615,7 @@ async fn join_room_by_id_helper(
|
|||
room_id: room_id.to_owned(),
|
||||
event_id: event_id.to_owned(),
|
||||
pdu: PduEvent::convert_to_outgoing_federation_event(join_event.clone()),
|
||||
omit_members: false,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
@ -597,7 +623,7 @@ async fn join_room_by_id_helper(
|
|||
info!("send_join finished");
|
||||
|
||||
if let Some(signed_raw) = &send_join_response.room_state.event {
|
||||
info!("There is a signed event. This room is probably using restricted joins");
|
||||
info!("There is a signed event. This room is probably using restricted joins. Adding signature to our event");
|
||||
let (signed_event_id, signed_value) =
|
||||
match gen_event_id_canonical_json(signed_raw, &room_version_id) {
|
||||
Ok(t) => t,
|
||||
|
@ -617,7 +643,7 @@ async fn join_room_by_id_helper(
|
|||
));
|
||||
}
|
||||
|
||||
if let Ok(signature) = signed_value["signatures"]
|
||||
match signed_value["signatures"]
|
||||
.as_object()
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
|
@ -628,18 +654,20 @@ async fn join_room_by_id_helper(
|
|||
ErrorKind::InvalidParam,
|
||||
"Server did not send its signature",
|
||||
))
|
||||
})
|
||||
{
|
||||
join_event
|
||||
.get_mut("signatures")
|
||||
.expect("we created a valid pdu")
|
||||
.as_object_mut()
|
||||
.expect("we created a valid pdu")
|
||||
.insert(remote_server.to_string(), signature.clone());
|
||||
} else {
|
||||
warn!(
|
||||
"Server {remote_server} sent invalid signature in sendjoin signatures for event {signed_value:?}",
|
||||
);
|
||||
}) {
|
||||
Ok(signature) => {
|
||||
join_event
|
||||
.get_mut("signatures")
|
||||
.expect("we created a valid pdu")
|
||||
.as_object_mut()
|
||||
.expect("we created a valid pdu")
|
||||
.insert(remote_server.to_string(), signature.clone());
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Server {remote_server} sent invalid signature in sendjoin signatures for event {signed_value:?}: {e:?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -666,13 +694,13 @@ async fn join_room_by_id_helper(
|
|||
.iter()
|
||||
.map(|pdu| validate_and_add_event_id(pdu, &room_version_id, &pub_key_map))
|
||||
{
|
||||
let (event_id, value) = match result {
|
||||
let (event_id, value) = match result.await {
|
||||
Ok(t) => t,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
let pdu = PduEvent::from_id_val(&event_id, value.clone()).map_err(|e| {
|
||||
warn!("{:?}: {}", value, e);
|
||||
warn!("Invalid PDU in send_join response: {} {:?}", e, value);
|
||||
Error::BadServerResponse("Invalid PDU in send_join response.")
|
||||
})?;
|
||||
|
||||
|
@ -696,7 +724,7 @@ async fn join_room_by_id_helper(
|
|||
.iter()
|
||||
.map(|pdu| validate_and_add_event_id(pdu, &room_version_id, &pub_key_map))
|
||||
{
|
||||
let (event_id, value) = match result {
|
||||
let (event_id, value) = match result.await {
|
||||
Ok(t) => t,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
@ -708,7 +736,7 @@ async fn join_room_by_id_helper(
|
|||
}
|
||||
|
||||
info!("Running send_join auth check");
|
||||
if !state_res::event_auth::auth_check(
|
||||
let authenticated = state_res::event_auth::auth_check(
|
||||
&state_res::RoomVersion::new(&room_version_id).expect("room version is supported"),
|
||||
&parsed_join_pdu,
|
||||
None::<PduEvent>, // TODO: third party invite
|
||||
|
@ -731,7 +759,9 @@ async fn join_room_by_id_helper(
|
|||
.map_err(|e| {
|
||||
warn!("Auth check failed: {e}");
|
||||
Error::BadRequest(ErrorKind::InvalidParam, "Auth check failed")
|
||||
})? {
|
||||
})?;
|
||||
|
||||
if !authenticated {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Auth check failed",
|
||||
|
@ -741,15 +771,17 @@ async fn join_room_by_id_helper(
|
|||
info!("Saving state from send_join");
|
||||
let (statehash_before_join, new, removed) = services().rooms.state_compressor.save_state(
|
||||
room_id,
|
||||
state
|
||||
.into_iter()
|
||||
.map(|(k, id)| {
|
||||
services()
|
||||
.rooms
|
||||
.state_compressor
|
||||
.compress_state_event(k, &id)
|
||||
})
|
||||
.collect::<Result<_>>()?,
|
||||
Arc::new(
|
||||
state
|
||||
.into_iter()
|
||||
.map(|(k, id)| {
|
||||
services()
|
||||
.rooms
|
||||
.state_compressor
|
||||
.compress_state_event(k, &id)
|
||||
})
|
||||
.collect::<Result<_>>()?,
|
||||
),
|
||||
)?;
|
||||
|
||||
services()
|
||||
|
@ -766,12 +798,16 @@ async fn join_room_by_id_helper(
|
|||
let statehash_after_join = services().rooms.state.append_to_state(&parsed_join_pdu)?;
|
||||
|
||||
info!("Appending new room join event");
|
||||
services().rooms.timeline.append_pdu(
|
||||
&parsed_join_pdu,
|
||||
join_event,
|
||||
vec![(*parsed_join_pdu.event_id).to_owned()],
|
||||
&state_lock,
|
||||
)?;
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.append_pdu(
|
||||
&parsed_join_pdu,
|
||||
join_event,
|
||||
vec![(*parsed_join_pdu.event_id).to_owned()],
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!("Setting final room state for new room");
|
||||
// We set the room state after inserting the pdu, so that we never have a moment in time
|
||||
|
@ -884,23 +920,34 @@ async fn join_room_by_id_helper(
|
|||
};
|
||||
|
||||
// Try normal join first
|
||||
let error = match services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomMember,
|
||||
content: to_raw_value(&event).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(sender_user.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
room_id,
|
||||
&state_lock,
|
||||
) {
|
||||
let error = match services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
content: to_raw_value(&event).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(sender_user.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_event_id) => return Ok(join_room_by_id::v3::Response::new(room_id.to_owned())),
|
||||
Err(e) => e,
|
||||
};
|
||||
|
||||
if !restriction_rooms.is_empty() {
|
||||
if !restriction_rooms.is_empty()
|
||||
&& servers
|
||||
.iter()
|
||||
.filter(|s| *s != services().globals.server_name())
|
||||
.count()
|
||||
> 0
|
||||
{
|
||||
info!(
|
||||
"We couldn't do the join locally, maybe federation can help to satisfy the restricted join requirements"
|
||||
);
|
||||
|
@ -996,6 +1043,7 @@ async fn join_room_by_id_helper(
|
|||
room_id: room_id.to_owned(),
|
||||
event_id: event_id.to_owned(),
|
||||
pdu: PduEvent::convert_to_outgoing_federation_event(join_event.clone()),
|
||||
omit_members: false,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
@ -1084,7 +1132,7 @@ async fn make_join_request(
|
|||
make_join_response_and_server
|
||||
}
|
||||
|
||||
fn validate_and_add_event_id(
|
||||
async fn validate_and_add_event_id(
|
||||
pdu: &RawJsonValue,
|
||||
room_version: &RoomVersionId,
|
||||
pub_key_map: &RwLock<BTreeMap<String, BTreeMap<String, Base64>>>,
|
||||
|
@ -1100,24 +1148,26 @@ fn validate_and_add_event_id(
|
|||
))
|
||||
.expect("ruma's reference hashes are valid event ids");
|
||||
|
||||
let back_off = |id| match services()
|
||||
.globals
|
||||
.bad_event_ratelimiter
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(id)
|
||||
{
|
||||
Entry::Vacant(e) => {
|
||||
e.insert((Instant::now(), 1));
|
||||
let back_off = |id| async {
|
||||
match services()
|
||||
.globals
|
||||
.bad_event_ratelimiter
|
||||
.write()
|
||||
.await
|
||||
.entry(id)
|
||||
{
|
||||
Entry::Vacant(e) => {
|
||||
e.insert((Instant::now(), 1));
|
||||
}
|
||||
Entry::Occupied(mut e) => *e.get_mut() = (Instant::now(), e.get().1 + 1),
|
||||
}
|
||||
Entry::Occupied(mut e) => *e.get_mut() = (Instant::now(), e.get().1 + 1),
|
||||
};
|
||||
|
||||
if let Some((time, tries)) = services()
|
||||
.globals
|
||||
.bad_event_ratelimiter
|
||||
.read()
|
||||
.unwrap()
|
||||
.await
|
||||
.get(&event_id)
|
||||
{
|
||||
// Exponential backoff
|
||||
|
@ -1132,15 +1182,10 @@ fn validate_and_add_event_id(
|
|||
}
|
||||
}
|
||||
|
||||
if let Err(e) = ruma::signatures::verify_event(
|
||||
&*pub_key_map
|
||||
.read()
|
||||
.map_err(|_| Error::bad_database("RwLock is poisoned."))?,
|
||||
&value,
|
||||
room_version,
|
||||
) {
|
||||
if let Err(e) = ruma::signatures::verify_event(&*pub_key_map.read().await, &value, room_version)
|
||||
{
|
||||
warn!("Event {} failed verification {:?} {}", event_id, pdu, e);
|
||||
back_off(event_id);
|
||||
back_off(event_id).await;
|
||||
return Err(Error::BadServerResponse("Event failed verification."));
|
||||
}
|
||||
|
||||
|
@ -1166,7 +1211,7 @@ pub(crate) async fn invite_helper<'a>(
|
|||
.globals
|
||||
.roomid_mutex_state
|
||||
.write()
|
||||
.unwrap()
|
||||
.await
|
||||
.entry(room_id.to_owned())
|
||||
.or_default(),
|
||||
);
|
||||
|
@ -1186,7 +1231,7 @@ pub(crate) async fn invite_helper<'a>(
|
|||
|
||||
let (pdu, pdu_json) = services().rooms.timeline.create_hash_and_sign_event(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomMember,
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
content,
|
||||
unsigned: None,
|
||||
state_key: Some(user_id.to_string()),
|
||||
|
@ -1287,34 +1332,38 @@ pub(crate) async fn invite_helper<'a>(
|
|||
.globals
|
||||
.roomid_mutex_state
|
||||
.write()
|
||||
.unwrap()
|
||||
.await
|
||||
.entry(room_id.to_owned())
|
||||
.or_default(),
|
||||
);
|
||||
let state_lock = mutex_state.lock().await;
|
||||
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
membership: MembershipState::Invite,
|
||||
displayname: services().users.displayname(user_id)?,
|
||||
avatar_url: services().users.avatar_url(user_id)?,
|
||||
is_direct: Some(is_direct),
|
||||
third_party_invite: None,
|
||||
blurhash: services().users.blurhash(user_id)?,
|
||||
reason,
|
||||
join_authorized_via_users_server: None,
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(user_id.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
room_id,
|
||||
&state_lock,
|
||||
)?;
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
membership: MembershipState::Invite,
|
||||
displayname: services().users.displayname(user_id)?,
|
||||
avatar_url: services().users.avatar_url(user_id)?,
|
||||
is_direct: Some(is_direct),
|
||||
third_party_invite: None,
|
||||
blurhash: services().users.blurhash(user_id)?,
|
||||
reason,
|
||||
join_authorized_via_users_server: None,
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(user_id.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
|
@ -1351,7 +1400,7 @@ pub async fn leave_all_rooms(user_id: &UserId) -> Result<()> {
|
|||
pub async fn leave_room(user_id: &UserId, room_id: &RoomId, reason: Option<String>) -> Result<()> {
|
||||
// Ask a remote server if we don't have this room
|
||||
if !services().rooms.metadata.exists(room_id)?
|
||||
&& room_id.server_name() != services().globals.server_name()
|
||||
&& room_id.server_name() != Some(services().globals.server_name())
|
||||
{
|
||||
if let Err(e) = remote_leave_room(user_id, room_id).await {
|
||||
warn!("Failed to leave room {} remotely: {}", user_id, e);
|
||||
|
@ -1382,7 +1431,7 @@ pub async fn leave_room(user_id: &UserId, room_id: &RoomId, reason: Option<Strin
|
|||
.globals
|
||||
.roomid_mutex_state
|
||||
.write()
|
||||
.unwrap()
|
||||
.await
|
||||
.entry(room_id.to_owned())
|
||||
.or_default(),
|
||||
);
|
||||
|
@ -1417,19 +1466,24 @@ pub async fn leave_room(user_id: &UserId, room_id: &RoomId, reason: Option<Strin
|
|||
|
||||
event.membership = MembershipState::Leave;
|
||||
event.reason = reason;
|
||||
event.join_authorized_via_users_server = None;
|
||||
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomMember,
|
||||
content: to_raw_value(&event).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(user_id.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
user_id,
|
||||
room_id,
|
||||
&state_lock,
|
||||
)?;
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
content: to_raw_value(&event).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(user_id.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
user_id,
|
||||
room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -7,7 +7,7 @@ use ruma::{
|
|||
error::ErrorKind,
|
||||
message::{get_message_events, send_message_event},
|
||||
},
|
||||
events::{RoomEventType, StateEventType},
|
||||
events::{StateEventType, TimelineEventType},
|
||||
};
|
||||
use std::{
|
||||
collections::{BTreeMap, HashSet},
|
||||
|
@ -32,14 +32,14 @@ pub async fn send_message_event_route(
|
|||
.globals
|
||||
.roomid_mutex_state
|
||||
.write()
|
||||
.unwrap()
|
||||
.await
|
||||
.entry(body.room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let state_lock = mutex_state.lock().await;
|
||||
|
||||
// Forbid m.room.encrypted if encryption is disabled
|
||||
if RoomEventType::RoomEncrypted == body.event_type.to_string().into()
|
||||
if TimelineEventType::RoomEncrypted == body.event_type.to_string().into()
|
||||
&& !services().globals.allow_encryption()
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
|
@ -73,19 +73,23 @@ pub async fn send_message_event_route(
|
|||
let mut unsigned = BTreeMap::new();
|
||||
unsigned.insert("transaction_id".to_owned(), body.txn_id.to_string().into());
|
||||
|
||||
let event_id = services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: body.event_type.to_string().into(),
|
||||
content: serde_json::from_str(body.body.body.json().get())
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?,
|
||||
unsigned: Some(unsigned),
|
||||
state_key: None,
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&state_lock,
|
||||
)?;
|
||||
let event_id = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: body.event_type.to_string().into(),
|
||||
content: serde_json::from_str(body.body.body.json().get())
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?,
|
||||
unsigned: Some(unsigned),
|
||||
state_key: None,
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
services().transaction_ids.add_txnid(
|
||||
sender_user,
|
||||
|
@ -116,25 +120,23 @@ pub async fn get_message_events_route(
|
|||
let from = match body.from.clone() {
|
||||
Some(from) => PduCount::try_from_string(&from)?,
|
||||
None => match body.dir {
|
||||
ruma::api::client::Direction::Forward => PduCount::min(),
|
||||
ruma::api::client::Direction::Backward => PduCount::max(),
|
||||
ruma::api::Direction::Forward => PduCount::min(),
|
||||
ruma::api::Direction::Backward => PduCount::max(),
|
||||
},
|
||||
};
|
||||
|
||||
let to = body
|
||||
.to
|
||||
.as_ref()
|
||||
.and_then(|t| PduCount::try_from_string(&t).ok());
|
||||
.and_then(|t| PduCount::try_from_string(t).ok());
|
||||
|
||||
services().rooms.lazy_loading.lazy_load_confirm_delivery(
|
||||
sender_user,
|
||||
sender_device,
|
||||
&body.room_id,
|
||||
from,
|
||||
)?;
|
||||
services()
|
||||
.rooms
|
||||
.lazy_loading
|
||||
.lazy_load_confirm_delivery(sender_user, sender_device, &body.room_id, from)
|
||||
.await?;
|
||||
|
||||
// Use limit or else 10
|
||||
let limit = body.limit.try_into().map_or(10_usize, |l: u32| l as usize);
|
||||
let limit = u64::from(body.limit).min(100) as usize;
|
||||
|
||||
let next_token;
|
||||
|
||||
|
@ -143,7 +145,7 @@ pub async fn get_message_events_route(
|
|||
let mut lazy_loaded = HashSet::new();
|
||||
|
||||
match body.dir {
|
||||
ruma::api::client::Direction::Forward => {
|
||||
ruma::api::Direction::Forward => {
|
||||
let events_after: Vec<_> = services()
|
||||
.rooms
|
||||
.timeline
|
||||
|
@ -187,7 +189,7 @@ pub async fn get_message_events_route(
|
|||
resp.end = next_token.map(|count| count.stringify());
|
||||
resp.chunk = events_after;
|
||||
}
|
||||
ruma::api::client::Direction::Backward => {
|
||||
ruma::api::Direction::Backward => {
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
|
|
|
@ -16,14 +16,17 @@ mod profile;
|
|||
mod push;
|
||||
mod read_marker;
|
||||
mod redact;
|
||||
mod relations;
|
||||
mod report;
|
||||
mod room;
|
||||
mod search;
|
||||
mod session;
|
||||
mod space;
|
||||
mod state;
|
||||
mod sync;
|
||||
mod tag;
|
||||
mod thirdparty;
|
||||
mod threads;
|
||||
mod to_device;
|
||||
mod typing;
|
||||
mod unversioned;
|
||||
|
@ -48,14 +51,17 @@ pub use profile::*;
|
|||
pub use push::*;
|
||||
pub use read_marker::*;
|
||||
pub use redact::*;
|
||||
pub use relations::*;
|
||||
pub use report::*;
|
||||
pub use room::*;
|
||||
pub use search::*;
|
||||
pub use session::*;
|
||||
pub use space::*;
|
||||
pub use state::*;
|
||||
pub use sync::*;
|
||||
pub use tag::*;
|
||||
pub use thirdparty::*;
|
||||
pub use threads::*;
|
||||
pub use to_device::*;
|
||||
pub use typing::*;
|
||||
pub use unversioned::*;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use crate::{services, utils, Result, Ruma};
|
||||
use ruma::api::client::presence::{get_presence, set_presence};
|
||||
use crate::{services, utils, Error, Result, Ruma};
|
||||
use ruma::api::client::{
|
||||
error::ErrorKind,
|
||||
presence::{get_presence, set_presence},
|
||||
};
|
||||
use std::time::Duration;
|
||||
|
||||
/// # `PUT /_matrix/client/r0/presence/{userId}/status`
|
||||
|
@ -79,6 +82,9 @@ pub async fn get_presence_route(
|
|||
presence: presence.content.presence,
|
||||
})
|
||||
} else {
|
||||
todo!();
|
||||
Err(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"Presence state for this user was not found",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use ruma::{
|
|||
},
|
||||
federation::{self, query::get_profile_information::v1::ProfileField},
|
||||
},
|
||||
events::{room::member::RoomMemberEventContent, RoomEventType, StateEventType},
|
||||
events::{room::member::RoomMemberEventContent, StateEventType, TimelineEventType},
|
||||
};
|
||||
use serde_json::value::to_raw_value;
|
||||
use std::sync::Arc;
|
||||
|
@ -37,9 +37,10 @@ pub async fn set_displayname_route(
|
|||
.map(|room_id| {
|
||||
Ok::<_, Error>((
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomMember,
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
displayname: body.displayname.clone(),
|
||||
join_authorized_via_users_server: None,
|
||||
..serde_json::from_str(
|
||||
services()
|
||||
.rooms
|
||||
|
@ -77,18 +78,17 @@ pub async fn set_displayname_route(
|
|||
.globals
|
||||
.roomid_mutex_state
|
||||
.write()
|
||||
.unwrap()
|
||||
.await
|
||||
.entry(room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let state_lock = mutex_state.lock().await;
|
||||
|
||||
let _ = services().rooms.timeline.build_and_append_pdu(
|
||||
pdu_builder,
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
);
|
||||
let _ = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(pdu_builder, sender_user, &room_id, &state_lock)
|
||||
.await;
|
||||
|
||||
// Presence update
|
||||
services().rooms.edus.presence.update_presence(
|
||||
|
@ -172,9 +172,10 @@ pub async fn set_avatar_url_route(
|
|||
.map(|room_id| {
|
||||
Ok::<_, Error>((
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomMember,
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
avatar_url: body.avatar_url.clone(),
|
||||
join_authorized_via_users_server: None,
|
||||
..serde_json::from_str(
|
||||
services()
|
||||
.rooms
|
||||
|
@ -212,18 +213,17 @@ pub async fn set_avatar_url_route(
|
|||
.globals
|
||||
.roomid_mutex_state
|
||||
.write()
|
||||
.unwrap()
|
||||
.await
|
||||
.entry(room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let state_lock = mutex_state.lock().await;
|
||||
|
||||
let _ = services().rooms.timeline.build_and_append_pdu(
|
||||
pdu_builder,
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
);
|
||||
let _ = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(pdu_builder, sender_user, &room_id, &state_lock)
|
||||
.await;
|
||||
|
||||
// Presence update
|
||||
services().rooms.edus.presence.update_presence(
|
||||
|
|
|
@ -5,11 +5,11 @@ use ruma::{
|
|||
push::{
|
||||
delete_pushrule, get_pushers, get_pushrule, get_pushrule_actions, get_pushrule_enabled,
|
||||
get_pushrules_all, set_pusher, set_pushrule, set_pushrule_actions,
|
||||
set_pushrule_enabled, RuleKind, RuleScope,
|
||||
set_pushrule_enabled, RuleScope,
|
||||
},
|
||||
},
|
||||
events::{push_rules::PushRulesEvent, GlobalAccountDataEventType},
|
||||
push::{ConditionalPushRuleInit, NewPushRule, PatternedPushRuleInit, SimplePushRuleInit},
|
||||
push::{InsertPushRuleError, RemovePushRuleError},
|
||||
};
|
||||
|
||||
/// # `GET /_matrix/client/r0/pushrules`
|
||||
|
@ -65,30 +65,10 @@ pub async fn get_pushrule_route(
|
|||
.map_err(|_| Error::bad_database("Invalid account data event in db."))?
|
||||
.content;
|
||||
|
||||
let global = account_data.global;
|
||||
let rule = match body.kind {
|
||||
RuleKind::Override => global
|
||||
.override_
|
||||
.get(body.rule_id.as_str())
|
||||
.map(|rule| rule.clone().into()),
|
||||
RuleKind::Underride => global
|
||||
.underride
|
||||
.get(body.rule_id.as_str())
|
||||
.map(|rule| rule.clone().into()),
|
||||
RuleKind::Sender => global
|
||||
.sender
|
||||
.get(body.rule_id.as_str())
|
||||
.map(|rule| rule.clone().into()),
|
||||
RuleKind::Room => global
|
||||
.room
|
||||
.get(body.rule_id.as_str())
|
||||
.map(|rule| rule.clone().into()),
|
||||
RuleKind::Content => global
|
||||
.content
|
||||
.get(body.rule_id.as_str())
|
||||
.map(|rule| rule.clone().into()),
|
||||
_ => None,
|
||||
};
|
||||
let rule = account_data
|
||||
.global
|
||||
.get(body.kind.clone(), &body.rule_id)
|
||||
.map(Into::into);
|
||||
|
||||
if let Some(rule) = rule {
|
||||
Ok(get_pushrule::v3::Response { rule })
|
||||
|
@ -131,66 +111,36 @@ pub async fn set_pushrule_route(
|
|||
let mut account_data = serde_json::from_str::<PushRulesEvent>(event.get())
|
||||
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
|
||||
|
||||
let global = &mut account_data.content.global;
|
||||
match body.rule {
|
||||
NewPushRule::Override(rule) => {
|
||||
global.override_.replace(
|
||||
ConditionalPushRuleInit {
|
||||
actions: rule.actions,
|
||||
default: false,
|
||||
enabled: true,
|
||||
rule_id: rule.rule_id,
|
||||
conditions: rule.conditions,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
NewPushRule::Underride(rule) => {
|
||||
global.underride.replace(
|
||||
ConditionalPushRuleInit {
|
||||
actions: rule.actions,
|
||||
default: false,
|
||||
enabled: true,
|
||||
rule_id: rule.rule_id,
|
||||
conditions: rule.conditions,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
NewPushRule::Sender(rule) => {
|
||||
global.sender.replace(
|
||||
SimplePushRuleInit {
|
||||
actions: rule.actions,
|
||||
default: false,
|
||||
enabled: true,
|
||||
rule_id: rule.rule_id,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
NewPushRule::Room(rule) => {
|
||||
global.room.replace(
|
||||
SimplePushRuleInit {
|
||||
actions: rule.actions,
|
||||
default: false,
|
||||
enabled: true,
|
||||
rule_id: rule.rule_id,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
NewPushRule::Content(rule) => {
|
||||
global.content.replace(
|
||||
PatternedPushRuleInit {
|
||||
actions: rule.actions,
|
||||
default: false,
|
||||
enabled: true,
|
||||
rule_id: rule.rule_id,
|
||||
pattern: rule.pattern,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
if let Err(error) = account_data.content.global.insert(
|
||||
body.rule.clone(),
|
||||
body.after.as_deref(),
|
||||
body.before.as_deref(),
|
||||
) {
|
||||
let err = match error {
|
||||
InsertPushRuleError::ServerDefaultRuleId => Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Rule IDs starting with a dot are reserved for server-default rules.",
|
||||
),
|
||||
InsertPushRuleError::InvalidRuleId => Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Rule ID containing invalid characters.",
|
||||
),
|
||||
InsertPushRuleError::RelativeToServerDefaultRule => Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Can't place a push rule relatively to a server-default rule.",
|
||||
),
|
||||
InsertPushRuleError::UnknownRuleId => Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"The before or after rule could not be found.",
|
||||
),
|
||||
InsertPushRuleError::BeforeHigherThanAfter => Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"The before rule has a higher priority than the after rule.",
|
||||
),
|
||||
_ => Error::BadRequest(ErrorKind::InvalidParam, "Invalid data."),
|
||||
};
|
||||
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
services().account_data.update(
|
||||
|
@ -235,33 +185,15 @@ pub async fn get_pushrule_actions_route(
|
|||
.content;
|
||||
|
||||
let global = account_data.global;
|
||||
let actions = match body.kind {
|
||||
RuleKind::Override => global
|
||||
.override_
|
||||
.get(body.rule_id.as_str())
|
||||
.map(|rule| rule.actions.clone()),
|
||||
RuleKind::Underride => global
|
||||
.underride
|
||||
.get(body.rule_id.as_str())
|
||||
.map(|rule| rule.actions.clone()),
|
||||
RuleKind::Sender => global
|
||||
.sender
|
||||
.get(body.rule_id.as_str())
|
||||
.map(|rule| rule.actions.clone()),
|
||||
RuleKind::Room => global
|
||||
.room
|
||||
.get(body.rule_id.as_str())
|
||||
.map(|rule| rule.actions.clone()),
|
||||
RuleKind::Content => global
|
||||
.content
|
||||
.get(body.rule_id.as_str())
|
||||
.map(|rule| rule.actions.clone()),
|
||||
_ => None,
|
||||
};
|
||||
let actions = global
|
||||
.get(body.kind.clone(), &body.rule_id)
|
||||
.map(|rule| rule.actions().to_owned())
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"Push rule not found.",
|
||||
))?;
|
||||
|
||||
Ok(get_pushrule_actions::v3::Response {
|
||||
actions: actions.unwrap_or_default(),
|
||||
})
|
||||
Ok(get_pushrule_actions::v3::Response { actions })
|
||||
}
|
||||
|
||||
/// # `PUT /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/actions`
|
||||
|
@ -294,40 +226,17 @@ pub async fn set_pushrule_actions_route(
|
|||
let mut account_data = serde_json::from_str::<PushRulesEvent>(event.get())
|
||||
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
|
||||
|
||||
let global = &mut account_data.content.global;
|
||||
match body.kind {
|
||||
RuleKind::Override => {
|
||||
if let Some(mut rule) = global.override_.get(body.rule_id.as_str()).cloned() {
|
||||
rule.actions = body.actions.clone();
|
||||
global.override_.replace(rule);
|
||||
}
|
||||
}
|
||||
RuleKind::Underride => {
|
||||
if let Some(mut rule) = global.underride.get(body.rule_id.as_str()).cloned() {
|
||||
rule.actions = body.actions.clone();
|
||||
global.underride.replace(rule);
|
||||
}
|
||||
}
|
||||
RuleKind::Sender => {
|
||||
if let Some(mut rule) = global.sender.get(body.rule_id.as_str()).cloned() {
|
||||
rule.actions = body.actions.clone();
|
||||
global.sender.replace(rule);
|
||||
}
|
||||
}
|
||||
RuleKind::Room => {
|
||||
if let Some(mut rule) = global.room.get(body.rule_id.as_str()).cloned() {
|
||||
rule.actions = body.actions.clone();
|
||||
global.room.replace(rule);
|
||||
}
|
||||
}
|
||||
RuleKind::Content => {
|
||||
if let Some(mut rule) = global.content.get(body.rule_id.as_str()).cloned() {
|
||||
rule.actions = body.actions.clone();
|
||||
global.content.replace(rule);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
if account_data
|
||||
.content
|
||||
.global
|
||||
.set_actions(body.kind.clone(), &body.rule_id, body.actions.clone())
|
||||
.is_err()
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"Push rule not found.",
|
||||
));
|
||||
}
|
||||
|
||||
services().account_data.update(
|
||||
None,
|
||||
|
@ -370,34 +279,13 @@ pub async fn get_pushrule_enabled_route(
|
|||
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
|
||||
|
||||
let global = account_data.content.global;
|
||||
let enabled = match body.kind {
|
||||
RuleKind::Override => global
|
||||
.override_
|
||||
.iter()
|
||||
.find(|rule| rule.rule_id == body.rule_id)
|
||||
.map_or(false, |rule| rule.enabled),
|
||||
RuleKind::Underride => global
|
||||
.underride
|
||||
.iter()
|
||||
.find(|rule| rule.rule_id == body.rule_id)
|
||||
.map_or(false, |rule| rule.enabled),
|
||||
RuleKind::Sender => global
|
||||
.sender
|
||||
.iter()
|
||||
.find(|rule| rule.rule_id == body.rule_id)
|
||||
.map_or(false, |rule| rule.enabled),
|
||||
RuleKind::Room => global
|
||||
.room
|
||||
.iter()
|
||||
.find(|rule| rule.rule_id == body.rule_id)
|
||||
.map_or(false, |rule| rule.enabled),
|
||||
RuleKind::Content => global
|
||||
.content
|
||||
.iter()
|
||||
.find(|rule| rule.rule_id == body.rule_id)
|
||||
.map_or(false, |rule| rule.enabled),
|
||||
_ => false,
|
||||
};
|
||||
let enabled = global
|
||||
.get(body.kind.clone(), &body.rule_id)
|
||||
.map(|r| r.enabled())
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"Push rule not found.",
|
||||
))?;
|
||||
|
||||
Ok(get_pushrule_enabled::v3::Response { enabled })
|
||||
}
|
||||
|
@ -432,44 +320,16 @@ pub async fn set_pushrule_enabled_route(
|
|||
let mut account_data = serde_json::from_str::<PushRulesEvent>(event.get())
|
||||
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
|
||||
|
||||
let global = &mut account_data.content.global;
|
||||
match body.kind {
|
||||
RuleKind::Override => {
|
||||
if let Some(mut rule) = global.override_.get(body.rule_id.as_str()).cloned() {
|
||||
global.override_.remove(&rule);
|
||||
rule.enabled = body.enabled;
|
||||
global.override_.insert(rule);
|
||||
}
|
||||
}
|
||||
RuleKind::Underride => {
|
||||
if let Some(mut rule) = global.underride.get(body.rule_id.as_str()).cloned() {
|
||||
global.underride.remove(&rule);
|
||||
rule.enabled = body.enabled;
|
||||
global.underride.insert(rule);
|
||||
}
|
||||
}
|
||||
RuleKind::Sender => {
|
||||
if let Some(mut rule) = global.sender.get(body.rule_id.as_str()).cloned() {
|
||||
global.sender.remove(&rule);
|
||||
rule.enabled = body.enabled;
|
||||
global.sender.insert(rule);
|
||||
}
|
||||
}
|
||||
RuleKind::Room => {
|
||||
if let Some(mut rule) = global.room.get(body.rule_id.as_str()).cloned() {
|
||||
global.room.remove(&rule);
|
||||
rule.enabled = body.enabled;
|
||||
global.room.insert(rule);
|
||||
}
|
||||
}
|
||||
RuleKind::Content => {
|
||||
if let Some(mut rule) = global.content.get(body.rule_id.as_str()).cloned() {
|
||||
global.content.remove(&rule);
|
||||
rule.enabled = body.enabled;
|
||||
global.content.insert(rule);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
if account_data
|
||||
.content
|
||||
.global
|
||||
.set_enabled(body.kind.clone(), &body.rule_id, body.enabled)
|
||||
.is_err()
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"Push rule not found.",
|
||||
));
|
||||
}
|
||||
|
||||
services().account_data.update(
|
||||
|
@ -512,34 +372,23 @@ pub async fn delete_pushrule_route(
|
|||
let mut account_data = serde_json::from_str::<PushRulesEvent>(event.get())
|
||||
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
|
||||
|
||||
let global = &mut account_data.content.global;
|
||||
match body.kind {
|
||||
RuleKind::Override => {
|
||||
if let Some(rule) = global.override_.get(body.rule_id.as_str()).cloned() {
|
||||
global.override_.remove(&rule);
|
||||
if let Err(error) = account_data
|
||||
.content
|
||||
.global
|
||||
.remove(body.kind.clone(), &body.rule_id)
|
||||
{
|
||||
let err = match error {
|
||||
RemovePushRuleError::ServerDefault => Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Cannot delete a server-default pushrule.",
|
||||
),
|
||||
RemovePushRuleError::NotFound => {
|
||||
Error::BadRequest(ErrorKind::NotFound, "Push rule not found.")
|
||||
}
|
||||
}
|
||||
RuleKind::Underride => {
|
||||
if let Some(rule) = global.underride.get(body.rule_id.as_str()).cloned() {
|
||||
global.underride.remove(&rule);
|
||||
}
|
||||
}
|
||||
RuleKind::Sender => {
|
||||
if let Some(rule) = global.sender.get(body.rule_id.as_str()).cloned() {
|
||||
global.sender.remove(&rule);
|
||||
}
|
||||
}
|
||||
RuleKind::Room => {
|
||||
if let Some(rule) = global.room.get(body.rule_id.as_str()).cloned() {
|
||||
global.room.remove(&rule);
|
||||
}
|
||||
}
|
||||
RuleKind::Content => {
|
||||
if let Some(rule) = global.content.get(body.rule_id.as_str()).cloned() {
|
||||
global.content.remove(&rule);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
_ => Error::BadRequest(ErrorKind::InvalidParam, "Invalid data."),
|
||||
};
|
||||
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
services().account_data.update(
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||
use crate::{service::pdu::PduBuilder, services, Result, Ruma};
|
||||
use ruma::{
|
||||
api::client::redact::redact_event,
|
||||
events::{room::redaction::RoomRedactionEventContent, RoomEventType},
|
||||
events::{room::redaction::RoomRedactionEventContent, TimelineEventType},
|
||||
};
|
||||
|
||||
use serde_json::value::to_raw_value;
|
||||
|
@ -24,27 +24,32 @@ pub async fn redact_event_route(
|
|||
.globals
|
||||
.roomid_mutex_state
|
||||
.write()
|
||||
.unwrap()
|
||||
.await
|
||||
.entry(body.room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let state_lock = mutex_state.lock().await;
|
||||
|
||||
let event_id = services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomRedaction,
|
||||
content: to_raw_value(&RoomRedactionEventContent {
|
||||
reason: body.reason.clone(),
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: None,
|
||||
redacts: Some(body.event_id.into()),
|
||||
},
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&state_lock,
|
||||
)?;
|
||||
let event_id = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomRedaction,
|
||||
content: to_raw_value(&RoomRedactionEventContent {
|
||||
redacts: Some(body.event_id.clone()),
|
||||
reason: body.reason.clone(),
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: None,
|
||||
redacts: Some(body.event_id.into()),
|
||||
},
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
|
|
146
src/api/client_server/relations.rs
Normal file
146
src/api/client_server/relations.rs
Normal file
|
@ -0,0 +1,146 @@
|
|||
use ruma::api::client::relations::{
|
||||
get_relating_events, get_relating_events_with_rel_type,
|
||||
get_relating_events_with_rel_type_and_event_type,
|
||||
};
|
||||
|
||||
use crate::{service::rooms::timeline::PduCount, services, Result, Ruma};
|
||||
|
||||
/// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}/{relType}/{eventType}`
|
||||
pub async fn get_relating_events_with_rel_type_and_event_type_route(
|
||||
body: Ruma<get_relating_events_with_rel_type_and_event_type::v1::Request>,
|
||||
) -> Result<get_relating_events_with_rel_type_and_event_type::v1::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let from = match body.from.clone() {
|
||||
Some(from) => PduCount::try_from_string(&from)?,
|
||||
None => match ruma::api::Direction::Backward {
|
||||
// TODO: fix ruma so `body.dir` exists
|
||||
ruma::api::Direction::Forward => PduCount::min(),
|
||||
ruma::api::Direction::Backward => PduCount::max(),
|
||||
},
|
||||
};
|
||||
|
||||
let to = body
|
||||
.to
|
||||
.as_ref()
|
||||
.and_then(|t| PduCount::try_from_string(t).ok());
|
||||
|
||||
// Use limit or else 10, with maximum 100
|
||||
let limit = body
|
||||
.limit
|
||||
.and_then(|u| u32::try_from(u).ok())
|
||||
.map_or(10_usize, |u| u as usize)
|
||||
.min(100);
|
||||
|
||||
let res = services()
|
||||
.rooms
|
||||
.pdu_metadata
|
||||
.paginate_relations_with_filter(
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&body.event_id,
|
||||
Some(body.event_type.clone()),
|
||||
Some(body.rel_type.clone()),
|
||||
from,
|
||||
to,
|
||||
limit,
|
||||
)?;
|
||||
|
||||
Ok(
|
||||
get_relating_events_with_rel_type_and_event_type::v1::Response {
|
||||
chunk: res.chunk,
|
||||
next_batch: res.next_batch,
|
||||
prev_batch: res.prev_batch,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}/{relType}`
|
||||
pub async fn get_relating_events_with_rel_type_route(
|
||||
body: Ruma<get_relating_events_with_rel_type::v1::Request>,
|
||||
) -> Result<get_relating_events_with_rel_type::v1::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let from = match body.from.clone() {
|
||||
Some(from) => PduCount::try_from_string(&from)?,
|
||||
None => match ruma::api::Direction::Backward {
|
||||
// TODO: fix ruma so `body.dir` exists
|
||||
ruma::api::Direction::Forward => PduCount::min(),
|
||||
ruma::api::Direction::Backward => PduCount::max(),
|
||||
},
|
||||
};
|
||||
|
||||
let to = body
|
||||
.to
|
||||
.as_ref()
|
||||
.and_then(|t| PduCount::try_from_string(t).ok());
|
||||
|
||||
// Use limit or else 10, with maximum 100
|
||||
let limit = body
|
||||
.limit
|
||||
.and_then(|u| u32::try_from(u).ok())
|
||||
.map_or(10_usize, |u| u as usize)
|
||||
.min(100);
|
||||
|
||||
let res = services()
|
||||
.rooms
|
||||
.pdu_metadata
|
||||
.paginate_relations_with_filter(
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&body.event_id,
|
||||
None,
|
||||
Some(body.rel_type.clone()),
|
||||
from,
|
||||
to,
|
||||
limit,
|
||||
)?;
|
||||
|
||||
Ok(get_relating_events_with_rel_type::v1::Response {
|
||||
chunk: res.chunk,
|
||||
next_batch: res.next_batch,
|
||||
prev_batch: res.prev_batch,
|
||||
})
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}`
|
||||
pub async fn get_relating_events_route(
|
||||
body: Ruma<get_relating_events::v1::Request>,
|
||||
) -> Result<get_relating_events::v1::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let from = match body.from.clone() {
|
||||
Some(from) => PduCount::try_from_string(&from)?,
|
||||
None => match ruma::api::Direction::Backward {
|
||||
// TODO: fix ruma so `body.dir` exists
|
||||
ruma::api::Direction::Forward => PduCount::min(),
|
||||
ruma::api::Direction::Backward => PduCount::max(),
|
||||
},
|
||||
};
|
||||
|
||||
let to = body
|
||||
.to
|
||||
.as_ref()
|
||||
.and_then(|t| PduCount::try_from_string(t).ok());
|
||||
|
||||
// Use limit or else 10, with maximum 100
|
||||
let limit = body
|
||||
.limit
|
||||
.and_then(|u| u32::try_from(u).ok())
|
||||
.map_or(10_usize, |u| u as usize)
|
||||
.min(100);
|
||||
|
||||
services()
|
||||
.rooms
|
||||
.pdu_metadata
|
||||
.paginate_relations_with_filter(
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&body.event_id,
|
||||
None,
|
||||
None,
|
||||
from,
|
||||
to,
|
||||
limit,
|
||||
)
|
||||
}
|
|
@ -19,7 +19,7 @@ use ruma::{
|
|||
tombstone::RoomTombstoneEventContent,
|
||||
topic::RoomTopicEventContent,
|
||||
},
|
||||
RoomEventType, StateEventType,
|
||||
StateEventType, TimelineEventType,
|
||||
},
|
||||
int,
|
||||
serde::JsonObject,
|
||||
|
@ -61,7 +61,7 @@ pub async fn create_room_route(
|
|||
.globals
|
||||
.roomid_mutex_state
|
||||
.write()
|
||||
.unwrap()
|
||||
.await
|
||||
.entry(room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
|
@ -142,8 +142,9 @@ pub async fn create_room_route(
|
|||
content
|
||||
}
|
||||
None => {
|
||||
// TODO: Add correct value for v11
|
||||
let mut content = serde_json::from_str::<CanonicalJsonObject>(
|
||||
to_raw_value(&RoomCreateEventContent::new(sender_user.clone()))
|
||||
to_raw_value(&RoomCreateEventContent::new_v1(sender_user.clone()))
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid creation content"))?
|
||||
.get(),
|
||||
)
|
||||
|
@ -173,42 +174,50 @@ pub async fn create_room_route(
|
|||
}
|
||||
|
||||
// 1. The room create event
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomCreate,
|
||||
content: to_raw_value(&content).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)?;
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomCreate,
|
||||
content: to_raw_value(&content).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 2. Let the room creator join
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
membership: MembershipState::Join,
|
||||
displayname: services().users.displayname(sender_user)?,
|
||||
avatar_url: services().users.avatar_url(sender_user)?,
|
||||
is_direct: Some(body.is_direct),
|
||||
third_party_invite: None,
|
||||
blurhash: services().users.blurhash(sender_user)?,
|
||||
reason: None,
|
||||
join_authorized_via_users_server: None,
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(sender_user.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)?;
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
membership: MembershipState::Join,
|
||||
displayname: services().users.displayname(sender_user)?,
|
||||
avatar_url: services().users.avatar_url(sender_user)?,
|
||||
is_direct: Some(body.is_direct),
|
||||
third_party_invite: None,
|
||||
blurhash: services().users.blurhash(sender_user)?,
|
||||
reason: None,
|
||||
join_authorized_via_users_server: None,
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(sender_user.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 3. Power levels
|
||||
|
||||
|
@ -245,30 +254,14 @@ pub async fn create_room_route(
|
|||
}
|
||||
}
|
||||
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomPowerLevels,
|
||||
content: to_raw_value(&power_levels_content)
|
||||
.expect("to_raw_value always works on serde_json::Value"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)?;
|
||||
|
||||
// 4. Canonical room alias
|
||||
if let Some(room_alias_id) = &alias {
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomCanonicalAlias,
|
||||
content: to_raw_value(&RoomCanonicalAliasEventContent {
|
||||
alias: Some(room_alias_id.to_owned()),
|
||||
alt_aliases: vec![],
|
||||
})
|
||||
.expect("We checked that alias earlier, it must be fine"),
|
||||
event_type: TimelineEventType::RoomPowerLevels,
|
||||
content: to_raw_value(&power_levels_content)
|
||||
.expect("to_raw_value always works on serde_json::Value"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
|
@ -276,64 +269,100 @@ pub async fn create_room_route(
|
|||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 4. Canonical room alias
|
||||
if let Some(room_alias_id) = &alias {
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomCanonicalAlias,
|
||||
content: to_raw_value(&RoomCanonicalAliasEventContent {
|
||||
alias: Some(room_alias_id.to_owned()),
|
||||
alt_aliases: vec![],
|
||||
})
|
||||
.expect("We checked that alias earlier, it must be fine"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// 5. Events set by preset
|
||||
|
||||
// 5.1 Join Rules
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomJoinRules,
|
||||
content: to_raw_value(&RoomJoinRulesEventContent::new(match preset {
|
||||
RoomPreset::PublicChat => JoinRule::Public,
|
||||
// according to spec "invite" is the default
|
||||
_ => JoinRule::Invite,
|
||||
}))
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)?;
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomJoinRules,
|
||||
content: to_raw_value(&RoomJoinRulesEventContent::new(match preset {
|
||||
RoomPreset::PublicChat => JoinRule::Public,
|
||||
// according to spec "invite" is the default
|
||||
_ => JoinRule::Invite,
|
||||
}))
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 5.2 History Visibility
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomHistoryVisibility,
|
||||
content: to_raw_value(&RoomHistoryVisibilityEventContent::new(
|
||||
HistoryVisibility::Shared,
|
||||
))
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)?;
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomHistoryVisibility,
|
||||
content: to_raw_value(&RoomHistoryVisibilityEventContent::new(
|
||||
HistoryVisibility::Shared,
|
||||
))
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 5.3 Guest Access
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomGuestAccess,
|
||||
content: to_raw_value(&RoomGuestAccessEventContent::new(match preset {
|
||||
RoomPreset::PublicChat => GuestAccess::Forbidden,
|
||||
_ => GuestAccess::CanJoin,
|
||||
}))
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)?;
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomGuestAccess,
|
||||
content: to_raw_value(&RoomGuestAccessEventContent::new(match preset {
|
||||
RoomPreset::PublicChat => GuestAccess::Forbidden,
|
||||
_ => GuestAccess::CanJoin,
|
||||
}))
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 6. Events listed in initial_state
|
||||
for event in &body.initial_state {
|
||||
|
@ -346,53 +375,60 @@ pub async fn create_room_route(
|
|||
pdu_builder.state_key.get_or_insert_with(|| "".to_owned());
|
||||
|
||||
// Silently skip encryption events if they are not allowed
|
||||
if pdu_builder.event_type == RoomEventType::RoomEncryption
|
||||
if pdu_builder.event_type == TimelineEventType::RoomEncryption
|
||||
&& !services().globals.allow_encryption()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
pdu_builder,
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)?;
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(pdu_builder, sender_user, &room_id, &state_lock)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// 7. Events implied by name and topic
|
||||
if let Some(name) = &body.name {
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomName,
|
||||
content: to_raw_value(&RoomNameEventContent::new(Some(name.clone())))
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)?;
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomName,
|
||||
content: to_raw_value(&RoomNameEventContent::new(name.clone()))
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(topic) = &body.topic {
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomTopic,
|
||||
content: to_raw_value(&RoomTopicEventContent {
|
||||
topic: topic.clone(),
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)?;
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomTopic,
|
||||
content: to_raw_value(&RoomTopicEventContent {
|
||||
topic: topic.clone(),
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// 8. Events implied by invite (and TODO: invite_3pid)
|
||||
|
@ -429,7 +465,10 @@ pub async fn get_room_event_route(
|
|||
.rooms
|
||||
.timeline
|
||||
.get_pdu(&body.event_id)?
|
||||
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Event not found."))?;
|
||||
.ok_or_else(|| {
|
||||
warn!("Event not found, event ID: {:?}", &body.event_id);
|
||||
Error::BadRequest(ErrorKind::NotFound, "Event not found.")
|
||||
})?;
|
||||
|
||||
if !services().rooms.state_accessor.user_can_see_event(
|
||||
sender_user,
|
||||
|
@ -442,6 +481,9 @@ pub async fn get_room_event_route(
|
|||
));
|
||||
}
|
||||
|
||||
let mut event = (*event).clone();
|
||||
event.add_age()?;
|
||||
|
||||
Ok(get_room_event::v3::Response {
|
||||
event: event.to_room_event(),
|
||||
})
|
||||
|
@ -516,7 +558,7 @@ pub async fn upgrade_room_route(
|
|||
.globals
|
||||
.roomid_mutex_state
|
||||
.write()
|
||||
.unwrap()
|
||||
.await
|
||||
.entry(body.room_id.clone())
|
||||
.or_default(),
|
||||
);
|
||||
|
@ -524,22 +566,26 @@ pub async fn upgrade_room_route(
|
|||
|
||||
// Send a m.room.tombstone event to the old room to indicate that it is not intended to be used any further
|
||||
// Fail if the sender does not have the required permissions
|
||||
let tombstone_event_id = services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomTombstone,
|
||||
content: to_raw_value(&RoomTombstoneEventContent {
|
||||
body: "This room has been replaced".to_owned(),
|
||||
replacement_room: replacement_room.clone(),
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&state_lock,
|
||||
)?;
|
||||
let tombstone_event_id = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomTombstone,
|
||||
content: to_raw_value(&RoomTombstoneEventContent {
|
||||
body: "This room has been replaced".to_owned(),
|
||||
replacement_room: replacement_room.clone(),
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Change lock to replacement room
|
||||
drop(state_lock);
|
||||
|
@ -548,7 +594,7 @@ pub async fn upgrade_room_route(
|
|||
.globals
|
||||
.roomid_mutex_state
|
||||
.write()
|
||||
.unwrap()
|
||||
.await
|
||||
.entry(replacement_room.clone())
|
||||
.or_default(),
|
||||
);
|
||||
|
@ -606,43 +652,51 @@ pub async fn upgrade_room_route(
|
|||
));
|
||||
}
|
||||
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomCreate,
|
||||
content: to_raw_value(&create_event_content)
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&replacement_room,
|
||||
&state_lock,
|
||||
)?;
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomCreate,
|
||||
content: to_raw_value(&create_event_content)
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&replacement_room,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Join the new room
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
membership: MembershipState::Join,
|
||||
displayname: services().users.displayname(sender_user)?,
|
||||
avatar_url: services().users.avatar_url(sender_user)?,
|
||||
is_direct: None,
|
||||
third_party_invite: None,
|
||||
blurhash: services().users.blurhash(sender_user)?,
|
||||
reason: None,
|
||||
join_authorized_via_users_server: None,
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(sender_user.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&replacement_room,
|
||||
&state_lock,
|
||||
)?;
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
membership: MembershipState::Join,
|
||||
displayname: services().users.displayname(sender_user)?,
|
||||
avatar_url: services().users.avatar_url(sender_user)?,
|
||||
is_direct: None,
|
||||
third_party_invite: None,
|
||||
blurhash: services().users.blurhash(sender_user)?,
|
||||
reason: None,
|
||||
join_authorized_via_users_server: None,
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(sender_user.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&replacement_room,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Recommended transferable state events list from the specs
|
||||
let transferable_state_events = vec![
|
||||
|
@ -669,18 +723,22 @@ pub async fn upgrade_room_route(
|
|||
None => continue, // Skipping missing events.
|
||||
};
|
||||
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: event_type.to_string().into(),
|
||||
content: event_content,
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&replacement_room,
|
||||
&state_lock,
|
||||
)?;
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: event_type.to_string().into(),
|
||||
content: event_content,
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&replacement_room,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Moves any local aliases to the new room
|
||||
|
@ -714,19 +772,23 @@ pub async fn upgrade_room_route(
|
|||
power_levels_event_content.invite = new_level;
|
||||
|
||||
// Modify the power levels in the old room to prevent sending of events and inviting new users
|
||||
let _ = services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomPowerLevels,
|
||||
content: to_raw_value(&power_levels_event_content)
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&state_lock,
|
||||
)?;
|
||||
let _ = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomPowerLevels,
|
||||
content: to_raw_value(&power_levels_event_content)
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
|
|
|
@ -31,7 +31,8 @@ pub async fn search_events_route(
|
|||
.collect()
|
||||
});
|
||||
|
||||
let limit = filter.limit.map_or(10, |l| u64::from(l) as usize);
|
||||
// Use limit or else 10, with maximum 100
|
||||
let limit = filter.limit.map_or(10, u64::from).min(100) as usize;
|
||||
|
||||
let mut searches = Vec::new();
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ use ruma::{
|
|||
UserId,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use tracing::info;
|
||||
use tracing::{info, warn};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Claims {
|
||||
|
@ -26,6 +26,7 @@ pub async fn get_login_types_route(
|
|||
) -> Result<get_login_types::v3::Response> {
|
||||
Ok(get_login_types::v3::Response::new(vec![
|
||||
get_login_types::v3::LoginType::Password(Default::default()),
|
||||
get_login_types::v3::LoginType::ApplicationService(Default::default()),
|
||||
]))
|
||||
}
|
||||
|
||||
|
@ -41,23 +42,31 @@ pub async fn get_login_types_route(
|
|||
/// Note: You can use [`GET /_matrix/client/r0/login`](fn.get_supported_versions_route.html) to see
|
||||
/// supported login types.
|
||||
pub async fn login_route(body: Ruma<login::v3::Request>) -> Result<login::v3::Response> {
|
||||
// To allow deprecated login methods
|
||||
#![allow(deprecated)]
|
||||
// Validate login method
|
||||
// TODO: Other login methods
|
||||
let user_id = match &body.login_info {
|
||||
login::v3::LoginInfo::Password(login::v3::Password {
|
||||
identifier,
|
||||
password,
|
||||
user,
|
||||
address: _,
|
||||
medium: _,
|
||||
}) => {
|
||||
let username = if let UserIdentifier::UserIdOrLocalpart(user_id) = identifier {
|
||||
user_id.to_lowercase()
|
||||
let user_id = if let Some(UserIdentifier::UserIdOrLocalpart(user_id)) = identifier {
|
||||
UserId::parse_with_server_name(
|
||||
user_id.to_lowercase(),
|
||||
services().globals.server_name(),
|
||||
)
|
||||
} else if let Some(user) = user {
|
||||
UserId::parse(user)
|
||||
} else {
|
||||
warn!("Bad login type: {:?}", &body.login_info);
|
||||
return Err(Error::BadRequest(ErrorKind::Forbidden, "Bad login type."));
|
||||
};
|
||||
let user_id =
|
||||
UserId::parse_with_server_name(username, services().globals.server_name())
|
||||
.map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid.")
|
||||
})?;
|
||||
}
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?;
|
||||
|
||||
let hash = services()
|
||||
.users
|
||||
.password_hash(&user_id)?
|
||||
|
@ -103,7 +112,31 @@ pub async fn login_route(body: Ruma<login::v3::Request>) -> Result<login::v3::Re
|
|||
));
|
||||
}
|
||||
}
|
||||
login::v3::LoginInfo::ApplicationService(login::v3::ApplicationService {
|
||||
identifier,
|
||||
user,
|
||||
}) => {
|
||||
if !body.from_appservice {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::MissingToken,
|
||||
"Missing appservice token.",
|
||||
));
|
||||
};
|
||||
if let Some(UserIdentifier::UserIdOrLocalpart(user_id)) = identifier {
|
||||
UserId::parse_with_server_name(
|
||||
user_id.to_lowercase(),
|
||||
services().globals.server_name(),
|
||||
)
|
||||
} else if let Some(user) = user {
|
||||
UserId::parse(user)
|
||||
} else {
|
||||
warn!("Bad login type: {:?}", &body.login_info);
|
||||
return Err(Error::BadRequest(ErrorKind::Forbidden, "Bad login type."));
|
||||
}
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?
|
||||
}
|
||||
_ => {
|
||||
warn!("Unsupported or unknown login type: {:?}", &body.login_info);
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Unknown,
|
||||
"Unsupported login type.",
|
||||
|
@ -141,6 +174,8 @@ pub async fn login_route(body: Ruma<login::v3::Request>) -> Result<login::v3::Re
|
|||
|
||||
info!("{} logged in", user_id);
|
||||
|
||||
// Homeservers are still required to send the `home_server` field
|
||||
#[allow(deprecated)]
|
||||
Ok(login::v3::Response {
|
||||
user_id,
|
||||
access_token: token,
|
||||
|
|
34
src/api/client_server/space.rs
Normal file
34
src/api/client_server/space.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
use crate::{services, Result, Ruma};
|
||||
use ruma::api::client::space::get_hierarchy;
|
||||
|
||||
/// # `GET /_matrix/client/v1/rooms/{room_id}/hierarchy``
|
||||
///
|
||||
/// Paginates over the space tree in a depth-first manner to locate child rooms of a given space.
|
||||
pub async fn get_hierarchy_route(
|
||||
body: Ruma<get_hierarchy::v1::Request>,
|
||||
) -> Result<get_hierarchy::v1::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let skip = body
|
||||
.from
|
||||
.as_ref()
|
||||
.and_then(|s| s.parse::<usize>().ok())
|
||||
.unwrap_or(0);
|
||||
|
||||
let limit = body.limit.map_or(10, u64::from).min(100) as usize;
|
||||
|
||||
let max_depth = body.max_depth.map_or(3, u64::from).min(10) as usize + 1; // +1 to skip the space room itself
|
||||
|
||||
services()
|
||||
.rooms
|
||||
.spaces
|
||||
.get_hierarchy(
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
limit,
|
||||
skip,
|
||||
max_depth,
|
||||
body.suggested_only,
|
||||
)
|
||||
.await
|
||||
}
|
|
@ -12,6 +12,7 @@ use ruma::{
|
|||
serde::Raw,
|
||||
EventId, RoomId, UserId,
|
||||
};
|
||||
use tracing::log::warn;
|
||||
|
||||
/// # `PUT /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey}`
|
||||
///
|
||||
|
@ -84,7 +85,7 @@ pub async fn get_state_events_route(
|
|||
if !services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_state_events(&sender_user, &body.room_id)?
|
||||
.user_can_see_state_events(sender_user, &body.room_id)?
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
|
@ -117,7 +118,7 @@ pub async fn get_state_events_for_key_route(
|
|||
if !services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_state_events(&sender_user, &body.room_id)?
|
||||
.user_can_see_state_events(sender_user, &body.room_id)?
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
|
@ -129,10 +130,13 @@ pub async fn get_state_events_for_key_route(
|
|||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(&body.room_id, &body.event_type, &body.state_key)?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"State event not found.",
|
||||
))?;
|
||||
.ok_or_else(|| {
|
||||
warn!(
|
||||
"State event {:?} not found in room {:?}",
|
||||
&body.event_type, &body.room_id
|
||||
);
|
||||
Error::BadRequest(ErrorKind::NotFound, "State event not found.")
|
||||
})?;
|
||||
|
||||
Ok(get_state_events_for_key::v3::Response {
|
||||
content: serde_json::from_str(event.content.get())
|
||||
|
@ -153,7 +157,7 @@ pub async fn get_state_events_for_empty_key_route(
|
|||
if !services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_state_events(&sender_user, &body.room_id)?
|
||||
.user_can_see_state_events(sender_user, &body.room_id)?
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
|
@ -165,10 +169,13 @@ pub async fn get_state_events_for_empty_key_route(
|
|||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(&body.room_id, &body.event_type, "")?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"State event not found.",
|
||||
))?;
|
||||
.ok_or_else(|| {
|
||||
warn!(
|
||||
"State event {:?} not found in room {:?}",
|
||||
&body.event_type, &body.room_id
|
||||
);
|
||||
Error::BadRequest(ErrorKind::NotFound, "State event not found.")
|
||||
})?;
|
||||
|
||||
Ok(get_state_events_for_key::v3::Response {
|
||||
content: serde_json::from_str(event.content.get())
|
||||
|
@ -220,24 +227,28 @@ async fn send_state_event_for_key_helper(
|
|||
.globals
|
||||
.roomid_mutex_state
|
||||
.write()
|
||||
.unwrap()
|
||||
.await
|
||||
.entry(room_id.to_owned())
|
||||
.or_default(),
|
||||
);
|
||||
let state_lock = mutex_state.lock().await;
|
||||
|
||||
let event_id = services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: event_type.to_string().into(),
|
||||
content: serde_json::from_str(json.json().get()).expect("content is valid json"),
|
||||
unsigned: None,
|
||||
state_key: Some(state_key),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
room_id,
|
||||
&state_lock,
|
||||
)?;
|
||||
let event_id = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: event_type.to_string().into(),
|
||||
content: serde_json::from_str(json.json().get()).expect("content is valid json"),
|
||||
unsigned: None,
|
||||
state_key: Some(state_key),
|
||||
redacts: None,
|
||||
},
|
||||
sender_user,
|
||||
room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(event_id)
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
49
src/api/client_server/threads.rs
Normal file
49
src/api/client_server/threads.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
use ruma::api::client::{error::ErrorKind, threads::get_threads};
|
||||
|
||||
use crate::{services, Error, Result, Ruma};
|
||||
|
||||
/// # `GET /_matrix/client/r0/rooms/{roomId}/threads`
|
||||
pub async fn get_threads_route(
|
||||
body: Ruma<get_threads::v1::Request>,
|
||||
) -> Result<get_threads::v1::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
// Use limit or else 10, with maximum 100
|
||||
let limit = body
|
||||
.limit
|
||||
.and_then(|l| l.try_into().ok())
|
||||
.unwrap_or(10)
|
||||
.min(100);
|
||||
|
||||
let from = if let Some(from) = &body.from {
|
||||
from.parse()
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, ""))?
|
||||
} else {
|
||||
u64::MAX
|
||||
};
|
||||
|
||||
let threads = services()
|
||||
.rooms
|
||||
.threads
|
||||
.threads_until(sender_user, &body.room_id, from, &body.include)?
|
||||
.take(limit)
|
||||
.filter_map(|r| r.ok())
|
||||
.filter(|(_, pdu)| {
|
||||
services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_event(sender_user, &body.room_id, &pdu.event_id)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let next_batch = threads.last().map(|(count, _)| count.to_string());
|
||||
|
||||
Ok(get_threads::v1::Response {
|
||||
chunk: threads
|
||||
.into_iter()
|
||||
.map(|(_, pdu)| pdu.to_room_event())
|
||||
.collect(),
|
||||
next_batch,
|
||||
})
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
use ruma::events::ToDeviceEventType;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::{services, Error, Result, Ruma};
|
||||
|
@ -42,7 +41,7 @@ pub async fn send_event_to_device_route(
|
|||
serde_json::to_vec(&federation::transactions::edu::Edu::DirectToDevice(
|
||||
DirectDeviceContent {
|
||||
sender: sender_user.clone(),
|
||||
ev_type: ToDeviceEventType::from(&*body.event_type),
|
||||
ev_type: body.event_type.clone(),
|
||||
message_id: count.to_string().into(),
|
||||
messages,
|
||||
},
|
||||
|
@ -60,7 +59,7 @@ pub async fn send_event_to_device_route(
|
|||
sender_user,
|
||||
target_user_id,
|
||||
target_device_id,
|
||||
&body.event_type,
|
||||
&body.event_type.to_string(),
|
||||
event.deserialize_as().map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid")
|
||||
})?,
|
||||
|
@ -73,7 +72,7 @@ pub async fn send_event_to_device_route(
|
|||
sender_user,
|
||||
target_user_id,
|
||||
&target_device_id?,
|
||||
&body.event_type,
|
||||
&body.event_type.to_string(),
|
||||
event.deserialize_as().map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid")
|
||||
})?,
|
||||
|
|
|
@ -23,17 +23,23 @@ pub async fn create_typing_event_route(
|
|||
}
|
||||
|
||||
if let Typing::Yes(duration) = body.state {
|
||||
services().rooms.edus.typing.typing_add(
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
duration.as_millis() as u64 + utils::millis_since_unix_epoch(),
|
||||
)?;
|
||||
services()
|
||||
.rooms
|
||||
.edus
|
||||
.typing
|
||||
.typing_add(
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
duration.as_millis() as u64 + utils::millis_since_unix_epoch(),
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
services()
|
||||
.rooms
|
||||
.edus
|
||||
.typing
|
||||
.typing_remove(sender_user, &body.room_id)?;
|
||||
.typing_remove(sender_user, &body.room_id)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(create_typing_event::v3::Response {})
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use std::{collections::BTreeMap, iter::FromIterator};
|
||||
|
||||
use ruma::api::client::discovery::get_supported_versions;
|
||||
use axum::{response::IntoResponse, Json};
|
||||
use ruma::api::client::{discovery::get_supported_versions, error::ErrorKind};
|
||||
|
||||
use crate::{Result, Ruma};
|
||||
use crate::{services, Error, Result, Ruma};
|
||||
|
||||
/// # `GET /_matrix/client/versions`
|
||||
///
|
||||
|
@ -23,9 +24,27 @@ pub async fn get_supported_versions_route(
|
|||
"r0.6.0".to_owned(),
|
||||
"v1.1".to_owned(),
|
||||
"v1.2".to_owned(),
|
||||
"v1.3".to_owned(),
|
||||
"v1.4".to_owned(),
|
||||
"v1.5".to_owned(),
|
||||
],
|
||||
unstable_features: BTreeMap::from_iter([("org.matrix.e2e_cross_signing".to_owned(), true)]),
|
||||
};
|
||||
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
/// # `GET /.well-known/matrix/client`
|
||||
pub async fn well_known_client_route(
|
||||
_body: Ruma<get_supported_versions::Request>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
let client_url = match services().globals.well_known_client() {
|
||||
Some(url) => url.clone(),
|
||||
None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
|
||||
};
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"m.homeserver": {"base_url": client_url},
|
||||
"org.matrix.msc3575.proxy": {"url": client_url}
|
||||
})))
|
||||
}
|
||||
|
|
|
@ -48,6 +48,9 @@ pub async fn search_users_route(
|
|||
return None;
|
||||
}
|
||||
|
||||
// It's a matching user, but is the sender allowed to see them?
|
||||
let mut user_visible = false;
|
||||
|
||||
let user_is_in_public_rooms = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
|
@ -69,22 +72,26 @@ pub async fn search_users_route(
|
|||
});
|
||||
|
||||
if user_is_in_public_rooms {
|
||||
return Some(user);
|
||||
user_visible = true;
|
||||
} else {
|
||||
let user_is_in_shared_rooms = services()
|
||||
.rooms
|
||||
.user
|
||||
.get_shared_rooms(vec![sender_user.clone(), user_id])
|
||||
.ok()?
|
||||
.next()
|
||||
.is_some();
|
||||
|
||||
if user_is_in_shared_rooms {
|
||||
user_visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
let user_is_in_shared_rooms = services()
|
||||
.rooms
|
||||
.user
|
||||
.get_shared_rooms(vec![sender_user.clone(), user_id])
|
||||
.ok()?
|
||||
.next()
|
||||
.is_some();
|
||||
|
||||
if user_is_in_shared_rooms {
|
||||
return Some(user);
|
||||
if !user_visible {
|
||||
return None;
|
||||
}
|
||||
|
||||
None
|
||||
Some(user)
|
||||
});
|
||||
|
||||
let results = users.by_ref().take(limit).collect();
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::{services, Result, Ruma};
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use hmac::{Hmac, Mac};
|
||||
use ruma::{api::client::voip::get_turn_server_info, SecondsSinceUnixEpoch};
|
||||
use sha1::Sha1;
|
||||
|
@ -28,7 +29,7 @@ pub async fn turn_server_route(
|
|||
.expect("HMAC can take key of any size");
|
||||
mac.update(username.as_bytes());
|
||||
|
||||
let password: String = base64::encode_config(mac.finalize().into_bytes(), base64::STANDARD);
|
||||
let password: String = general_purpose::STANDARD.encode(mac.finalize().into_bytes());
|
||||
|
||||
(username, password)
|
||||
} else {
|
||||
|
|
|
@ -3,51 +3,73 @@ use std::{collections::BTreeMap, iter::FromIterator, str};
|
|||
use axum::{
|
||||
async_trait,
|
||||
body::{Full, HttpBody},
|
||||
extract::{
|
||||
rejection::TypedHeaderRejectionReason, FromRequest, Path, RequestParts, TypedHeader,
|
||||
},
|
||||
extract::{rejection::TypedHeaderRejectionReason, FromRequest, Path, TypedHeader},
|
||||
headers::{
|
||||
authorization::{Bearer, Credentials},
|
||||
Authorization,
|
||||
},
|
||||
response::{IntoResponse, Response},
|
||||
BoxError,
|
||||
BoxError, RequestExt, RequestPartsExt,
|
||||
};
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use http::StatusCode;
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
use http::{Request, StatusCode};
|
||||
use ruma::{
|
||||
api::{client::error::ErrorKind, AuthScheme, IncomingRequest, OutgoingResponse},
|
||||
CanonicalJsonValue, OwnedDeviceId, OwnedServerName, UserId,
|
||||
CanonicalJsonValue, OwnedDeviceId, OwnedServerName, OwnedUserId, UserId,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use tracing::{debug, error, warn};
|
||||
|
||||
use super::{Ruma, RumaResponse};
|
||||
use crate::{services, Error, Result};
|
||||
use crate::{service::appservice::RegistrationInfo, services, Error, Result};
|
||||
|
||||
enum Token {
|
||||
Appservice(Box<RegistrationInfo>),
|
||||
User((OwnedUserId, OwnedDeviceId)),
|
||||
Invalid,
|
||||
None,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T, B> FromRequest<B> for Ruma<T>
|
||||
impl<T, S, B> FromRequest<S, B> for Ruma<T>
|
||||
where
|
||||
T: IncomingRequest,
|
||||
B: HttpBody + Send,
|
||||
B: HttpBody + Send + 'static,
|
||||
B::Data: Send,
|
||||
B::Error: Into<BoxError>,
|
||||
{
|
||||
type Rejection = Error;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: Request<B>, _state: &S) -> Result<Self, Self::Rejection> {
|
||||
#[derive(Deserialize)]
|
||||
struct QueryParams {
|
||||
access_token: Option<String>,
|
||||
user_id: Option<String>,
|
||||
}
|
||||
|
||||
let metadata = T::METADATA;
|
||||
let auth_header = Option::<TypedHeader<Authorization<Bearer>>>::from_request(req).await?;
|
||||
let path_params = Path::<Vec<String>>::from_request(req).await?;
|
||||
let (mut parts, mut body) = match req.with_limited_body() {
|
||||
Ok(limited_req) => {
|
||||
let (parts, body) = limited_req.into_parts();
|
||||
let body = to_bytes(body)
|
||||
.await
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::MissingToken, "Missing token."))?;
|
||||
(parts, body)
|
||||
}
|
||||
Err(original_req) => {
|
||||
let (parts, body) = original_req.into_parts();
|
||||
let body = to_bytes(body)
|
||||
.await
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::MissingToken, "Missing token."))?;
|
||||
(parts, body)
|
||||
}
|
||||
};
|
||||
|
||||
let query = req.uri().query().unwrap_or_default();
|
||||
let query_params: QueryParams = match ruma::serde::urlencoded::from_str(query) {
|
||||
let metadata = T::METADATA;
|
||||
let auth_header: Option<TypedHeader<Authorization<Bearer>>> = parts.extract().await?;
|
||||
let path_params: Path<Vec<String>> = parts.extract().await?;
|
||||
|
||||
let query = parts.uri.query().unwrap_or_default();
|
||||
let query_params: QueryParams = match serde_html_form::from_str(query) {
|
||||
Ok(params) => params,
|
||||
Err(e) => {
|
||||
error!(%query, "Failed to deserialize query parameters: {}", e);
|
||||
|
@ -63,188 +85,197 @@ where
|
|||
None => query_params.access_token.as_deref(),
|
||||
};
|
||||
|
||||
let mut body = Bytes::from_request(req)
|
||||
.await
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::MissingToken, "Missing token."))?;
|
||||
let token = if let Some(token) = token {
|
||||
if let Some(reg_info) = services().appservice.find_from_token(token).await {
|
||||
Token::Appservice(Box::new(reg_info.clone()))
|
||||
} else if let Some((user_id, device_id)) = services().users.find_from_token(token)? {
|
||||
Token::User((user_id, OwnedDeviceId::from(device_id)))
|
||||
} else {
|
||||
Token::Invalid
|
||||
}
|
||||
} else {
|
||||
Token::None
|
||||
};
|
||||
|
||||
let mut json_body = serde_json::from_slice::<CanonicalJsonValue>(&body).ok();
|
||||
|
||||
let appservices = services().appservice.all().unwrap();
|
||||
let appservice_registration = appservices.iter().find(|(_id, registration)| {
|
||||
registration
|
||||
.get("as_token")
|
||||
.and_then(|as_token| as_token.as_str())
|
||||
.map_or(false, |as_token| token == Some(as_token))
|
||||
});
|
||||
|
||||
let (sender_user, sender_device, sender_servername, from_appservice) =
|
||||
if let Some((_id, registration)) = appservice_registration {
|
||||
match metadata.authentication {
|
||||
AuthScheme::AccessToken => {
|
||||
let user_id = query_params.user_id.map_or_else(
|
||||
match (metadata.authentication, token) {
|
||||
(_, Token::Invalid) => {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::UnknownToken { soft_logout: false },
|
||||
"Unknown access token.",
|
||||
))
|
||||
}
|
||||
(
|
||||
AuthScheme::AccessToken | AuthScheme::AccessTokenOptional,
|
||||
Token::Appservice(info),
|
||||
) => {
|
||||
let user_id = query_params
|
||||
.user_id
|
||||
.map_or_else(
|
||||
|| {
|
||||
UserId::parse_with_server_name(
|
||||
registration
|
||||
.get("sender_localpart")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap(),
|
||||
info.registration.sender_localpart.as_str(),
|
||||
services().globals.server_name(),
|
||||
)
|
||||
.unwrap()
|
||||
},
|
||||
|s| UserId::parse(s).unwrap(),
|
||||
);
|
||||
UserId::parse,
|
||||
)
|
||||
.map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid.")
|
||||
})?;
|
||||
if !services().users.exists(&user_id)? {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"User does not exist.",
|
||||
));
|
||||
}
|
||||
|
||||
if !services().users.exists(&user_id).unwrap() {
|
||||
// TODO: Check if appservice is allowed to be that user
|
||||
(Some(user_id), None, None, true)
|
||||
}
|
||||
(AuthScheme::None | AuthScheme::AppserviceToken, Token::Appservice(_)) => {
|
||||
(None, None, None, true)
|
||||
}
|
||||
(AuthScheme::AccessToken, Token::None) => {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::MissingToken,
|
||||
"Missing access token.",
|
||||
));
|
||||
}
|
||||
(
|
||||
AuthScheme::AccessToken | AuthScheme::AccessTokenOptional | AuthScheme::None,
|
||||
Token::User((user_id, device_id)),
|
||||
) => (Some(user_id), Some(device_id), None, false),
|
||||
(AuthScheme::ServerSignatures, Token::None) => {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let TypedHeader(Authorization(x_matrix)) = parts
|
||||
.extract::<TypedHeader<Authorization<XMatrix>>>()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Missing or invalid Authorization header: {}", e);
|
||||
|
||||
let msg = match e.reason() {
|
||||
TypedHeaderRejectionReason::Missing => {
|
||||
"Missing Authorization header."
|
||||
}
|
||||
TypedHeaderRejectionReason::Error(_) => {
|
||||
"Invalid X-Matrix signatures."
|
||||
}
|
||||
_ => "Unknown header-related error",
|
||||
};
|
||||
|
||||
Error::BadRequest(ErrorKind::Forbidden, msg)
|
||||
})?;
|
||||
|
||||
let origin_signatures = BTreeMap::from_iter([(
|
||||
x_matrix.key.clone(),
|
||||
CanonicalJsonValue::String(x_matrix.sig),
|
||||
)]);
|
||||
|
||||
let signatures = BTreeMap::from_iter([(
|
||||
x_matrix.origin.as_str().to_owned(),
|
||||
CanonicalJsonValue::Object(origin_signatures),
|
||||
)]);
|
||||
|
||||
let mut request_map = BTreeMap::from_iter([
|
||||
(
|
||||
"method".to_owned(),
|
||||
CanonicalJsonValue::String(parts.method.to_string()),
|
||||
),
|
||||
(
|
||||
"uri".to_owned(),
|
||||
CanonicalJsonValue::String(parts.uri.to_string()),
|
||||
),
|
||||
(
|
||||
"origin".to_owned(),
|
||||
CanonicalJsonValue::String(x_matrix.origin.as_str().to_owned()),
|
||||
),
|
||||
(
|
||||
"destination".to_owned(),
|
||||
CanonicalJsonValue::String(
|
||||
services().globals.server_name().as_str().to_owned(),
|
||||
),
|
||||
),
|
||||
(
|
||||
"signatures".to_owned(),
|
||||
CanonicalJsonValue::Object(signatures),
|
||||
),
|
||||
]);
|
||||
|
||||
if let Some(json_body) = &json_body {
|
||||
request_map.insert("content".to_owned(), json_body.clone());
|
||||
};
|
||||
|
||||
let keys_result = services()
|
||||
.rooms
|
||||
.event_handler
|
||||
.fetch_signing_keys(&x_matrix.origin, vec![x_matrix.key.to_owned()])
|
||||
.await;
|
||||
|
||||
let keys = match keys_result {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
warn!("Failed to fetch signing keys: {}", e);
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"User does not exist.",
|
||||
"Failed to fetch signing keys.",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Check if appservice is allowed to be that user
|
||||
(Some(user_id), None, None, true)
|
||||
}
|
||||
AuthScheme::ServerSignatures => (None, None, None, true),
|
||||
AuthScheme::None => (None, None, None, true),
|
||||
}
|
||||
} else {
|
||||
match metadata.authentication {
|
||||
AuthScheme::AccessToken => {
|
||||
let token = match token {
|
||||
Some(token) => token,
|
||||
_ => {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::MissingToken,
|
||||
"Missing access token.",
|
||||
))
|
||||
}
|
||||
};
|
||||
let pub_key_map =
|
||||
BTreeMap::from_iter([(x_matrix.origin.as_str().to_owned(), keys)]);
|
||||
|
||||
match services().users.find_from_token(token).unwrap() {
|
||||
None => {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::UnknownToken { soft_logout: false },
|
||||
"Unknown access token.",
|
||||
))
|
||||
}
|
||||
Some((user_id, device_id)) => (
|
||||
Some(user_id),
|
||||
Some(OwnedDeviceId::from(device_id)),
|
||||
None,
|
||||
false,
|
||||
),
|
||||
}
|
||||
}
|
||||
AuthScheme::ServerSignatures => {
|
||||
let TypedHeader(Authorization(x_matrix)) =
|
||||
TypedHeader::<Authorization<XMatrix>>::from_request(req)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Missing or invalid Authorization header: {}", e);
|
||||
match ruma::signatures::verify_json(&pub_key_map, &request_map) {
|
||||
Ok(()) => (None, None, Some(x_matrix.origin), false),
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Failed to verify json request from {}: {}\n{:?}",
|
||||
x_matrix.origin, e, request_map
|
||||
);
|
||||
|
||||
let msg = match e.reason() {
|
||||
TypedHeaderRejectionReason::Missing => {
|
||||
"Missing Authorization header."
|
||||
}
|
||||
TypedHeaderRejectionReason::Error(_) => {
|
||||
"Invalid X-Matrix signatures."
|
||||
}
|
||||
_ => "Unknown header-related error",
|
||||
};
|
||||
|
||||
Error::BadRequest(ErrorKind::Forbidden, msg)
|
||||
})?;
|
||||
|
||||
let origin_signatures = BTreeMap::from_iter([(
|
||||
x_matrix.key.clone(),
|
||||
CanonicalJsonValue::String(x_matrix.sig),
|
||||
)]);
|
||||
|
||||
let signatures = BTreeMap::from_iter([(
|
||||
x_matrix.origin.as_str().to_owned(),
|
||||
CanonicalJsonValue::Object(origin_signatures),
|
||||
)]);
|
||||
|
||||
let mut request_map = BTreeMap::from_iter([
|
||||
(
|
||||
"method".to_owned(),
|
||||
CanonicalJsonValue::String(req.method().to_string()),
|
||||
),
|
||||
(
|
||||
"uri".to_owned(),
|
||||
CanonicalJsonValue::String(req.uri().to_string()),
|
||||
),
|
||||
(
|
||||
"origin".to_owned(),
|
||||
CanonicalJsonValue::String(x_matrix.origin.as_str().to_owned()),
|
||||
),
|
||||
(
|
||||
"destination".to_owned(),
|
||||
CanonicalJsonValue::String(
|
||||
services().globals.server_name().as_str().to_owned(),
|
||||
),
|
||||
),
|
||||
(
|
||||
"signatures".to_owned(),
|
||||
CanonicalJsonValue::Object(signatures),
|
||||
),
|
||||
]);
|
||||
|
||||
if let Some(json_body) = &json_body {
|
||||
request_map.insert("content".to_owned(), json_body.clone());
|
||||
};
|
||||
|
||||
let keys_result = services()
|
||||
.rooms
|
||||
.event_handler
|
||||
.fetch_signing_keys(&x_matrix.origin, vec![x_matrix.key.to_owned()])
|
||||
.await;
|
||||
|
||||
let keys = match keys_result {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
warn!("Failed to fetch signing keys: {}", e);
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Failed to fetch signing keys.",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let pub_key_map =
|
||||
BTreeMap::from_iter([(x_matrix.origin.as_str().to_owned(), keys)]);
|
||||
|
||||
match ruma::signatures::verify_json(&pub_key_map, &request_map) {
|
||||
Ok(()) => (None, None, Some(x_matrix.origin), false),
|
||||
Err(e) => {
|
||||
if parts.uri.to_string().contains('@') {
|
||||
warn!(
|
||||
"Failed to verify json request from {}: {}\n{:?}",
|
||||
x_matrix.origin, e, request_map
|
||||
);
|
||||
|
||||
if req.uri().to_string().contains('@') {
|
||||
warn!(
|
||||
"Request uri contained '@' character. Make sure your \
|
||||
"Request uri contained '@' character. Make sure your \
|
||||
reverse proxy gives Conduit the raw uri (apache: use \
|
||||
nocanon)"
|
||||
);
|
||||
}
|
||||
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Failed to verify X-Matrix signatures.",
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Failed to verify X-Matrix signatures.",
|
||||
));
|
||||
}
|
||||
}
|
||||
AuthScheme::None => (None, None, None, false),
|
||||
}
|
||||
(
|
||||
AuthScheme::None
|
||||
| AuthScheme::AppserviceToken
|
||||
| AuthScheme::AccessTokenOptional,
|
||||
Token::None,
|
||||
) => (None, None, None, false),
|
||||
(AuthScheme::ServerSignatures, Token::Appservice(_) | Token::User(_)) => {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Unauthorized,
|
||||
"Only server signatures should be used on this endpoint.",
|
||||
));
|
||||
}
|
||||
(AuthScheme::AppserviceToken, Token::User(_)) => {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Unauthorized,
|
||||
"Only appservice access tokens should be used on this endpoint.",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let mut http_request = http::Request::builder().uri(req.uri()).method(req.method());
|
||||
*http_request.headers_mut().unwrap() = req.headers().clone();
|
||||
let mut http_request = http::Request::builder().uri(parts.uri).method(parts.method);
|
||||
*http_request.headers_mut().unwrap() = parts.headers;
|
||||
|
||||
if let Some(CanonicalJsonValue::Object(json_body)) = &mut json_body {
|
||||
let user_id = sender_user.clone().unwrap_or_else(|| {
|
||||
|
@ -281,10 +312,8 @@ where
|
|||
debug!("{:?}", http_request);
|
||||
|
||||
let body = T::try_from_http_request(http_request, &path_params).map_err(|e| {
|
||||
warn!(
|
||||
"try_from_http_request failed: {:?}\nJSON body: {:?}",
|
||||
e, json_body
|
||||
);
|
||||
warn!("try_from_http_request failed: {:?}", e);
|
||||
debug!("JSON body: {:?}", json_body);
|
||||
Error::BadRequest(ErrorKind::BadJson, "Failed to deserialize request.")
|
||||
})?;
|
||||
|
||||
|
@ -364,3 +393,55 @@ impl<T: OutgoingResponse> IntoResponse for RumaResponse<T> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copied from hyper under the following license:
|
||||
// Copyright (c) 2014-2021 Sean McArthur
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
pub(crate) async fn to_bytes<T>(body: T) -> Result<Bytes, T::Error>
|
||||
where
|
||||
T: HttpBody,
|
||||
{
|
||||
futures_util::pin_mut!(body);
|
||||
|
||||
// If there's only 1 chunk, we can just return Buf::to_bytes()
|
||||
let mut first = if let Some(buf) = body.data().await {
|
||||
buf?
|
||||
} else {
|
||||
return Ok(Bytes::new());
|
||||
};
|
||||
|
||||
let second = if let Some(buf) = body.data().await {
|
||||
buf?
|
||||
} else {
|
||||
return Ok(first.copy_to_bytes(first.remaining()));
|
||||
};
|
||||
|
||||
// With more than 1 buf, we gotta flatten into a Vec first.
|
||||
let cap = first.remaining() + second.remaining() + body.size_hint().lower() as usize;
|
||||
let mut vec = Vec::with_capacity(cap);
|
||||
vec.put(first);
|
||||
vec.put(second);
|
||||
|
||||
while let Some(buf) = body.data().await {
|
||||
vec.put(buf?);
|
||||
}
|
||||
|
||||
Ok(vec.into())
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(deprecated)]
|
||||
|
||||
use crate::{
|
||||
api::client_server::{self, claim_keys_helper, get_keys_helper},
|
||||
service::pdu::{gen_event_id_canonical_json, PduBuilder},
|
||||
|
@ -18,11 +20,7 @@ use ruma::{
|
|||
discovery::{get_server_keys, get_server_version, ServerSigningKeys, VerifyKey},
|
||||
event::{get_event, get_missing_events, get_room_state, get_room_state_ids},
|
||||
keys::{claim_keys, get_keys},
|
||||
membership::{
|
||||
create_invite,
|
||||
create_join_event::{self, RoomState},
|
||||
prepare_join_event,
|
||||
},
|
||||
membership::{create_invite, create_join_event, prepare_join_event},
|
||||
query::{get_profile_information, get_room_information},
|
||||
transactions::{
|
||||
edu::{DeviceListUpdateContent, DirectDeviceContent, Edu, SigningKeyUpdateContent},
|
||||
|
@ -39,7 +37,7 @@ use ruma::{
|
|||
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
RoomEventType, StateEventType,
|
||||
StateEventType, TimelineEventType,
|
||||
},
|
||||
serde::{Base64, JsonObject, Raw},
|
||||
to_device::DeviceIdOrAllDevices,
|
||||
|
@ -53,11 +51,12 @@ use std::{
|
|||
fmt::Debug,
|
||||
mem,
|
||||
net::{IpAddr, SocketAddr},
|
||||
sync::{Arc, RwLock},
|
||||
sync::Arc,
|
||||
time::{Duration, Instant, SystemTime},
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use tracing::{debug, error, info, warn};
|
||||
use tracing::{debug, error, warn};
|
||||
|
||||
/// Wraps either an literal IP address plus port, or a hostname plus complement
|
||||
/// (colon-plus-port if it was specified).
|
||||
|
@ -125,6 +124,12 @@ where
|
|||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
if destination == services().globals.server_name() {
|
||||
return Err(Error::bad_config(
|
||||
"Won't send federation request to ourselves",
|
||||
));
|
||||
}
|
||||
|
||||
debug!("Preparing to send request to {destination}");
|
||||
|
||||
let mut write_destination_to_cache = false;
|
||||
|
@ -133,7 +138,7 @@ where
|
|||
.globals
|
||||
.actual_destination_cache
|
||||
.read()
|
||||
.unwrap()
|
||||
.await
|
||||
.get(destination)
|
||||
.cloned();
|
||||
|
||||
|
@ -153,7 +158,7 @@ where
|
|||
.try_into_http_request::<Vec<u8>>(
|
||||
&actual_destination_str,
|
||||
SendAccessToken::IfRequired(""),
|
||||
&[MatrixVersion::V1_0],
|
||||
&[MatrixVersion::V1_4],
|
||||
)
|
||||
.map_err(|e| {
|
||||
warn!(
|
||||
|
@ -228,8 +233,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
let reqwest_request = reqwest::Request::try_from(http_request)
|
||||
.expect("all http requests are valid reqwest requests");
|
||||
let reqwest_request = reqwest::Request::try_from(http_request)?;
|
||||
|
||||
let url = reqwest_request.url().clone();
|
||||
|
||||
|
@ -286,7 +290,7 @@ where
|
|||
.globals
|
||||
.actual_destination_cache
|
||||
.write()
|
||||
.unwrap()
|
||||
.await
|
||||
.insert(
|
||||
OwnedServerName::from(destination),
|
||||
(actual_destination, host),
|
||||
|
@ -337,7 +341,7 @@ fn add_port_to_hostname(destination_str: &str) -> FedDest {
|
|||
}
|
||||
|
||||
/// Returns: actual_destination, host header
|
||||
/// Implemented according to the specification at https://matrix.org/docs/spec/server_server/r0.1.4#resolving-server-names
|
||||
/// Implemented according to the specification at <https://matrix.org/docs/spec/server_server/r0.1.4#resolving-server-names>
|
||||
/// Numbers in comments below refer to bullet points in linked section of specification
|
||||
async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDest) {
|
||||
debug!("Finding actual destination for {destination}");
|
||||
|
@ -471,12 +475,11 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
|
|||
(actual_destination, hostname)
|
||||
}
|
||||
|
||||
async fn query_srv_record(hostname: &'_ str) -> Option<FedDest> {
|
||||
let hostname = hostname.trim_end_matches('.');
|
||||
if let Ok(Some(host_port)) = services()
|
||||
async fn query_given_srv_record(record: &str) -> Option<FedDest> {
|
||||
services()
|
||||
.globals
|
||||
.dns_resolver()
|
||||
.srv_lookup(format!("_matrix._tcp.{hostname}."))
|
||||
.srv_lookup(record)
|
||||
.await
|
||||
.map(|srv| {
|
||||
srv.iter().next().map(|result| {
|
||||
|
@ -486,10 +489,17 @@ async fn query_srv_record(hostname: &'_ str) -> Option<FedDest> {
|
|||
)
|
||||
})
|
||||
})
|
||||
.unwrap_or(None)
|
||||
}
|
||||
|
||||
async fn query_srv_record(hostname: &'_ str) -> Option<FedDest> {
|
||||
let hostname = hostname.trim_end_matches('.');
|
||||
|
||||
if let Some(host_port) = query_given_srv_record(&format!("_matrix-fed._tcp.{hostname}.")).await
|
||||
{
|
||||
Some(host_port)
|
||||
} else {
|
||||
None
|
||||
query_given_srv_record(&format!("_matrix._tcp.{hostname}.")).await
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -501,6 +511,10 @@ async fn request_well_known(destination: &str) -> Option<String> {
|
|||
.send()
|
||||
.await;
|
||||
debug!("Got well known response");
|
||||
if let Err(e) = &response {
|
||||
debug!("Well known error: {e:?}");
|
||||
return None;
|
||||
}
|
||||
let text = response.ok()?.text().await;
|
||||
debug!("Got well known response text");
|
||||
let body: serde_json::Value = serde_json::from_str(&text.ok()?).ok()?;
|
||||
|
@ -592,10 +606,6 @@ pub async fn get_server_keys_deprecated_route() -> impl IntoResponse {
|
|||
pub async fn get_public_rooms_filtered_route(
|
||||
body: Ruma<get_public_rooms_filtered::v1::Request>,
|
||||
) -> Result<get_public_rooms_filtered::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let response = client_server::get_public_rooms_filtered_helper(
|
||||
None,
|
||||
body.limit,
|
||||
|
@ -619,10 +629,6 @@ pub async fn get_public_rooms_filtered_route(
|
|||
pub async fn get_public_rooms_route(
|
||||
body: Ruma<get_public_rooms::v1::Request>,
|
||||
) -> Result<get_public_rooms::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let response = client_server::get_public_rooms_filtered_helper(
|
||||
None,
|
||||
body.limit,
|
||||
|
@ -658,7 +664,7 @@ pub fn parse_incoming_pdu(
|
|||
|
||||
let room_version_id = services().rooms.state.get_room_version(&room_id)?;
|
||||
|
||||
let (event_id, value) = match gen_event_id_canonical_json(&pdu, &room_version_id) {
|
||||
let (event_id, value) = match gen_event_id_canonical_json(pdu, &room_version_id) {
|
||||
Ok(t) => t,
|
||||
Err(_) => {
|
||||
// Event could not be converted to canonical json
|
||||
|
@ -677,10 +683,6 @@ pub fn parse_incoming_pdu(
|
|||
pub async fn send_transaction_message_route(
|
||||
body: Ruma<send_transaction_message::v1::Request>,
|
||||
) -> Result<send_transaction_message::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let sender_servername = body
|
||||
.sender_servername
|
||||
.as_ref()
|
||||
|
@ -699,27 +701,40 @@ pub async fn send_transaction_message_route(
|
|||
// let mut auth_cache = EventMap::new();
|
||||
|
||||
for pdu in &body.pdus {
|
||||
let r = parse_incoming_pdu(&pdu);
|
||||
let value: CanonicalJsonObject = serde_json::from_str(pdu.get()).map_err(|e| {
|
||||
warn!("Error parsing incoming event {:?}: {:?}", pdu, e);
|
||||
Error::BadServerResponse("Invalid PDU in server response")
|
||||
})?;
|
||||
let room_id: OwnedRoomId = value
|
||||
.get("room_id")
|
||||
.and_then(|id| RoomId::parse(id.as_str()?).ok())
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Invalid room id in pdu",
|
||||
))?;
|
||||
|
||||
if services().rooms.state.get_room_version(&room_id).is_err() {
|
||||
debug!("Server is not in room {room_id}");
|
||||
continue;
|
||||
}
|
||||
|
||||
let r = parse_incoming_pdu(pdu);
|
||||
let (event_id, value, room_id) = match r {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
warn!("Could not parse pdu: {e}");
|
||||
warn!("Could not parse PDU: {e}");
|
||||
warn!("Full PDU: {:?}", &pdu);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
// We do not add the event_id field to the pdu here because of signature and hashes checks
|
||||
|
||||
services()
|
||||
.rooms
|
||||
.event_handler
|
||||
.acl_check(sender_servername, &room_id)?;
|
||||
|
||||
let mutex = Arc::clone(
|
||||
services()
|
||||
.globals
|
||||
.roomid_mutex_federation
|
||||
.write()
|
||||
.unwrap()
|
||||
.await
|
||||
.entry(room_id.to_owned())
|
||||
.or_default(),
|
||||
);
|
||||
|
@ -804,7 +819,7 @@ pub async fn send_transaction_message_route(
|
|||
.readreceipt_update(&user_id, &room_id, event)?;
|
||||
} else {
|
||||
// TODO fetch missing events
|
||||
info!("No known event ids in read receipt: {:?}", user_updates);
|
||||
debug!("No known event ids in read receipt: {:?}", user_updates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -816,17 +831,23 @@ pub async fn send_transaction_message_route(
|
|||
.is_joined(&typing.user_id, &typing.room_id)?
|
||||
{
|
||||
if typing.typing {
|
||||
services().rooms.edus.typing.typing_add(
|
||||
&typing.user_id,
|
||||
&typing.room_id,
|
||||
3000 + utils::millis_since_unix_epoch(),
|
||||
)?;
|
||||
services()
|
||||
.rooms
|
||||
.edus
|
||||
.typing
|
||||
.typing_add(
|
||||
&typing.user_id,
|
||||
&typing.room_id,
|
||||
3000 + utils::millis_since_unix_epoch(),
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
services()
|
||||
.rooms
|
||||
.edus
|
||||
.typing
|
||||
.typing_remove(&typing.user_id, &typing.room_id)?;
|
||||
.typing_remove(&typing.user_id, &typing.room_id)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -908,6 +929,7 @@ pub async fn send_transaction_message_route(
|
|||
&master_key,
|
||||
&self_signing_key,
|
||||
&None,
|
||||
true,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
@ -918,7 +940,7 @@ pub async fn send_transaction_message_route(
|
|||
Ok(send_transaction_message::v1::Response {
|
||||
pdus: resolved_map
|
||||
.into_iter()
|
||||
.map(|(e, r)| (e, r.map_err(|e| e.to_string())))
|
||||
.map(|(e, r)| (e, r.map_err(|e| e.sanitized_error())))
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
@ -931,10 +953,6 @@ pub async fn send_transaction_message_route(
|
|||
pub async fn get_event_route(
|
||||
body: Ruma<get_event::v1::Request>,
|
||||
) -> Result<get_event::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let sender_servername = body
|
||||
.sender_servername
|
||||
.as_ref()
|
||||
|
@ -944,7 +962,10 @@ pub async fn get_event_route(
|
|||
.rooms
|
||||
.timeline
|
||||
.get_pdu_json(&body.event_id)?
|
||||
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Event not found."))?;
|
||||
.ok_or_else(|| {
|
||||
warn!("Event not found, event ID: {:?}", &body.event_id);
|
||||
Error::BadRequest(ErrorKind::NotFound, "Event not found.")
|
||||
})?;
|
||||
|
||||
let room_id_str = event
|
||||
.get("room_id")
|
||||
|
@ -967,7 +988,7 @@ pub async fn get_event_route(
|
|||
|
||||
if !services().rooms.state_accessor.server_can_see_event(
|
||||
sender_servername,
|
||||
&room_id,
|
||||
room_id,
|
||||
&body.event_id,
|
||||
)? {
|
||||
return Err(Error::BadRequest(
|
||||
|
@ -990,16 +1011,12 @@ pub async fn get_event_route(
|
|||
pub async fn get_backfill_route(
|
||||
body: Ruma<get_backfill::v1::Request>,
|
||||
) -> Result<get_backfill::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let sender_servername = body
|
||||
.sender_servername
|
||||
.as_ref()
|
||||
.expect("server is authenticated");
|
||||
|
||||
info!("Got backfill request from: {}", sender_servername);
|
||||
debug!("Got backfill request from: {}", sender_servername);
|
||||
|
||||
if !services()
|
||||
.rooms
|
||||
|
@ -1033,7 +1050,7 @@ pub async fn get_backfill_route(
|
|||
let all_events = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_until(&user_id!("@doesntmatter:conduit.rs"), &body.room_id, until)?
|
||||
.pdus_until(user_id!("@doesntmatter:conduit.rs"), &body.room_id, until)?
|
||||
.take(limit.try_into().unwrap());
|
||||
|
||||
let events = all_events
|
||||
|
@ -1050,7 +1067,7 @@ pub async fn get_backfill_route(
|
|||
})
|
||||
.map(|(_, pdu)| services().rooms.timeline.get_pdu_json(&pdu.event_id))
|
||||
.filter_map(|r| r.ok().flatten())
|
||||
.map(|pdu| PduEvent::convert_to_outgoing_federation_event(pdu))
|
||||
.map(PduEvent::convert_to_outgoing_federation_event)
|
||||
.collect();
|
||||
|
||||
Ok(get_backfill::v1::Response {
|
||||
|
@ -1066,10 +1083,6 @@ pub async fn get_backfill_route(
|
|||
pub async fn get_missing_events_route(
|
||||
body: Ruma<get_missing_events::v1::Request>,
|
||||
) -> Result<get_missing_events::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let sender_servername = body
|
||||
.sender_servername
|
||||
.as_ref()
|
||||
|
@ -1155,10 +1168,6 @@ pub async fn get_missing_events_route(
|
|||
pub async fn get_event_authorization_route(
|
||||
body: Ruma<get_event_authorization::v1::Request>,
|
||||
) -> Result<get_event_authorization::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let sender_servername = body
|
||||
.sender_servername
|
||||
.as_ref()
|
||||
|
@ -1184,7 +1193,10 @@ pub async fn get_event_authorization_route(
|
|||
.rooms
|
||||
.timeline
|
||||
.get_pdu_json(&body.event_id)?
|
||||
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Event not found."))?;
|
||||
.ok_or_else(|| {
|
||||
warn!("Event not found, event ID: {:?}", &body.event_id);
|
||||
Error::BadRequest(ErrorKind::NotFound, "Event not found.")
|
||||
})?;
|
||||
|
||||
let room_id_str = event
|
||||
.get("room_id")
|
||||
|
@ -1214,10 +1226,6 @@ pub async fn get_event_authorization_route(
|
|||
pub async fn get_room_state_route(
|
||||
body: Ruma<get_room_state::v1::Request>,
|
||||
) -> Result<get_room_state::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let sender_servername = body
|
||||
.sender_servername
|
||||
.as_ref()
|
||||
|
@ -1294,10 +1302,6 @@ pub async fn get_room_state_route(
|
|||
pub async fn get_room_state_ids_route(
|
||||
body: Ruma<get_room_state_ids::v1::Request>,
|
||||
) -> Result<get_room_state_ids::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let sender_servername = body
|
||||
.sender_servername
|
||||
.as_ref()
|
||||
|
@ -1355,10 +1359,6 @@ pub async fn get_room_state_ids_route(
|
|||
pub async fn create_join_event_template_route(
|
||||
body: Ruma<prepare_join_event::v1::Request>,
|
||||
) -> Result<prepare_join_event::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
if !services().rooms.metadata.exists(&body.room_id)? {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
|
@ -1381,7 +1381,7 @@ pub async fn create_join_event_template_route(
|
|||
.globals
|
||||
.roomid_mutex_state
|
||||
.write()
|
||||
.unwrap()
|
||||
.await
|
||||
.entry(body.room_id.to_owned())
|
||||
.or_default(),
|
||||
);
|
||||
|
@ -1440,7 +1440,7 @@ pub async fn create_join_event_template_route(
|
|||
|
||||
let (_pdu, mut pdu_json) = services().rooms.timeline.create_hash_and_sign_event(
|
||||
PduBuilder {
|
||||
event_type: RoomEventType::RoomMember,
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
content,
|
||||
unsigned: None,
|
||||
state_key: Some(body.user_id.to_string()),
|
||||
|
@ -1465,11 +1465,7 @@ async fn create_join_event(
|
|||
sender_servername: &ServerName,
|
||||
room_id: &RoomId,
|
||||
pdu: &RawJsonValue,
|
||||
) -> Result<RoomState> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
) -> Result<create_join_event::v1::RoomState> {
|
||||
if !services().rooms.metadata.exists(room_id)? {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
|
@ -1551,7 +1547,7 @@ async fn create_join_event(
|
|||
.globals
|
||||
.roomid_mutex_federation
|
||||
.write()
|
||||
.unwrap()
|
||||
.await
|
||||
.entry(room_id.to_owned())
|
||||
.or_default(),
|
||||
);
|
||||
|
@ -1587,7 +1583,7 @@ async fn create_join_event(
|
|||
|
||||
services().sending.send_pdu(servers, &pdu_id)?;
|
||||
|
||||
Ok(RoomState {
|
||||
Ok(create_join_event::v1::RoomState {
|
||||
auth_chain: auth_chain_ids
|
||||
.filter_map(|id| services().rooms.timeline.get_pdu_json(&id).ok().flatten())
|
||||
.map(PduEvent::convert_to_outgoing_federation_event)
|
||||
|
@ -1628,7 +1624,18 @@ pub async fn create_join_event_v2_route(
|
|||
.as_ref()
|
||||
.expect("server is authenticated");
|
||||
|
||||
let room_state = create_join_event(sender_servername, &body.room_id, &body.pdu).await?;
|
||||
let create_join_event::v1::RoomState {
|
||||
auth_chain,
|
||||
state,
|
||||
event,
|
||||
} = create_join_event(sender_servername, &body.room_id, &body.pdu).await?;
|
||||
let room_state = create_join_event::v2::RoomState {
|
||||
members_omitted: false,
|
||||
auth_chain,
|
||||
state,
|
||||
event,
|
||||
servers_in_room: None,
|
||||
};
|
||||
|
||||
Ok(create_join_event::v2::Response { room_state })
|
||||
}
|
||||
|
@ -1639,10 +1646,6 @@ pub async fn create_join_event_v2_route(
|
|||
pub async fn create_invite_route(
|
||||
body: Ruma<create_invite::v2::Request>,
|
||||
) -> Result<create_invite::v2::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let sender_servername = body
|
||||
.sender_servername
|
||||
.as_ref()
|
||||
|
@ -1756,8 +1759,11 @@ pub async fn create_invite_route(
|
|||
pub async fn get_devices_route(
|
||||
body: Ruma<get_devices::v1::Request>,
|
||||
) -> Result<get_devices::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
if body.user_id.server_name() != services().globals.server_name() {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Tried to access user from other server.",
|
||||
));
|
||||
}
|
||||
|
||||
let sender_servername = body
|
||||
|
@ -1788,12 +1794,14 @@ pub async fn get_devices_route(
|
|||
})
|
||||
})
|
||||
.collect(),
|
||||
master_key: services()
|
||||
.users
|
||||
.get_master_key(&body.user_id, &|u| u.server_name() == sender_servername)?,
|
||||
master_key: services().users.get_master_key(None, &body.user_id, &|u| {
|
||||
u.server_name() == sender_servername
|
||||
})?,
|
||||
self_signing_key: services()
|
||||
.users
|
||||
.get_self_signing_key(&body.user_id, &|u| u.server_name() == sender_servername)?,
|
||||
.get_self_signing_key(None, &body.user_id, &|u| {
|
||||
u.server_name() == sender_servername
|
||||
})?,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1803,10 +1811,6 @@ pub async fn get_devices_route(
|
|||
pub async fn get_room_information_route(
|
||||
body: Ruma<get_room_information::v1::Request>,
|
||||
) -> Result<get_room_information::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let room_id = services()
|
||||
.rooms
|
||||
.alias
|
||||
|
@ -1828,8 +1832,11 @@ pub async fn get_room_information_route(
|
|||
pub async fn get_profile_information_route(
|
||||
body: Ruma<get_profile_information::v1::Request>,
|
||||
) -> Result<get_profile_information::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
if body.user_id.server_name() != services().globals.server_name() {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Tried to access user from other server.",
|
||||
));
|
||||
}
|
||||
|
||||
let mut displayname = None;
|
||||
|
@ -1864,8 +1871,15 @@ pub async fn get_profile_information_route(
|
|||
///
|
||||
/// Gets devices and identity keys for the given users.
|
||||
pub async fn get_keys_route(body: Ruma<get_keys::v1::Request>) -> Result<get_keys::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
if body
|
||||
.device_keys
|
||||
.iter()
|
||||
.any(|(u, _)| u.server_name() != services().globals.server_name())
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Tried to access user from other server.",
|
||||
));
|
||||
}
|
||||
|
||||
let result = get_keys_helper(None, &body.device_keys, |u| {
|
||||
|
@ -1886,8 +1900,15 @@ pub async fn get_keys_route(body: Ruma<get_keys::v1::Request>) -> Result<get_key
|
|||
pub async fn claim_keys_route(
|
||||
body: Ruma<claim_keys::v1::Request>,
|
||||
) -> Result<claim_keys::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
if body
|
||||
.one_time_keys
|
||||
.iter()
|
||||
.any(|(u, _)| u.server_name() != services().globals.server_name())
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Tried to access user from other server.",
|
||||
));
|
||||
}
|
||||
|
||||
let result = claim_keys_helper(&body.one_time_keys).await?;
|
||||
|
|
27
src/clap.rs
Normal file
27
src/clap.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
//! Integration with `clap`
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
/// Returns the current version of the crate with extra info if supplied
|
||||
///
|
||||
/// Set the environment variable `CONDUIT_VERSION_EXTRA` to any UTF-8 string to
|
||||
/// include it in parenthesis after the SemVer version. A common value are git
|
||||
/// commit hashes.
|
||||
fn version() -> String {
|
||||
let cargo_pkg_version = env!("CARGO_PKG_VERSION");
|
||||
|
||||
match option_env!("CONDUIT_VERSION_EXTRA") {
|
||||
Some(x) => format!("{} ({})", cargo_pkg_version, x),
|
||||
None => cargo_pkg_version.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Command line arguments
|
||||
#[derive(Parser)]
|
||||
#[clap(about, version = version())]
|
||||
pub struct Args {}
|
||||
|
||||
/// Parse command line arguments into structured data
|
||||
pub fn parse() -> Args {
|
||||
Args::parse()
|
||||
}
|
|
@ -28,6 +28,8 @@ pub struct Config {
|
|||
pub db_cache_capacity_mb: f64,
|
||||
#[serde(default = "true_fn")]
|
||||
pub enable_lightning_bolt: bool,
|
||||
#[serde(default = "true_fn")]
|
||||
pub allow_check_for_updates: bool,
|
||||
#[serde(default = "default_conduit_cache_capacity_modifier")]
|
||||
pub conduit_cache_capacity_modifier: f64,
|
||||
#[serde(default = "default_rocksdb_max_open_files")]
|
||||
|
@ -44,6 +46,7 @@ pub struct Config {
|
|||
pub max_fetch_prev_events: u16,
|
||||
#[serde(default = "false_fn")]
|
||||
pub allow_registration: bool,
|
||||
pub registration_token: Option<String>,
|
||||
#[serde(default = "true_fn")]
|
||||
pub allow_encryption: bool,
|
||||
#[serde(default = "false_fn")]
|
||||
|
@ -54,6 +57,7 @@ pub struct Config {
|
|||
pub allow_unstable_room_versions: bool,
|
||||
#[serde(default = "default_default_room_version")]
|
||||
pub default_room_version: RoomVersionId,
|
||||
pub well_known_client: Option<String>,
|
||||
#[serde(default = "false_fn")]
|
||||
pub allow_jaeger: bool,
|
||||
#[serde(default = "false_fn")]
|
||||
|
@ -61,7 +65,7 @@ pub struct Config {
|
|||
#[serde(default)]
|
||||
pub proxy: ProxyConfig,
|
||||
pub jwt_secret: Option<String>,
|
||||
#[serde(default = "Vec::new")]
|
||||
#[serde(default = "default_trusted_servers")]
|
||||
pub trusted_servers: Vec<OwnedServerName>,
|
||||
#[serde(default = "default_log")]
|
||||
pub log: String,
|
||||
|
@ -224,7 +228,7 @@ fn default_database_backend() -> String {
|
|||
}
|
||||
|
||||
fn default_db_cache_capacity_mb() -> f64 {
|
||||
1000.0
|
||||
300.0
|
||||
}
|
||||
|
||||
fn default_conduit_cache_capacity_modifier() -> f64 {
|
||||
|
@ -255,8 +259,12 @@ fn default_max_fetch_prev_events() -> u16 {
|
|||
100_u16
|
||||
}
|
||||
|
||||
fn default_trusted_servers() -> Vec<OwnedServerName> {
|
||||
vec![OwnedServerName::try_from("matrix.org").unwrap()]
|
||||
}
|
||||
|
||||
fn default_log() -> String {
|
||||
"warn,state_res=warn,_=off,sled=off".to_owned()
|
||||
"warn,state_res=warn,_=off".to_owned()
|
||||
}
|
||||
|
||||
fn default_turn_ttl() -> u64 {
|
||||
|
|
|
@ -29,7 +29,9 @@ use crate::Result;
|
|||
/// would be used for `ordinary.onion`, `matrix.myspecial.onion`, but not `hello.myspecial.onion`.
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[derive(Default)]
|
||||
pub enum ProxyConfig {
|
||||
#[default]
|
||||
None,
|
||||
Global {
|
||||
#[serde(deserialize_with = "crate::utils::deserialize_from_str")]
|
||||
|
@ -48,11 +50,6 @@ impl ProxyConfig {
|
|||
})
|
||||
}
|
||||
}
|
||||
impl Default for ProxyConfig {
|
||||
fn default() -> Self {
|
||||
ProxyConfig::None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct PartialProxyConfig {
|
||||
|
|
|
@ -38,6 +38,7 @@ pub trait KeyValueDatabaseEngine: Send + Sync {
|
|||
fn memory_usage(&self) -> Result<String> {
|
||||
Ok("Current database engine does not support memory usage reporting.".to_owned())
|
||||
}
|
||||
fn clear_caches(&self) {}
|
||||
}
|
||||
|
||||
pub trait KvTree: Send + Sync {
|
||||
|
|
|
@ -116,7 +116,7 @@ impl KvTree for PersyTree {
|
|||
match iter {
|
||||
Ok(iter) => Box::new(iter.filter_map(|(k, v)| {
|
||||
v.into_iter()
|
||||
.map(|val| ((*k).to_owned().into(), (*val).to_owned().into()))
|
||||
.map(|val| ((*k).to_owned(), (*val).to_owned()))
|
||||
.next()
|
||||
})),
|
||||
Err(e) => {
|
||||
|
@ -142,7 +142,7 @@ impl KvTree for PersyTree {
|
|||
Ok(iter) => {
|
||||
let map = iter.filter_map(|(k, v)| {
|
||||
v.into_iter()
|
||||
.map(|val| ((*k).to_owned().into(), (*val).to_owned().into()))
|
||||
.map(|val| ((*k).to_owned(), (*val).to_owned()))
|
||||
.next()
|
||||
});
|
||||
if backwards {
|
||||
|
@ -179,7 +179,7 @@ impl KvTree for PersyTree {
|
|||
iter.take_while(move |(k, _)| (*k).starts_with(&owned_prefix))
|
||||
.filter_map(|(k, v)| {
|
||||
v.into_iter()
|
||||
.map(|val| ((*k).to_owned().into(), (*val).to_owned().into()))
|
||||
.map(|val| ((*k).to_owned(), (*val).to_owned()))
|
||||
.next()
|
||||
}),
|
||||
)
|
||||
|
|
|
@ -23,30 +23,35 @@ pub struct RocksDbEngineTree<'a> {
|
|||
fn db_options(max_open_files: i32, rocksdb_cache: &rocksdb::Cache) -> rocksdb::Options {
|
||||
let mut block_based_options = rocksdb::BlockBasedOptions::default();
|
||||
block_based_options.set_block_cache(rocksdb_cache);
|
||||
|
||||
// "Difference of spinning disk"
|
||||
// https://zhangyuchi.gitbooks.io/rocksdbbook/content/RocksDB-Tuning-Guide.html
|
||||
block_based_options.set_bloom_filter(10.0, false);
|
||||
block_based_options.set_block_size(4 * 1024);
|
||||
block_based_options.set_cache_index_and_filter_blocks(true);
|
||||
block_based_options.set_pin_l0_filter_and_index_blocks_in_cache(true);
|
||||
block_based_options.set_optimize_filters_for_memory(true);
|
||||
|
||||
let mut db_opts = rocksdb::Options::default();
|
||||
db_opts.set_block_based_table_factory(&block_based_options);
|
||||
db_opts.set_optimize_filters_for_hits(true);
|
||||
db_opts.set_skip_stats_update_on_db_open(true);
|
||||
db_opts.set_level_compaction_dynamic_level_bytes(true);
|
||||
db_opts.set_target_file_size_base(256 * 1024 * 1024);
|
||||
//db_opts.set_compaction_readahead_size(2 * 1024 * 1024);
|
||||
//db_opts.set_use_direct_reads(true);
|
||||
//db_opts.set_use_direct_io_for_flush_and_compaction(true);
|
||||
db_opts.create_if_missing(true);
|
||||
db_opts.increase_parallelism(num_cpus::get() as i32);
|
||||
db_opts.set_max_open_files(max_open_files);
|
||||
db_opts.set_compression_type(rocksdb::DBCompressionType::Zstd);
|
||||
db_opts.set_compression_type(rocksdb::DBCompressionType::Lz4);
|
||||
db_opts.set_bottommost_compression_type(rocksdb::DBCompressionType::Zstd);
|
||||
db_opts.set_compaction_style(rocksdb::DBCompactionStyle::Level);
|
||||
db_opts.optimize_level_style_compaction(10 * 1024 * 1024);
|
||||
|
||||
let prefix_extractor = rocksdb::SliceTransform::create_fixed_prefix(1);
|
||||
db_opts.set_prefix_extractor(prefix_extractor);
|
||||
// https://github.com/facebook/rocksdb/wiki/Setup-Options-and-Basic-Tuning
|
||||
db_opts.set_level_compaction_dynamic_level_bytes(true);
|
||||
db_opts.set_max_background_jobs(6);
|
||||
db_opts.set_bytes_per_sync(1048576);
|
||||
|
||||
// https://github.com/facebook/rocksdb/issues/849
|
||||
db_opts.set_keep_log_file_num(100);
|
||||
|
||||
// https://github.com/facebook/rocksdb/wiki/WAL-Recovery-Modes#ktoleratecorruptedtailrecords
|
||||
//
|
||||
// Unclean shutdowns of a Matrix homeserver are likely to be fine when
|
||||
// recovered in this manner as it's likely any lost information will be
|
||||
// restored via federation.
|
||||
db_opts.set_wal_recovery_mode(rocksdb::DBRecoveryMode::TolerateCorruptedTailRecords);
|
||||
|
||||
db_opts
|
||||
}
|
||||
|
@ -54,7 +59,7 @@ fn db_options(max_open_files: i32, rocksdb_cache: &rocksdb::Cache) -> rocksdb::O
|
|||
impl KeyValueDatabaseEngine for Arc<Engine> {
|
||||
fn open(config: &Config) -> Result<Self> {
|
||||
let cache_capacity_bytes = (config.db_cache_capacity_mb * 1024.0 * 1024.0) as usize;
|
||||
let rocksdb_cache = rocksdb::Cache::new_lru_cache(cache_capacity_bytes).unwrap();
|
||||
let rocksdb_cache = rocksdb::Cache::new_lru_cache(cache_capacity_bytes);
|
||||
|
||||
let db_opts = db_options(config.rocksdb_max_open_files, &rocksdb_cache);
|
||||
|
||||
|
@ -121,6 +126,8 @@ impl KeyValueDatabaseEngine for Arc<Engine> {
|
|||
self.cache.get_pinned_usage() as f64 / 1024.0 / 1024.0,
|
||||
))
|
||||
}
|
||||
|
||||
fn clear_caches(&self) {}
|
||||
}
|
||||
|
||||
impl RocksDbEngineTree<'_> {
|
||||
|
@ -131,12 +138,17 @@ impl RocksDbEngineTree<'_> {
|
|||
|
||||
impl KvTree for RocksDbEngineTree<'_> {
|
||||
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
|
||||
Ok(self.db.rocks.get_cf(&self.cf(), key)?)
|
||||
let readoptions = rocksdb::ReadOptions::default();
|
||||
|
||||
Ok(self.db.rocks.get_cf_opt(&self.cf(), key, &readoptions)?)
|
||||
}
|
||||
|
||||
fn insert(&self, key: &[u8], value: &[u8]) -> Result<()> {
|
||||
let writeoptions = rocksdb::WriteOptions::default();
|
||||
let lock = self.write_lock.read().unwrap();
|
||||
self.db.rocks.put_cf(&self.cf(), key, value)?;
|
||||
self.db
|
||||
.rocks
|
||||
.put_cf_opt(&self.cf(), key, value, &writeoptions)?;
|
||||
drop(lock);
|
||||
|
||||
self.watchers.wake(key);
|
||||
|
@ -145,23 +157,32 @@ impl KvTree for RocksDbEngineTree<'_> {
|
|||
}
|
||||
|
||||
fn insert_batch<'a>(&self, iter: &mut dyn Iterator<Item = (Vec<u8>, Vec<u8>)>) -> Result<()> {
|
||||
let writeoptions = rocksdb::WriteOptions::default();
|
||||
for (key, value) in iter {
|
||||
self.db.rocks.put_cf(&self.cf(), key, value)?;
|
||||
self.db
|
||||
.rocks
|
||||
.put_cf_opt(&self.cf(), key, value, &writeoptions)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove(&self, key: &[u8]) -> Result<()> {
|
||||
Ok(self.db.rocks.delete_cf(&self.cf(), key)?)
|
||||
let writeoptions = rocksdb::WriteOptions::default();
|
||||
Ok(self
|
||||
.db
|
||||
.rocks
|
||||
.delete_cf_opt(&self.cf(), key, &writeoptions)?)
|
||||
}
|
||||
|
||||
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)> + 'a> {
|
||||
let readoptions = rocksdb::ReadOptions::default();
|
||||
|
||||
Box::new(
|
||||
self.db
|
||||
.rocks
|
||||
.iterator_cf(&self.cf(), rocksdb::IteratorMode::Start)
|
||||
//.map(|r| r.unwrap())
|
||||
.iterator_cf_opt(&self.cf(), readoptions, rocksdb::IteratorMode::Start)
|
||||
.map(|r| r.unwrap())
|
||||
.map(|(k, v)| (Vec::from(k), Vec::from(v))),
|
||||
)
|
||||
}
|
||||
|
@ -171,11 +192,14 @@ impl KvTree for RocksDbEngineTree<'_> {
|
|||
from: &[u8],
|
||||
backwards: bool,
|
||||
) -> Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)> + 'a> {
|
||||
let readoptions = rocksdb::ReadOptions::default();
|
||||
|
||||
Box::new(
|
||||
self.db
|
||||
.rocks
|
||||
.iterator_cf(
|
||||
.iterator_cf_opt(
|
||||
&self.cf(),
|
||||
readoptions,
|
||||
rocksdb::IteratorMode::From(
|
||||
from,
|
||||
if backwards {
|
||||
|
@ -185,29 +209,39 @@ impl KvTree for RocksDbEngineTree<'_> {
|
|||
},
|
||||
),
|
||||
)
|
||||
//.map(|r| r.unwrap())
|
||||
.map(|r| r.unwrap())
|
||||
.map(|(k, v)| (Vec::from(k), Vec::from(v))),
|
||||
)
|
||||
}
|
||||
|
||||
fn increment(&self, key: &[u8]) -> Result<Vec<u8>> {
|
||||
let readoptions = rocksdb::ReadOptions::default();
|
||||
let writeoptions = rocksdb::WriteOptions::default();
|
||||
|
||||
let lock = self.write_lock.write().unwrap();
|
||||
|
||||
let old = self.db.rocks.get_cf(&self.cf(), key)?;
|
||||
let old = self.db.rocks.get_cf_opt(&self.cf(), key, &readoptions)?;
|
||||
let new = utils::increment(old.as_deref()).unwrap();
|
||||
self.db.rocks.put_cf(&self.cf(), key, &new)?;
|
||||
self.db
|
||||
.rocks
|
||||
.put_cf_opt(&self.cf(), key, &new, &writeoptions)?;
|
||||
|
||||
drop(lock);
|
||||
Ok(new)
|
||||
}
|
||||
|
||||
fn increment_batch<'a>(&self, iter: &mut dyn Iterator<Item = Vec<u8>>) -> Result<()> {
|
||||
let readoptions = rocksdb::ReadOptions::default();
|
||||
let writeoptions = rocksdb::WriteOptions::default();
|
||||
|
||||
let lock = self.write_lock.write().unwrap();
|
||||
|
||||
for key in iter {
|
||||
let old = self.db.rocks.get_cf(&self.cf(), &key)?;
|
||||
let old = self.db.rocks.get_cf_opt(&self.cf(), &key, &readoptions)?;
|
||||
let new = utils::increment(old.as_deref()).unwrap();
|
||||
self.db.rocks.put_cf(&self.cf(), key, new)?;
|
||||
self.db
|
||||
.rocks
|
||||
.put_cf_opt(&self.cf(), key, new, &writeoptions)?;
|
||||
}
|
||||
|
||||
drop(lock);
|
||||
|
@ -219,14 +253,17 @@ impl KvTree for RocksDbEngineTree<'_> {
|
|||
&'a self,
|
||||
prefix: Vec<u8>,
|
||||
) -> Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)> + 'a> {
|
||||
let readoptions = rocksdb::ReadOptions::default();
|
||||
|
||||
Box::new(
|
||||
self.db
|
||||
.rocks
|
||||
.iterator_cf(
|
||||
.iterator_cf_opt(
|
||||
&self.cf(),
|
||||
readoptions,
|
||||
rocksdb::IteratorMode::From(&prefix, rocksdb::Direction::Forward),
|
||||
)
|
||||
//.map(|r| r.unwrap())
|
||||
.map(|r| r.unwrap())
|
||||
.map(|(k, v)| (Vec::from(k), Vec::from(v)))
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix)),
|
||||
)
|
||||
|
|
|
@ -33,7 +33,7 @@ impl Iterator for PreparedStatementIterator<'_> {
|
|||
struct NonAliasingBox<T>(*mut T);
|
||||
impl<T> Drop for NonAliasingBox<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe { Box::from_raw(self.0) };
|
||||
drop(unsafe { Box::from_raw(self.0) });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ use tokio::sync::watch;
|
|||
|
||||
#[derive(Default)]
|
||||
pub(super) struct Watchers {
|
||||
#[allow(clippy::type_complexity)]
|
||||
watchers: RwLock<HashMap<Vec<u8>, (watch::Sender<()>, watch::Receiver<()>)>>,
|
||||
}
|
||||
|
||||
|
|
|
@ -123,13 +123,12 @@ impl service::account_data::Data for KeyValueDatabase {
|
|||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||
.map(|(k, v)| {
|
||||
Ok::<_, Error>((
|
||||
RoomAccountDataEventType::try_from(
|
||||
RoomAccountDataEventType::from(
|
||||
utils::string_from_bytes(k.rsplit(|&b| b == 0xff).next().ok_or_else(
|
||||
|| Error::bad_database("RoomUserData ID in db is invalid."),
|
||||
)?)
|
||||
.map_err(|_| Error::bad_database("RoomUserData ID in db is invalid."))?,
|
||||
)
|
||||
.map_err(|_| Error::bad_database("RoomUserData ID in db is invalid."))?,
|
||||
),
|
||||
serde_json::from_slice::<Raw<AnyEphemeralRoomEvent>>(&v).map_err(|_| {
|
||||
Error::bad_database("Database contains invalid account data.")
|
||||
})?,
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
use ruma::api::appservice::Registration;
|
||||
|
||||
use crate::{database::KeyValueDatabase, service, utils, Error, Result};
|
||||
|
||||
impl service::appservice::Data for KeyValueDatabase {
|
||||
/// Registers an appservice and returns the ID to the caller
|
||||
fn register_appservice(&self, yaml: serde_yaml::Value) -> Result<String> {
|
||||
// TODO: Rumaify
|
||||
let id = yaml.get("id").unwrap().as_str().unwrap();
|
||||
fn register_appservice(&self, yaml: Registration) -> Result<String> {
|
||||
let id = yaml.id.as_str();
|
||||
self.id_appserviceregistrations.insert(
|
||||
id.as_bytes(),
|
||||
serde_yaml::to_string(&yaml).unwrap().as_bytes(),
|
||||
)?;
|
||||
self.cached_registrations
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(id.to_owned(), yaml.to_owned());
|
||||
|
||||
Ok(id.to_owned())
|
||||
}
|
||||
|
@ -25,33 +22,18 @@ impl service::appservice::Data for KeyValueDatabase {
|
|||
fn unregister_appservice(&self, service_name: &str) -> Result<()> {
|
||||
self.id_appserviceregistrations
|
||||
.remove(service_name.as_bytes())?;
|
||||
self.cached_registrations
|
||||
.write()
|
||||
.unwrap()
|
||||
.remove(service_name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_registration(&self, id: &str) -> Result<Option<serde_yaml::Value>> {
|
||||
self.cached_registrations
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(id)
|
||||
.map_or_else(
|
||||
|| {
|
||||
self.id_appserviceregistrations
|
||||
.get(id.as_bytes())?
|
||||
.map(|bytes| {
|
||||
serde_yaml::from_slice(&bytes).map_err(|_| {
|
||||
Error::bad_database(
|
||||
"Invalid registration bytes in id_appserviceregistrations.",
|
||||
)
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
},
|
||||
|r| Ok(Some(r.clone())),
|
||||
)
|
||||
fn get_registration(&self, id: &str) -> Result<Option<Registration>> {
|
||||
self.id_appserviceregistrations
|
||||
.get(id.as_bytes())?
|
||||
.map(|bytes| {
|
||||
serde_yaml::from_slice(&bytes).map_err(|_| {
|
||||
Error::bad_database("Invalid registration bytes in id_appserviceregistrations.")
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
fn iter_ids<'a>(&'a self) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>> {
|
||||
|
@ -64,7 +46,7 @@ impl service::appservice::Data for KeyValueDatabase {
|
|||
)))
|
||||
}
|
||||
|
||||
fn all(&self) -> Result<Vec<(String, serde_yaml::Value)>> {
|
||||
fn all(&self) -> Result<Vec<(String, Registration)>> {
|
||||
self.iter_ids()?
|
||||
.filter_map(|id| id.ok())
|
||||
.map(move |id| {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use futures_util::{stream::FuturesUnordered, StreamExt};
|
||||
use lru_cache::LruCache;
|
||||
use ruma::{
|
||||
api::federation::discovery::{ServerSigningKeys, VerifyKey},
|
||||
signatures::Ed25519KeyPair,
|
||||
|
@ -11,6 +12,7 @@ use ruma::{
|
|||
use crate::{database::KeyValueDatabase, service, services, utils, Error, Result};
|
||||
|
||||
pub const COUNTER: &[u8] = b"c";
|
||||
pub const LAST_CHECK_FOR_UPDATES_COUNT: &[u8] = b"u";
|
||||
|
||||
#[async_trait]
|
||||
impl service::globals::Data for KeyValueDatabase {
|
||||
|
@ -26,6 +28,23 @@ impl service::globals::Data for KeyValueDatabase {
|
|||
})
|
||||
}
|
||||
|
||||
fn last_check_for_updates_id(&self) -> Result<u64> {
|
||||
self.global
|
||||
.get(LAST_CHECK_FOR_UPDATES_COUNT)?
|
||||
.map_or(Ok(0_u64), |bytes| {
|
||||
utils::u64_from_bytes(&bytes).map_err(|_| {
|
||||
Error::bad_database("last check for updates count has invalid bytes.")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn update_check_for_updates_id(&self, id: u64) -> Result<()> {
|
||||
self.global
|
||||
.insert(LAST_CHECK_FOR_UPDATES_COUNT, &id.to_be_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn watch(&self, user_id: &UserId, device_id: &DeviceId) -> Result<()> {
|
||||
let userid_bytes = user_id.as_bytes().to_vec();
|
||||
let mut userid_prefix = userid_bytes.clone();
|
||||
|
@ -75,7 +94,9 @@ impl service::globals::Data for KeyValueDatabase {
|
|||
futures.push(self.pduid_pdu.watch_prefix(&short_roomid));
|
||||
|
||||
// EDUs
|
||||
futures.push(self.roomid_lasttypingupdate.watch_prefix(&roomid_bytes));
|
||||
futures.push(Box::into_pin(Box::new(async move {
|
||||
let _result = services().rooms.edus.typing.wait_for_update(&room_id).await;
|
||||
})));
|
||||
|
||||
futures.push(self.readreceiptid_readreceipt.watch_prefix(&roomid_prefix));
|
||||
|
||||
|
@ -118,8 +139,67 @@ impl service::globals::Data for KeyValueDatabase {
|
|||
self._db.cleanup()
|
||||
}
|
||||
|
||||
fn memory_usage(&self) -> Result<String> {
|
||||
self._db.memory_usage()
|
||||
fn memory_usage(&self) -> String {
|
||||
let pdu_cache = self.pdu_cache.lock().unwrap().len();
|
||||
let shorteventid_cache = self.shorteventid_cache.lock().unwrap().len();
|
||||
let auth_chain_cache = self.auth_chain_cache.lock().unwrap().len();
|
||||
let eventidshort_cache = self.eventidshort_cache.lock().unwrap().len();
|
||||
let statekeyshort_cache = self.statekeyshort_cache.lock().unwrap().len();
|
||||
let our_real_users_cache = self.our_real_users_cache.read().unwrap().len();
|
||||
let appservice_in_room_cache = self.appservice_in_room_cache.read().unwrap().len();
|
||||
let lasttimelinecount_cache = self.lasttimelinecount_cache.lock().unwrap().len();
|
||||
|
||||
let mut response = format!(
|
||||
"\
|
||||
pdu_cache: {pdu_cache}
|
||||
shorteventid_cache: {shorteventid_cache}
|
||||
auth_chain_cache: {auth_chain_cache}
|
||||
eventidshort_cache: {eventidshort_cache}
|
||||
statekeyshort_cache: {statekeyshort_cache}
|
||||
our_real_users_cache: {our_real_users_cache}
|
||||
appservice_in_room_cache: {appservice_in_room_cache}
|
||||
lasttimelinecount_cache: {lasttimelinecount_cache}\n"
|
||||
);
|
||||
if let Ok(db_stats) = self._db.memory_usage() {
|
||||
response += &db_stats;
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
fn clear_caches(&self, amount: u32) {
|
||||
if amount > 0 {
|
||||
let c = &mut *self.pdu_cache.lock().unwrap();
|
||||
*c = LruCache::new(c.capacity());
|
||||
}
|
||||
if amount > 1 {
|
||||
let c = &mut *self.shorteventid_cache.lock().unwrap();
|
||||
*c = LruCache::new(c.capacity());
|
||||
}
|
||||
if amount > 2 {
|
||||
let c = &mut *self.auth_chain_cache.lock().unwrap();
|
||||
*c = LruCache::new(c.capacity());
|
||||
}
|
||||
if amount > 3 {
|
||||
let c = &mut *self.eventidshort_cache.lock().unwrap();
|
||||
*c = LruCache::new(c.capacity());
|
||||
}
|
||||
if amount > 4 {
|
||||
let c = &mut *self.statekeyshort_cache.lock().unwrap();
|
||||
*c = LruCache::new(c.capacity());
|
||||
}
|
||||
if amount > 5 {
|
||||
let c = &mut *self.our_real_users_cache.write().unwrap();
|
||||
*c = HashMap::new();
|
||||
}
|
||||
if amount > 6 {
|
||||
let c = &mut *self.appservice_in_room_cache.write().unwrap();
|
||||
*c = HashMap::new();
|
||||
}
|
||||
if amount > 7 {
|
||||
let c = &mut *self.lasttimelinecount_cache.lock().unwrap();
|
||||
*c = HashMap::new();
|
||||
}
|
||||
}
|
||||
|
||||
fn load_keypair(&self) -> Result<Ed25519KeyPair> {
|
||||
|
@ -178,8 +258,8 @@ impl service::globals::Data for KeyValueDatabase {
|
|||
..
|
||||
} = new_keys;
|
||||
|
||||
keys.verify_keys.extend(verify_keys.into_iter());
|
||||
keys.old_verify_keys.extend(old_verify_keys.into_iter());
|
||||
keys.verify_keys.extend(verify_keys);
|
||||
keys.old_verify_keys.extend(old_verify_keys);
|
||||
|
||||
self.server_signingkeys.insert(
|
||||
origin.as_bytes(),
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
mod presence;
|
||||
mod read_receipt;
|
||||
mod typing;
|
||||
|
||||
use crate::{database::KeyValueDatabase, service};
|
||||
|
||||
|
|
|
@ -1,127 +0,0 @@
|
|||
use std::{collections::HashSet, mem};
|
||||
|
||||
use ruma::{OwnedUserId, RoomId, UserId};
|
||||
|
||||
use crate::{database::KeyValueDatabase, service, services, utils, Error, Result};
|
||||
|
||||
impl service::rooms::edus::typing::Data for KeyValueDatabase {
|
||||
fn typing_add(&self, user_id: &UserId, room_id: &RoomId, timeout: u64) -> Result<()> {
|
||||
let mut prefix = room_id.as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
let count = services().globals.next_count()?.to_be_bytes();
|
||||
|
||||
let mut room_typing_id = prefix;
|
||||
room_typing_id.extend_from_slice(&timeout.to_be_bytes());
|
||||
room_typing_id.push(0xff);
|
||||
room_typing_id.extend_from_slice(&count);
|
||||
|
||||
self.typingid_userid
|
||||
.insert(&room_typing_id, user_id.as_bytes())?;
|
||||
|
||||
self.roomid_lasttypingupdate
|
||||
.insert(room_id.as_bytes(), &count)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn typing_remove(&self, user_id: &UserId, room_id: &RoomId) -> Result<()> {
|
||||
let mut prefix = room_id.as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
let user_id = user_id.to_string();
|
||||
|
||||
let mut found_outdated = false;
|
||||
|
||||
// Maybe there are multiple ones from calling roomtyping_add multiple times
|
||||
for outdated_edu in self
|
||||
.typingid_userid
|
||||
.scan_prefix(prefix)
|
||||
.filter(|(_, v)| &**v == user_id.as_bytes())
|
||||
{
|
||||
self.typingid_userid.remove(&outdated_edu.0)?;
|
||||
found_outdated = true;
|
||||
}
|
||||
|
||||
if found_outdated {
|
||||
self.roomid_lasttypingupdate.insert(
|
||||
room_id.as_bytes(),
|
||||
&services().globals.next_count()?.to_be_bytes(),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn typings_maintain(&self, room_id: &RoomId) -> Result<()> {
|
||||
let mut prefix = room_id.as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
let current_timestamp = utils::millis_since_unix_epoch();
|
||||
|
||||
let mut found_outdated = false;
|
||||
|
||||
// Find all outdated edus before inserting a new one
|
||||
for outdated_edu in self
|
||||
.typingid_userid
|
||||
.scan_prefix(prefix)
|
||||
.map(|(key, _)| {
|
||||
Ok::<_, Error>((
|
||||
key.clone(),
|
||||
utils::u64_from_bytes(
|
||||
&key.splitn(2, |&b| b == 0xff).nth(1).ok_or_else(|| {
|
||||
Error::bad_database("RoomTyping has invalid timestamp or delimiters.")
|
||||
})?[0..mem::size_of::<u64>()],
|
||||
)
|
||||
.map_err(|_| Error::bad_database("RoomTyping has invalid timestamp bytes."))?,
|
||||
))
|
||||
})
|
||||
.filter_map(|r| r.ok())
|
||||
.take_while(|&(_, timestamp)| timestamp < current_timestamp)
|
||||
{
|
||||
// This is an outdated edu (time > timestamp)
|
||||
self.typingid_userid.remove(&outdated_edu.0)?;
|
||||
found_outdated = true;
|
||||
}
|
||||
|
||||
if found_outdated {
|
||||
self.roomid_lasttypingupdate.insert(
|
||||
room_id.as_bytes(),
|
||||
&services().globals.next_count()?.to_be_bytes(),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn last_typing_update(&self, room_id: &RoomId) -> Result<u64> {
|
||||
Ok(self
|
||||
.roomid_lasttypingupdate
|
||||
.get(room_id.as_bytes())?
|
||||
.map(|bytes| {
|
||||
utils::u64_from_bytes(&bytes).map_err(|_| {
|
||||
Error::bad_database("Count in roomid_lastroomactiveupdate is invalid.")
|
||||
})
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or(0))
|
||||
}
|
||||
|
||||
fn typings_all(&self, room_id: &RoomId) -> Result<HashSet<OwnedUserId>> {
|
||||
let mut prefix = room_id.as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
let mut user_ids = HashSet::new();
|
||||
|
||||
for (_, user_id) in self.typingid_userid.scan_prefix(prefix) {
|
||||
let user_id = UserId::parse(utils::string_from_bytes(&user_id).map_err(|_| {
|
||||
Error::bad_database("User ID in typingid_userid is invalid unicode.")
|
||||
})?)
|
||||
.map_err(|_| Error::bad_database("User ID in typingid_userid is invalid."))?;
|
||||
|
||||
user_ids.insert(user_id);
|
||||
}
|
||||
|
||||
Ok(user_ids)
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ mod state;
|
|||
mod state_accessor;
|
||||
mod state_cache;
|
||||
mod state_compressor;
|
||||
mod threads;
|
||||
mod timeline;
|
||||
mod user;
|
||||
|
||||
|
|
|
@ -1,10 +1,64 @@
|
|||
use std::sync::Arc;
|
||||
use std::{mem, sync::Arc};
|
||||
|
||||
use ruma::{EventId, RoomId};
|
||||
use ruma::{EventId, RoomId, UserId};
|
||||
|
||||
use crate::{database::KeyValueDatabase, service, Result};
|
||||
use crate::{
|
||||
database::KeyValueDatabase,
|
||||
service::{self, rooms::timeline::PduCount},
|
||||
services, utils, Error, PduEvent, Result,
|
||||
};
|
||||
|
||||
impl service::rooms::pdu_metadata::Data for KeyValueDatabase {
|
||||
fn add_relation(&self, from: u64, to: u64) -> Result<()> {
|
||||
let mut key = to.to_be_bytes().to_vec();
|
||||
key.extend_from_slice(&from.to_be_bytes());
|
||||
self.tofrom_relation.insert(&key, &[])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn relations_until<'a>(
|
||||
&'a self,
|
||||
user_id: &'a UserId,
|
||||
shortroomid: u64,
|
||||
target: u64,
|
||||
until: PduCount,
|
||||
) -> Result<Box<dyn Iterator<Item = Result<(PduCount, PduEvent)>> + 'a>> {
|
||||
let prefix = target.to_be_bytes().to_vec();
|
||||
let mut current = prefix.clone();
|
||||
|
||||
let count_raw = match until {
|
||||
PduCount::Normal(x) => x - 1,
|
||||
PduCount::Backfilled(x) => {
|
||||
current.extend_from_slice(&0_u64.to_be_bytes());
|
||||
u64::MAX - x - 1
|
||||
}
|
||||
};
|
||||
current.extend_from_slice(&count_raw.to_be_bytes());
|
||||
|
||||
Ok(Box::new(
|
||||
self.tofrom_relation
|
||||
.iter_from(¤t, true)
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||
.map(move |(tofrom, _data)| {
|
||||
let from = utils::u64_from_bytes(&tofrom[(mem::size_of::<u64>())..])
|
||||
.map_err(|_| Error::bad_database("Invalid count in tofrom_relation."))?;
|
||||
|
||||
let mut pduid = shortroomid.to_be_bytes().to_vec();
|
||||
pduid.extend_from_slice(&from.to_be_bytes());
|
||||
|
||||
let mut pdu = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_from_id(&pduid)?
|
||||
.ok_or_else(|| Error::bad_database("Pdu in tofrom_relation is invalid."))?;
|
||||
if pdu.sender != user_id {
|
||||
pdu.remove_transaction_id()?;
|
||||
}
|
||||
Ok((PduCount::Normal(from), pdu))
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
fn mark_as_referenced(&self, room_id: &RoomId, event_ids: &[Arc<EventId>]) -> Result<()> {
|
||||
for prev in event_ids {
|
||||
let mut key = room_id.as_bytes().to_vec();
|
||||
|
|
|
@ -157,10 +157,9 @@ impl service::rooms::short::Data for KeyValueDatabase {
|
|||
.ok_or_else(|| Error::bad_database("Invalid statekey in shortstatekey_statekey."))?;
|
||||
|
||||
let event_type =
|
||||
StateEventType::try_from(utils::string_from_bytes(eventtype_bytes).map_err(|_| {
|
||||
StateEventType::from(utils::string_from_bytes(eventtype_bytes).map_err(|_| {
|
||||
Error::bad_database("Event type in shortstatekey_statekey is invalid unicode.")
|
||||
})?)
|
||||
.map_err(|_| Error::bad_database("Event type in shortstatekey_statekey is invalid."))?;
|
||||
})?);
|
||||
|
||||
let state_key = utils::string_from_bytes(statekey_bytes).map_err(|_| {
|
||||
Error::bad_database("Statekey in shortstatekey_statekey is invalid unicode.")
|
||||
|
|
|
@ -16,11 +16,11 @@ impl service::rooms::state_accessor::Data for KeyValueDatabase {
|
|||
.1;
|
||||
let mut result = HashMap::new();
|
||||
let mut i = 0;
|
||||
for compressed in full_state.into_iter() {
|
||||
for compressed in full_state.iter() {
|
||||
let parsed = services()
|
||||
.rooms
|
||||
.state_compressor
|
||||
.parse_compressed_state_event(&compressed)?;
|
||||
.parse_compressed_state_event(compressed)?;
|
||||
result.insert(parsed.0, parsed.1);
|
||||
|
||||
i += 1;
|
||||
|
@ -45,11 +45,11 @@ impl service::rooms::state_accessor::Data for KeyValueDatabase {
|
|||
|
||||
let mut result = HashMap::new();
|
||||
let mut i = 0;
|
||||
for compressed in full_state {
|
||||
for compressed in full_state.iter() {
|
||||
let (_, eventid) = services()
|
||||
.rooms
|
||||
.state_compressor
|
||||
.parse_compressed_state_event(&compressed)?;
|
||||
.parse_compressed_state_event(compressed)?;
|
||||
if let Some(pdu) = services().rooms.timeline.get_pdu(&eventid)? {
|
||||
result.insert(
|
||||
(
|
||||
|
@ -95,13 +95,13 @@ impl service::rooms::state_accessor::Data for KeyValueDatabase {
|
|||
.expect("there is always one layer")
|
||||
.1;
|
||||
Ok(full_state
|
||||
.into_iter()
|
||||
.iter()
|
||||
.find(|bytes| bytes.starts_with(&shortstatekey.to_be_bytes()))
|
||||
.and_then(|compressed| {
|
||||
services()
|
||||
.rooms
|
||||
.state_compressor
|
||||
.parse_compressed_state_event(&compressed)
|
||||
.parse_compressed_state_event(compressed)
|
||||
.ok()
|
||||
.map(|(_, id)| id)
|
||||
}))
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use regex::Regex;
|
||||
use ruma::{
|
||||
events::{AnyStrippedStateEvent, AnySyncStateEvent},
|
||||
serde::Raw,
|
||||
OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
|
||||
};
|
||||
|
||||
use crate::{database::KeyValueDatabase, service, services, utils, Error, Result};
|
||||
use crate::{
|
||||
database::KeyValueDatabase,
|
||||
service::{self, appservice::RegistrationInfo},
|
||||
services, utils, Error, Result,
|
||||
};
|
||||
|
||||
impl service::rooms::state_cache::Data for KeyValueDatabase {
|
||||
fn mark_as_once_joined(&self, user_id: &UserId, room_id: &RoomId) -> Result<()> {
|
||||
|
@ -184,46 +187,28 @@ impl service::rooms::state_cache::Data for KeyValueDatabase {
|
|||
}
|
||||
|
||||
#[tracing::instrument(skip(self, room_id, appservice))]
|
||||
fn appservice_in_room(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
appservice: &(String, serde_yaml::Value),
|
||||
) -> Result<bool> {
|
||||
fn appservice_in_room(&self, room_id: &RoomId, appservice: &RegistrationInfo) -> Result<bool> {
|
||||
let maybe = self
|
||||
.appservice_in_room_cache
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(room_id)
|
||||
.and_then(|map| map.get(&appservice.0))
|
||||
.and_then(|map| map.get(&appservice.registration.id))
|
||||
.copied();
|
||||
|
||||
if let Some(b) = maybe {
|
||||
Ok(b)
|
||||
} else if let Some(namespaces) = appservice.1.get("namespaces") {
|
||||
let users = namespaces
|
||||
.get("users")
|
||||
.and_then(|users| users.as_sequence())
|
||||
.map_or_else(Vec::new, |users| {
|
||||
users
|
||||
.iter()
|
||||
.filter_map(|users| Regex::new(users.get("regex")?.as_str()?).ok())
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
let bridge_user_id = appservice
|
||||
.1
|
||||
.get("sender_localpart")
|
||||
.and_then(|string| string.as_str())
|
||||
.and_then(|string| {
|
||||
UserId::parse_with_server_name(string, services().globals.server_name()).ok()
|
||||
});
|
||||
} else {
|
||||
let bridge_user_id = UserId::parse_with_server_name(
|
||||
appservice.registration.sender_localpart.as_str(),
|
||||
services().globals.server_name(),
|
||||
)
|
||||
.ok();
|
||||
|
||||
let in_room = bridge_user_id
|
||||
.map_or(false, |id| self.is_joined(&id, room_id).unwrap_or(false))
|
||||
|| self.room_members(room_id).any(|userid| {
|
||||
userid.map_or(false, |userid| {
|
||||
users.iter().any(|r| r.is_match(userid.as_str()))
|
||||
})
|
||||
userid.map_or(false, |userid| appservice.users.is_match(userid.as_str()))
|
||||
});
|
||||
|
||||
self.appservice_in_room_cache
|
||||
|
@ -231,11 +216,9 @@ impl service::rooms::state_cache::Data for KeyValueDatabase {
|
|||
.unwrap()
|
||||
.entry(room_id.to_owned())
|
||||
.or_default()
|
||||
.insert(appservice.0.clone(), in_room);
|
||||
.insert(appservice.registration.id.clone(), in_room);
|
||||
|
||||
Ok(in_room)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -471,6 +454,7 @@ impl service::rooms::state_cache::Data for KeyValueDatabase {
|
|||
}
|
||||
|
||||
/// Returns an iterator over all rooms a user was invited to.
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn rooms_invited<'a>(
|
||||
&'a self,
|
||||
|
@ -549,6 +533,7 @@ impl service::rooms::state_cache::Data for KeyValueDatabase {
|
|||
}
|
||||
|
||||
/// Returns an iterator over all rooms a user left.
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn rooms_left<'a>(
|
||||
&'a self,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::HashSet, mem::size_of};
|
||||
use std::{collections::HashSet, mem::size_of, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
database::KeyValueDatabase,
|
||||
|
@ -37,20 +37,20 @@ impl service::rooms::state_compressor::Data for KeyValueDatabase {
|
|||
|
||||
Ok(StateDiff {
|
||||
parent,
|
||||
added,
|
||||
removed,
|
||||
added: Arc::new(added),
|
||||
removed: Arc::new(removed),
|
||||
})
|
||||
}
|
||||
|
||||
fn save_statediff(&self, shortstatehash: u64, diff: StateDiff) -> Result<()> {
|
||||
let mut value = diff.parent.unwrap_or(0).to_be_bytes().to_vec();
|
||||
for new in &diff.added {
|
||||
for new in diff.added.iter() {
|
||||
value.extend_from_slice(&new[..]);
|
||||
}
|
||||
|
||||
if !diff.removed.is_empty() {
|
||||
value.extend_from_slice(&0_u64.to_be_bytes());
|
||||
for removed in &diff.removed {
|
||||
for removed in diff.removed.iter() {
|
||||
value.extend_from_slice(&removed[..]);
|
||||
}
|
||||
}
|
||||
|
|
78
src/database/key_value/rooms/threads.rs
Normal file
78
src/database/key_value/rooms/threads.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
use std::mem;
|
||||
|
||||
use ruma::{api::client::threads::get_threads::v1::IncludeThreads, OwnedUserId, RoomId, UserId};
|
||||
|
||||
use crate::{database::KeyValueDatabase, service, services, utils, Error, PduEvent, Result};
|
||||
|
||||
impl service::rooms::threads::Data for KeyValueDatabase {
|
||||
fn threads_until<'a>(
|
||||
&'a self,
|
||||
user_id: &'a UserId,
|
||||
room_id: &'a RoomId,
|
||||
until: u64,
|
||||
_include: &'a IncludeThreads,
|
||||
) -> Result<Box<dyn Iterator<Item = Result<(u64, PduEvent)>> + 'a>> {
|
||||
let prefix = services()
|
||||
.rooms
|
||||
.short
|
||||
.get_shortroomid(room_id)?
|
||||
.expect("room exists")
|
||||
.to_be_bytes()
|
||||
.to_vec();
|
||||
|
||||
let mut current = prefix.clone();
|
||||
current.extend_from_slice(&(until - 1).to_be_bytes());
|
||||
|
||||
Ok(Box::new(
|
||||
self.threadid_userids
|
||||
.iter_from(¤t, true)
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||
.map(move |(pduid, _users)| {
|
||||
let count = utils::u64_from_bytes(&pduid[(mem::size_of::<u64>())..])
|
||||
.map_err(|_| Error::bad_database("Invalid pduid in threadid_userids."))?;
|
||||
let mut pdu = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_from_id(&pduid)?
|
||||
.ok_or_else(|| {
|
||||
Error::bad_database("Invalid pduid reference in threadid_userids")
|
||||
})?;
|
||||
if pdu.sender != user_id {
|
||||
pdu.remove_transaction_id()?;
|
||||
}
|
||||
Ok((count, pdu))
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
fn update_participants(&self, root_id: &[u8], participants: &[OwnedUserId]) -> Result<()> {
|
||||
let users = participants
|
||||
.iter()
|
||||
.map(|user| user.as_bytes())
|
||||
.collect::<Vec<_>>()
|
||||
.join(&[0xff][..]);
|
||||
|
||||
self.threadid_userids.insert(root_id, &users)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_participants(&self, root_id: &[u8]) -> Result<Option<Vec<OwnedUserId>>> {
|
||||
if let Some(users) = self.threadid_userids.get(root_id)? {
|
||||
Ok(Some(
|
||||
users
|
||||
.split(|b| *b == 0xff)
|
||||
.map(|bytes| {
|
||||
UserId::parse(utils::string_from_bytes(bytes).map_err(|_| {
|
||||
Error::bad_database("Invalid UserId bytes in threadid_userids.")
|
||||
})?)
|
||||
.map_err(|_| Error::bad_database("Invalid UserId in threadid_userids."))
|
||||
})
|
||||
.filter_map(|r| r.ok())
|
||||
.collect(),
|
||||
))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -39,11 +39,10 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
|||
|
||||
/// Returns the `count` of this pdu's id.
|
||||
fn get_pdu_count(&self, event_id: &EventId) -> Result<Option<PduCount>> {
|
||||
Ok(self
|
||||
.eventid_pduid
|
||||
self.eventid_pduid
|
||||
.get(event_id.as_bytes())?
|
||||
.map(|pdu_id| pdu_count(&pdu_id))
|
||||
.transpose()?)
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Returns the json of a pdu.
|
||||
|
@ -80,12 +79,10 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
|||
|
||||
/// Returns the pdu's id.
|
||||
fn get_pdu_id(&self, event_id: &EventId) -> Result<Option<Vec<u8>>> {
|
||||
Ok(self.eventid_pduid.get(event_id.as_bytes())?)
|
||||
self.eventid_pduid.get(event_id.as_bytes())
|
||||
}
|
||||
|
||||
/// Returns the pdu.
|
||||
///
|
||||
/// Checks the `eventid_outlierpdu` Tree if not found in the timeline.
|
||||
fn get_non_outlier_pdu(&self, event_id: &EventId) -> Result<Option<PduEvent>> {
|
||||
self.eventid_pduid
|
||||
.get(event_id.as_bytes())?
|
||||
|
@ -198,19 +195,30 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
|||
}
|
||||
|
||||
/// Removes a pdu and creates a new one with the same id.
|
||||
fn replace_pdu(&self, pdu_id: &[u8], pdu: &PduEvent) -> Result<()> {
|
||||
fn replace_pdu(
|
||||
&self,
|
||||
pdu_id: &[u8],
|
||||
pdu_json: &CanonicalJsonObject,
|
||||
pdu: &PduEvent,
|
||||
) -> Result<()> {
|
||||
if self.pduid_pdu.get(pdu_id)?.is_some() {
|
||||
self.pduid_pdu.insert(
|
||||
pdu_id,
|
||||
&serde_json::to_vec(pdu).expect("CanonicalJsonObject is always a valid"),
|
||||
&serde_json::to_vec(pdu_json).expect("CanonicalJsonObject is always a valid"),
|
||||
)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::BadRequest(
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"PDU does not exist.",
|
||||
))
|
||||
));
|
||||
}
|
||||
|
||||
self.pdu_cache
|
||||
.lock()
|
||||
.unwrap()
|
||||
.remove(&(*pdu.event_id).to_owned());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns an iterator over all events and their tokens in a room that happened before the
|
||||
|
@ -221,7 +229,7 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
|||
room_id: &RoomId,
|
||||
until: PduCount,
|
||||
) -> Result<Box<dyn Iterator<Item = Result<(PduCount, PduEvent)>> + 'a>> {
|
||||
let (prefix, current) = count_to_id(&room_id, until, 1, true)?;
|
||||
let (prefix, current) = count_to_id(room_id, until, 1, true)?;
|
||||
|
||||
let user_id = user_id.to_owned();
|
||||
|
||||
|
@ -235,6 +243,7 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
|||
if pdu.sender != user_id {
|
||||
pdu.remove_transaction_id()?;
|
||||
}
|
||||
pdu.add_age()?;
|
||||
let count = pdu_count(&pdu_id)?;
|
||||
Ok((count, pdu))
|
||||
}),
|
||||
|
@ -247,7 +256,7 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
|||
room_id: &RoomId,
|
||||
from: PduCount,
|
||||
) -> Result<Box<dyn Iterator<Item = Result<(PduCount, PduEvent)>> + 'a>> {
|
||||
let (prefix, current) = count_to_id(&room_id, from, 1, false)?;
|
||||
let (prefix, current) = count_to_id(room_id, from, 1, false)?;
|
||||
|
||||
let user_id = user_id.to_owned();
|
||||
|
||||
|
@ -261,6 +270,7 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
|||
if pdu.sender != user_id {
|
||||
pdu.remove_transaction_id()?;
|
||||
}
|
||||
pdu.add_age()?;
|
||||
let count = pdu_count(&pdu_id)?;
|
||||
Ok((count, pdu))
|
||||
}),
|
||||
|
@ -321,7 +331,7 @@ fn count_to_id(
|
|||
.rooms
|
||||
.short
|
||||
.get_shortroomid(room_id)?
|
||||
.expect("room exists")
|
||||
.ok_or_else(|| Error::bad_database("Looked for bad shortroomid in timeline"))?
|
||||
.to_be_bytes()
|
||||
.to_vec();
|
||||
let mut pdu_id = prefix.clone();
|
||||
|
|
|
@ -146,10 +146,9 @@ impl service::users::Data for KeyValueDatabase {
|
|||
self.userid_avatarurl
|
||||
.get(user_id.as_bytes())?
|
||||
.map(|bytes| {
|
||||
let s = utils::string_from_bytes(&bytes)
|
||||
.map_err(|_| Error::bad_database("Avatar URL in db is invalid."))?;
|
||||
s.try_into()
|
||||
utils::string_from_bytes(&bytes)
|
||||
.map_err(|_| Error::bad_database("Avatar URL in db is invalid."))
|
||||
.map(Into::into)
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
@ -449,33 +448,13 @@ impl service::users::Data for KeyValueDatabase {
|
|||
master_key: &Raw<CrossSigningKey>,
|
||||
self_signing_key: &Option<Raw<CrossSigningKey>>,
|
||||
user_signing_key: &Option<Raw<CrossSigningKey>>,
|
||||
notify: bool,
|
||||
) -> Result<()> {
|
||||
// TODO: Check signatures
|
||||
|
||||
let mut prefix = user_id.as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
// Master key
|
||||
let mut master_key_ids = master_key
|
||||
.deserialize()
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid master key"))?
|
||||
.keys
|
||||
.into_values();
|
||||
|
||||
let master_key_id = master_key_ids.next().ok_or(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Master key contained no key.",
|
||||
))?;
|
||||
|
||||
if master_key_ids.next().is_some() {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Master key contained more than one key.",
|
||||
));
|
||||
}
|
||||
|
||||
let mut master_key_key = prefix.clone();
|
||||
master_key_key.extend_from_slice(master_key_id.as_bytes());
|
||||
let (master_key_key, _) = self.parse_master_key(user_id, master_key)?;
|
||||
|
||||
self.keyid_key
|
||||
.insert(&master_key_key, master_key.json().get().as_bytes())?;
|
||||
|
@ -551,7 +530,9 @@ impl service::users::Data for KeyValueDatabase {
|
|||
.insert(user_id.as_bytes(), &user_signing_key_key)?;
|
||||
}
|
||||
|
||||
self.mark_device_key_update(user_id)?;
|
||||
if notify {
|
||||
self.mark_device_key_update(user_id)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -592,7 +573,6 @@ impl service::users::Data for KeyValueDatabase {
|
|||
&serde_json::to_vec(&cross_signing_key).expect("CrossSigningKey::to_vec always works"),
|
||||
)?;
|
||||
|
||||
// TODO: Should we notify about this change?
|
||||
self.mark_device_key_update(target_id)?;
|
||||
|
||||
Ok(())
|
||||
|
@ -691,45 +671,80 @@ impl service::users::Data for KeyValueDatabase {
|
|||
})
|
||||
}
|
||||
|
||||
fn parse_master_key(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
master_key: &Raw<CrossSigningKey>,
|
||||
) -> Result<(Vec<u8>, CrossSigningKey)> {
|
||||
let mut prefix = user_id.as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
let master_key = master_key
|
||||
.deserialize()
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid master key"))?;
|
||||
let mut master_key_ids = master_key.keys.values();
|
||||
let master_key_id = master_key_ids.next().ok_or(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Master key contained no key.",
|
||||
))?;
|
||||
if master_key_ids.next().is_some() {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Master key contained more than one key.",
|
||||
));
|
||||
}
|
||||
let mut master_key_key = prefix.clone();
|
||||
master_key_key.extend_from_slice(master_key_id.as_bytes());
|
||||
Ok((master_key_key, master_key))
|
||||
}
|
||||
|
||||
fn get_key(
|
||||
&self,
|
||||
key: &[u8],
|
||||
sender_user: Option<&UserId>,
|
||||
user_id: &UserId,
|
||||
allowed_signatures: &dyn Fn(&UserId) -> bool,
|
||||
) -> Result<Option<Raw<CrossSigningKey>>> {
|
||||
self.keyid_key.get(key)?.map_or(Ok(None), |bytes| {
|
||||
let mut cross_signing_key = serde_json::from_slice::<serde_json::Value>(&bytes)
|
||||
.map_err(|_| Error::bad_database("CrossSigningKey in db is invalid."))?;
|
||||
clean_signatures(
|
||||
&mut cross_signing_key,
|
||||
sender_user,
|
||||
user_id,
|
||||
allowed_signatures,
|
||||
)?;
|
||||
|
||||
Ok(Some(Raw::from_json(
|
||||
serde_json::value::to_raw_value(&cross_signing_key)
|
||||
.expect("Value to RawValue serialization"),
|
||||
)))
|
||||
})
|
||||
}
|
||||
|
||||
fn get_master_key(
|
||||
&self,
|
||||
sender_user: Option<&UserId>,
|
||||
user_id: &UserId,
|
||||
allowed_signatures: &dyn Fn(&UserId) -> bool,
|
||||
) -> Result<Option<Raw<CrossSigningKey>>> {
|
||||
self.userid_masterkeyid
|
||||
.get(user_id.as_bytes())?
|
||||
.map_or(Ok(None), |key| {
|
||||
self.keyid_key.get(&key)?.map_or(Ok(None), |bytes| {
|
||||
let mut cross_signing_key = serde_json::from_slice::<serde_json::Value>(&bytes)
|
||||
.map_err(|_| Error::bad_database("CrossSigningKey in db is invalid."))?;
|
||||
clean_signatures(&mut cross_signing_key, user_id, allowed_signatures)?;
|
||||
|
||||
Ok(Some(Raw::from_json(
|
||||
serde_json::value::to_raw_value(&cross_signing_key)
|
||||
.expect("Value to RawValue serialization"),
|
||||
)))
|
||||
})
|
||||
self.get_key(&key, sender_user, user_id, allowed_signatures)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_self_signing_key(
|
||||
&self,
|
||||
sender_user: Option<&UserId>,
|
||||
user_id: &UserId,
|
||||
allowed_signatures: &dyn Fn(&UserId) -> bool,
|
||||
) -> Result<Option<Raw<CrossSigningKey>>> {
|
||||
self.userid_selfsigningkeyid
|
||||
.get(user_id.as_bytes())?
|
||||
.map_or(Ok(None), |key| {
|
||||
self.keyid_key.get(&key)?.map_or(Ok(None), |bytes| {
|
||||
let mut cross_signing_key = serde_json::from_slice::<serde_json::Value>(&bytes)
|
||||
.map_err(|_| Error::bad_database("CrossSigningKey in db is invalid."))?;
|
||||
clean_signatures(&mut cross_signing_key, user_id, allowed_signatures)?;
|
||||
|
||||
Ok(Some(Raw::from_json(
|
||||
serde_json::value::to_raw_value(&cross_signing_key)
|
||||
.expect("Value to RawValue serialization"),
|
||||
)))
|
||||
})
|
||||
self.get_key(&key, sender_user, user_id, allowed_signatures)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -930,6 +945,8 @@ impl service::users::Data for KeyValueDatabase {
|
|||
}
|
||||
}
|
||||
|
||||
impl KeyValueDatabase {}
|
||||
|
||||
/// Will only return with Some(username) if the password was not empty and the
|
||||
/// username could be successfully parsed.
|
||||
/// If utils::string_from_bytes(...) returns an error that username will be skipped
|
||||
|
|
|
@ -8,6 +8,7 @@ use crate::{
|
|||
use abstraction::{KeyValueDatabaseEngine, KvTree};
|
||||
use directories::ProjectDirs;
|
||||
use lru_cache::LruCache;
|
||||
|
||||
use ruma::{
|
||||
events::{
|
||||
push_rules::{PushRulesEvent, PushRulesEventContent},
|
||||
|
@ -18,6 +19,7 @@ use ruma::{
|
|||
CanonicalJsonValue, EventId, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId,
|
||||
UserId,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
fs::{self, remove_dir_all},
|
||||
|
@ -25,7 +27,9 @@ use std::{
|
|||
mem::size_of,
|
||||
path::Path,
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::time::interval;
|
||||
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
|
@ -67,8 +71,6 @@ pub struct KeyValueDatabase {
|
|||
pub(super) readreceiptid_readreceipt: Arc<dyn KvTree>, // ReadReceiptId = RoomId + Count + UserId
|
||||
pub(super) roomuserid_privateread: Arc<dyn KvTree>, // RoomUserId = Room + User, PrivateRead = Count
|
||||
pub(super) roomuserid_lastprivatereadupdate: Arc<dyn KvTree>, // LastPrivateReadUpdate = Count
|
||||
pub(super) typingid_userid: Arc<dyn KvTree>, // TypingId = RoomId + TimeoutTime + Count
|
||||
pub(super) roomid_lasttypingupdate: Arc<dyn KvTree>, // LastRoomTypingUpdate = Count
|
||||
pub(super) presenceid_presence: Arc<dyn KvTree>, // PresenceId = RoomId + Count + UserId
|
||||
pub(super) userid_lastpresenceupdate: Arc<dyn KvTree>, // LastPresenceUpdate = Count
|
||||
|
||||
|
@ -80,6 +82,8 @@ pub struct KeyValueDatabase {
|
|||
pub(super) aliasid_alias: Arc<dyn KvTree>, // AliasId = RoomId + Count
|
||||
pub(super) publicroomids: Arc<dyn KvTree>,
|
||||
|
||||
pub(super) threadid_userids: Arc<dyn KvTree>, // ThreadId = RoomId + Count
|
||||
|
||||
pub(super) tokenids: Arc<dyn KvTree>, // TokenId = ShortRoomId + Token + PduIdCount
|
||||
|
||||
/// Participating servers in a room.
|
||||
|
@ -128,6 +132,8 @@ pub struct KeyValueDatabase {
|
|||
pub(super) eventid_outlierpdu: Arc<dyn KvTree>,
|
||||
pub(super) softfailedeventids: Arc<dyn KvTree>,
|
||||
|
||||
/// ShortEventId + ShortEventId -> ().
|
||||
pub(super) tofrom_relation: Arc<dyn KvTree>,
|
||||
/// RoomId + EventId -> Parent PDU EventId.
|
||||
pub(super) referencedevents: Arc<dyn KvTree>,
|
||||
|
||||
|
@ -155,7 +161,6 @@ pub struct KeyValueDatabase {
|
|||
//pub pusher: pusher::PushData,
|
||||
pub(super) senderkey_pusher: Arc<dyn KvTree>,
|
||||
|
||||
pub(super) cached_registrations: Arc<RwLock<HashMap<String, serde_yaml::Value>>>,
|
||||
pub(super) pdu_cache: Mutex<LruCache<OwnedEventId, Arc<PduEvent>>>,
|
||||
pub(super) shorteventid_cache: Mutex<LruCache<u64, Arc<EventId>>>,
|
||||
pub(super) auth_chain_cache: Mutex<LruCache<Vec<u64>, Arc<HashSet<u64>>>>,
|
||||
|
@ -260,6 +265,10 @@ impl KeyValueDatabase {
|
|||
}
|
||||
};
|
||||
|
||||
if config.registration_token == Some(String::new()) {
|
||||
return Err(Error::bad_config("Registration token is empty"));
|
||||
}
|
||||
|
||||
if config.max_request_size < 1024 {
|
||||
error!(?config.max_request_size, "Max request size is less than 1KB. Please increase it.");
|
||||
}
|
||||
|
@ -290,8 +299,6 @@ impl KeyValueDatabase {
|
|||
roomuserid_privateread: builder.open_tree("roomuserid_privateread")?, // "Private" read receipt
|
||||
roomuserid_lastprivatereadupdate: builder
|
||||
.open_tree("roomuserid_lastprivatereadupdate")?,
|
||||
typingid_userid: builder.open_tree("typingid_userid")?,
|
||||
roomid_lasttypingupdate: builder.open_tree("roomid_lasttypingupdate")?,
|
||||
presenceid_presence: builder.open_tree("presenceid_presence")?,
|
||||
userid_lastpresenceupdate: builder.open_tree("userid_lastpresenceupdate")?,
|
||||
pduid_pdu: builder.open_tree("pduid_pdu")?,
|
||||
|
@ -302,6 +309,8 @@ impl KeyValueDatabase {
|
|||
aliasid_alias: builder.open_tree("aliasid_alias")?,
|
||||
publicroomids: builder.open_tree("publicroomids")?,
|
||||
|
||||
threadid_userids: builder.open_tree("threadid_userids")?,
|
||||
|
||||
tokenids: builder.open_tree("tokenids")?,
|
||||
|
||||
roomserverids: builder.open_tree("roomserverids")?,
|
||||
|
@ -342,6 +351,7 @@ impl KeyValueDatabase {
|
|||
eventid_outlierpdu: builder.open_tree("eventid_outlierpdu")?,
|
||||
softfailedeventids: builder.open_tree("softfailedeventids")?,
|
||||
|
||||
tofrom_relation: builder.open_tree("tofrom_relation")?,
|
||||
referencedevents: builder.open_tree("referencedevents")?,
|
||||
roomuserdataid_accountdata: builder.open_tree("roomuserdataid_accountdata")?,
|
||||
roomusertype_roomuserdataid: builder.open_tree("roomusertype_roomuserdataid")?,
|
||||
|
@ -358,7 +368,6 @@ impl KeyValueDatabase {
|
|||
global: builder.open_tree("global")?,
|
||||
server_signingkeys: builder.open_tree("server_signingkeys")?,
|
||||
|
||||
cached_registrations: Arc::new(RwLock::new(HashMap::new())),
|
||||
pdu_cache: Mutex::new(LruCache::new(
|
||||
config
|
||||
.pdu_cache_capacity
|
||||
|
@ -411,7 +420,7 @@ impl KeyValueDatabase {
|
|||
}
|
||||
|
||||
// If the database has any data, perform data migrations before starting
|
||||
let latest_database_version = 12;
|
||||
let latest_database_version = 13;
|
||||
|
||||
if services().users.count()? > 0 {
|
||||
// MIGRATIONS
|
||||
|
@ -580,8 +589,8 @@ impl KeyValueDatabase {
|
|||
|
||||
services().rooms.state_compressor.save_state_from_diff(
|
||||
current_sstatehash,
|
||||
statediffnew,
|
||||
statediffremoved,
|
||||
Arc::new(statediffnew),
|
||||
Arc::new(statediffremoved),
|
||||
2, // every state change is 2 event changes on average
|
||||
states_parents,
|
||||
)?;
|
||||
|
@ -838,7 +847,9 @@ impl KeyValueDatabase {
|
|||
if rule.is_some() {
|
||||
let mut rule = rule.unwrap().clone();
|
||||
rule.rule_id = content_rule_transformation[1].to_owned();
|
||||
rules_list.content.remove(content_rule_transformation[0]);
|
||||
rules_list
|
||||
.content
|
||||
.shift_remove(content_rule_transformation[0]);
|
||||
rules_list.content.insert(rule);
|
||||
}
|
||||
}
|
||||
|
@ -861,7 +872,7 @@ impl KeyValueDatabase {
|
|||
if let Some(rule) = rule {
|
||||
let mut rule = rule.clone();
|
||||
rule.rule_id = transformation[1].to_owned();
|
||||
rules_list.underride.remove(transformation[0]);
|
||||
rules_list.underride.shift_remove(transformation[0]);
|
||||
rules_list.underride.insert(rule);
|
||||
}
|
||||
}
|
||||
|
@ -880,7 +891,47 @@ impl KeyValueDatabase {
|
|||
warn!("Migration: 11 -> 12 finished");
|
||||
}
|
||||
|
||||
// This migration can be reused as-is anytime the server-default rules are updated.
|
||||
if services().globals.database_version()? < 13 {
|
||||
for username in services().users.list_local_users()? {
|
||||
let user = match UserId::parse_with_server_name(
|
||||
username.clone(),
|
||||
services().globals.server_name(),
|
||||
) {
|
||||
Ok(u) => u,
|
||||
Err(e) => {
|
||||
warn!("Invalid username {username}: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let raw_rules_list = services()
|
||||
.account_data
|
||||
.get(
|
||||
None,
|
||||
&user,
|
||||
GlobalAccountDataEventType::PushRules.to_string().into(),
|
||||
)
|
||||
.unwrap()
|
||||
.expect("Username is invalid");
|
||||
|
||||
let mut account_data =
|
||||
serde_json::from_str::<PushRulesEvent>(raw_rules_list.get()).unwrap();
|
||||
|
||||
let user_default_rules = ruma::push::Ruleset::server_default(&user);
|
||||
account_data
|
||||
.content
|
||||
.global
|
||||
.update_with_server_default(user_default_rules);
|
||||
|
||||
services().account_data.update(
|
||||
None,
|
||||
&user,
|
||||
GlobalAccountDataEventType::PushRules.to_string().into(),
|
||||
&serde_json::to_value(account_data).expect("to json value always works"),
|
||||
)?;
|
||||
}
|
||||
|
||||
// Move old media files to new names
|
||||
for (key, _) in db.mediaid_file.iter() {
|
||||
// we know that this method is deprecated, but we need to use it to migrate the old files
|
||||
|
@ -950,6 +1001,9 @@ impl KeyValueDatabase {
|
|||
services().sending.start_handler();
|
||||
|
||||
Self::start_cleanup_task().await;
|
||||
if services().globals.allow_check_for_updates() {
|
||||
Self::start_check_for_updates_task();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -966,9 +1020,61 @@ impl KeyValueDatabase {
|
|||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn start_cleanup_task() {
|
||||
use tokio::time::interval;
|
||||
pub fn start_check_for_updates_task() {
|
||||
tokio::spawn(async move {
|
||||
let timer_interval = Duration::from_secs(60 * 60);
|
||||
let mut i = interval(timer_interval);
|
||||
loop {
|
||||
i.tick().await;
|
||||
let _ = Self::try_handle_updates().await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn try_handle_updates() -> Result<()> {
|
||||
let response = services()
|
||||
.globals
|
||||
.default_client()
|
||||
.get("https://conduit.rs/check-for-updates/stable")
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CheckForUpdatesResponseEntry {
|
||||
id: u64,
|
||||
date: String,
|
||||
message: String,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
struct CheckForUpdatesResponse {
|
||||
updates: Vec<CheckForUpdatesResponseEntry>,
|
||||
}
|
||||
|
||||
let response = serde_json::from_str::<CheckForUpdatesResponse>(&response.text().await?)
|
||||
.map_err(|_| Error::BadServerResponse("Bad version check response"))?;
|
||||
|
||||
let mut last_update_id = services().globals.last_check_for_updates_id()?;
|
||||
for update in response.updates {
|
||||
last_update_id = last_update_id.max(update.id);
|
||||
if update.id > services().globals.last_check_for_updates_id()? {
|
||||
println!("{}", update.message);
|
||||
services()
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||
"@room: The following is a message from the Conduit developers. It was sent on '{}':\n\n{}",
|
||||
update.date, update.message
|
||||
)))
|
||||
}
|
||||
}
|
||||
services()
|
||||
.globals
|
||||
.update_check_for_updates_id(last_update_id)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn start_cleanup_task() {
|
||||
#[cfg(unix)]
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
|
||||
|
|
13
src/lib.rs
13
src/lib.rs
|
@ -1,18 +1,13 @@
|
|||
#![warn(
|
||||
rust_2018_idioms,
|
||||
unused_qualifications,
|
||||
clippy::cloned_instead_of_copied,
|
||||
clippy::str_to_string
|
||||
)]
|
||||
#![allow(clippy::suspicious_else_formatting)]
|
||||
#![deny(clippy::dbg_macro)]
|
||||
|
||||
pub mod api;
|
||||
pub mod clap;
|
||||
mod config;
|
||||
mod database;
|
||||
mod service;
|
||||
mod utils;
|
||||
|
||||
// Not async due to services() being used in many closures, and async closures are not stable as of writing
|
||||
// This is the case for every other occurence of sync Mutex/RwLock, except for database related ones, where
|
||||
// the current maintainer (Timo) has asked to not modify those
|
||||
use std::sync::RwLock;
|
||||
|
||||
pub use api::ruma_wrapper::{Ruma, RumaResponse};
|
||||
|
|
64
src/main.rs
64
src/main.rs
|
@ -1,17 +1,7 @@
|
|||
#![warn(
|
||||
rust_2018_idioms,
|
||||
unused_qualifications,
|
||||
clippy::cloned_instead_of_copied,
|
||||
clippy::str_to_string
|
||||
)]
|
||||
#![allow(clippy::suspicious_else_formatting)]
|
||||
#![deny(clippy::dbg_macro)]
|
||||
|
||||
use std::{future::Future, io, net::SocketAddr, sync::atomic, time::Duration};
|
||||
|
||||
use axum::{
|
||||
extract::{DefaultBodyLimit, FromRequest, MatchedPath},
|
||||
handler::Handler,
|
||||
extract::{DefaultBodyLimit, FromRequestParts, MatchedPath},
|
||||
response::IntoResponse,
|
||||
routing::{get, on, MethodFilter},
|
||||
Router,
|
||||
|
@ -40,7 +30,7 @@ use tower_http::{
|
|||
trace::TraceLayer,
|
||||
ServiceBuilderExt as _,
|
||||
};
|
||||
use tracing::{error, info, warn};
|
||||
use tracing::{debug, error, info, warn};
|
||||
use tracing_subscriber::{prelude::*, EnvFilter};
|
||||
|
||||
pub use conduit::*; // Re-export everything from the library crate
|
||||
|
@ -54,7 +44,9 @@ static GLOBAL: Jemalloc = Jemalloc;
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Initialize DB
|
||||
clap::parse();
|
||||
|
||||
// Initialize config
|
||||
let raw_config =
|
||||
Figment::new()
|
||||
.merge(
|
||||
|
@ -123,6 +115,16 @@ async fn main() {
|
|||
tracing::subscriber::set_global_default(subscriber).unwrap();
|
||||
}
|
||||
|
||||
// This is needed for opening lots of file descriptors, which tends to
|
||||
// happen more often when using RocksDB and making lots of federation
|
||||
// connections at startup. The soft limit is usually 1024, and the hard
|
||||
// limit is usually 512000; I've personally seen it hit >2000.
|
||||
//
|
||||
// * https://www.freedesktop.org/software/systemd/man/systemd.exec.html#id-1.12.2.1.17.6
|
||||
// * https://github.com/systemd/systemd/commit/0abf94923b4a95a7d89bc526efc84e7ca2b71741
|
||||
#[cfg(unix)]
|
||||
maximize_fd_limit().expect("should be able to increase the soft limit to the hard limit");
|
||||
|
||||
info!("Loading database");
|
||||
if let Err(error) = KeyValueDatabase::load_or_create(config).await {
|
||||
error!(?error, "The database couldn't be loaded or created");
|
||||
|
@ -159,7 +161,6 @@ async fn run_server() -> io::Result<()> {
|
|||
tracing::info_span!("http_request", %path)
|
||||
}),
|
||||
)
|
||||
.compression()
|
||||
.layer(axum::middleware::from_fn(unrecognized_method))
|
||||
.layer(
|
||||
CorsLayer::new()
|
||||
|
@ -227,7 +228,7 @@ async fn spawn_task<B: Send + 'static>(
|
|||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
|
||||
async fn unrecognized_method<B>(
|
||||
async fn unrecognized_method<B: Send>(
|
||||
req: axum::http::Request<B>,
|
||||
next: axum::middleware::Next<B>,
|
||||
) -> std::result::Result<axum::response::Response, StatusCode> {
|
||||
|
@ -358,6 +359,7 @@ fn routes() -> Router {
|
|||
.put(client_server::send_state_event_for_empty_key_route),
|
||||
)
|
||||
.ruma_route(client_server::sync_events_route)
|
||||
.ruma_route(client_server::sync_events_v4_route)
|
||||
.ruma_route(client_server::get_context_route)
|
||||
.ruma_route(client_server::get_message_events_route)
|
||||
.ruma_route(client_server::search_events_route)
|
||||
|
@ -383,6 +385,11 @@ fn routes() -> Router {
|
|||
.ruma_route(client_server::set_pushers_route)
|
||||
// .ruma_route(client_server::third_party_route)
|
||||
.ruma_route(client_server::upgrade_room_route)
|
||||
.ruma_route(client_server::get_threads_route)
|
||||
.ruma_route(client_server::get_relating_events_with_rel_type_and_event_type_route)
|
||||
.ruma_route(client_server::get_relating_events_with_rel_type_route)
|
||||
.ruma_route(client_server::get_relating_events_route)
|
||||
.ruma_route(client_server::get_hierarchy_route)
|
||||
.ruma_route(server_server::get_server_version_route)
|
||||
.route(
|
||||
"/_matrix/key/v2/server",
|
||||
|
@ -418,7 +425,8 @@ fn routes() -> Router {
|
|||
"/_matrix/client/v3/rooms/:room_id/initialSync",
|
||||
get(initial_sync),
|
||||
)
|
||||
.fallback(not_found.into_service())
|
||||
.route("/", get(it_works))
|
||||
.fallback(not_found)
|
||||
}
|
||||
|
||||
async fn shutdown_signal(handle: ServerHandle) {
|
||||
|
@ -467,6 +475,10 @@ async fn initial_sync(_uri: Uri) -> impl IntoResponse {
|
|||
)
|
||||
}
|
||||
|
||||
async fn it_works() -> &'static str {
|
||||
"Hello from Conduit!"
|
||||
}
|
||||
|
||||
trait RouterExt {
|
||||
fn ruma_route<H, T>(self, handler: H) -> Self
|
||||
where
|
||||
|
@ -502,7 +514,7 @@ macro_rules! impl_ruma_handler {
|
|||
Fut: Future<Output = Result<Req::OutgoingResponse, E>>
|
||||
+ Send,
|
||||
E: IntoResponse,
|
||||
$( $ty: FromRequest<axum::body::Body> + Send + 'static, )*
|
||||
$( $ty: FromRequestParts<()> + Send + 'static, )*
|
||||
{
|
||||
fn add_to_router(self, mut router: Router) -> Router {
|
||||
let meta = Req::METADATA;
|
||||
|
@ -545,3 +557,21 @@ fn method_to_filter(method: Method) -> MethodFilter {
|
|||
m => panic!("Unsupported HTTP method: {m:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tracing::instrument(err)]
|
||||
fn maximize_fd_limit() -> Result<(), nix::errno::Errno> {
|
||||
use nix::sys::resource::{getrlimit, setrlimit, Resource};
|
||||
|
||||
let res = Resource::RLIMIT_NOFILE;
|
||||
|
||||
let (soft_limit, hard_limit) = getrlimit(res)?;
|
||||
|
||||
debug!("Current nofile soft limit: {soft_limit}");
|
||||
|
||||
setrlimit(res, hard_limit, hard_limit)?;
|
||||
|
||||
debug!("Increased nofile soft limit to {hard_limit}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,8 +1,10 @@
|
|||
use ruma::api::appservice::Registration;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
pub trait Data: Send + Sync {
|
||||
/// Registers an appservice and returns the ID to the caller
|
||||
fn register_appservice(&self, yaml: serde_yaml::Value) -> Result<String>;
|
||||
fn register_appservice(&self, yaml: Registration) -> Result<String>;
|
||||
|
||||
/// Remove an appservice registration
|
||||
///
|
||||
|
@ -11,9 +13,9 @@ pub trait Data: Send + Sync {
|
|||
/// * `service_name` - the name you send to register the service previously
|
||||
fn unregister_appservice(&self, service_name: &str) -> Result<()>;
|
||||
|
||||
fn get_registration(&self, id: &str) -> Result<Option<serde_yaml::Value>>;
|
||||
fn get_registration(&self, id: &str) -> Result<Option<Registration>>;
|
||||
|
||||
fn iter_ids<'a>(&'a self) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>>;
|
||||
|
||||
fn all(&self) -> Result<Vec<(String, serde_yaml::Value)>>;
|
||||
fn all(&self) -> Result<Vec<(String, Registration)>>;
|
||||
}
|
||||
|
|
|
@ -1,37 +1,184 @@
|
|||
mod data;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub use data::Data;
|
||||
|
||||
use crate::Result;
|
||||
use futures_util::Future;
|
||||
use regex::RegexSet;
|
||||
use ruma::api::appservice::{Namespace, Registration};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::{services, Result};
|
||||
|
||||
/// Compiled regular expressions for a namespace.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NamespaceRegex {
|
||||
pub exclusive: Option<RegexSet>,
|
||||
pub non_exclusive: Option<RegexSet>,
|
||||
}
|
||||
|
||||
impl NamespaceRegex {
|
||||
/// Checks if this namespace has rights to a namespace
|
||||
pub fn is_match(&self, heystack: &str) -> bool {
|
||||
if self.is_exclusive_match(heystack) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some(non_exclusive) = &self.non_exclusive {
|
||||
if non_exclusive.is_match(heystack) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Checks if this namespace has exlusive rights to a namespace
|
||||
pub fn is_exclusive_match(&self, heystack: &str) -> bool {
|
||||
if let Some(exclusive) = &self.exclusive {
|
||||
if exclusive.is_match(heystack) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<Namespace>> for NamespaceRegex {
|
||||
fn try_from(value: Vec<Namespace>) -> Result<Self, regex::Error> {
|
||||
let mut exclusive = vec![];
|
||||
let mut non_exclusive = vec![];
|
||||
|
||||
for namespace in value {
|
||||
if namespace.exclusive {
|
||||
exclusive.push(namespace.regex);
|
||||
} else {
|
||||
non_exclusive.push(namespace.regex);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(NamespaceRegex {
|
||||
exclusive: if exclusive.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(RegexSet::new(exclusive)?)
|
||||
},
|
||||
non_exclusive: if non_exclusive.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(RegexSet::new(non_exclusive)?)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type Error = regex::Error;
|
||||
}
|
||||
|
||||
/// Appservice registration combined with its compiled regular expressions.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RegistrationInfo {
|
||||
pub registration: Registration,
|
||||
pub users: NamespaceRegex,
|
||||
pub aliases: NamespaceRegex,
|
||||
pub rooms: NamespaceRegex,
|
||||
}
|
||||
|
||||
impl TryFrom<Registration> for RegistrationInfo {
|
||||
fn try_from(value: Registration) -> Result<RegistrationInfo, regex::Error> {
|
||||
Ok(RegistrationInfo {
|
||||
users: value.namespaces.users.clone().try_into()?,
|
||||
aliases: value.namespaces.aliases.clone().try_into()?,
|
||||
rooms: value.namespaces.rooms.clone().try_into()?,
|
||||
registration: value,
|
||||
})
|
||||
}
|
||||
|
||||
type Error = regex::Error;
|
||||
}
|
||||
|
||||
pub struct Service {
|
||||
pub db: &'static dyn Data,
|
||||
registration_info: RwLock<BTreeMap<String, RegistrationInfo>>,
|
||||
}
|
||||
|
||||
impl Service {
|
||||
/// Registers an appservice and returns the ID to the caller
|
||||
pub fn register_appservice(&self, yaml: serde_yaml::Value) -> Result<String> {
|
||||
pub fn build(db: &'static dyn Data) -> Result<Self> {
|
||||
let mut registration_info = BTreeMap::new();
|
||||
// Inserting registrations into cache
|
||||
for appservice in db.all()? {
|
||||
registration_info.insert(
|
||||
appservice.0,
|
||||
appservice
|
||||
.1
|
||||
.try_into()
|
||||
.expect("Should be validated on registration"),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
db,
|
||||
registration_info: RwLock::new(registration_info),
|
||||
})
|
||||
}
|
||||
/// Registers an appservice and returns the ID to the caller.
|
||||
pub async fn register_appservice(&self, yaml: Registration) -> Result<String> {
|
||||
services()
|
||||
.appservice
|
||||
.registration_info
|
||||
.write()
|
||||
.await
|
||||
.insert(yaml.id.clone(), yaml.clone().try_into()?);
|
||||
|
||||
self.db.register_appservice(yaml)
|
||||
}
|
||||
|
||||
/// Remove an appservice registration
|
||||
/// Removes an appservice registration.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `service_name` - the name you send to register the service previously
|
||||
pub fn unregister_appservice(&self, service_name: &str) -> Result<()> {
|
||||
pub async fn unregister_appservice(&self, service_name: &str) -> Result<()> {
|
||||
services()
|
||||
.appservice
|
||||
.registration_info
|
||||
.write()
|
||||
.await
|
||||
.remove(service_name)
|
||||
.ok_or_else(|| crate::Error::AdminCommand("Appservice not found"))?;
|
||||
|
||||
self.db.unregister_appservice(service_name)
|
||||
}
|
||||
|
||||
pub fn get_registration(&self, id: &str) -> Result<Option<serde_yaml::Value>> {
|
||||
self.db.get_registration(id)
|
||||
pub async fn get_registration(&self, id: &str) -> Option<Registration> {
|
||||
self.registration_info
|
||||
.read()
|
||||
.await
|
||||
.get(id)
|
||||
.cloned()
|
||||
.map(|info| info.registration)
|
||||
}
|
||||
|
||||
pub fn iter_ids(&self) -> Result<impl Iterator<Item = Result<String>> + '_> {
|
||||
self.db.iter_ids()
|
||||
pub async fn iter_ids(&self) -> Vec<String> {
|
||||
self.registration_info
|
||||
.read()
|
||||
.await
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn all(&self) -> Result<Vec<(String, serde_yaml::Value)>> {
|
||||
self.db.all()
|
||||
pub async fn find_from_token(&self, token: &str) -> Option<RegistrationInfo> {
|
||||
self.read()
|
||||
.await
|
||||
.values()
|
||||
.find(|info| info.registration.as_token == token)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn read(
|
||||
&self,
|
||||
) -> impl Future<Output = tokio::sync::RwLockReadGuard<'_, BTreeMap<String, RegistrationInfo>>>
|
||||
{
|
||||
self.registration_info.read()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,9 +13,12 @@ use crate::Result;
|
|||
pub trait Data: Send + Sync {
|
||||
fn next_count(&self) -> Result<u64>;
|
||||
fn current_count(&self) -> Result<u64>;
|
||||
fn last_check_for_updates_id(&self) -> Result<u64>;
|
||||
fn update_check_for_updates_id(&self, id: u64) -> Result<()>;
|
||||
async fn watch(&self, user_id: &UserId, device_id: &DeviceId) -> Result<()>;
|
||||
fn cleanup(&self) -> Result<()>;
|
||||
fn memory_usage(&self) -> Result<String>;
|
||||
fn memory_usage(&self) -> String;
|
||||
fn clear_caches(&self, amount: u32);
|
||||
fn load_keypair(&self) -> Result<Ed25519KeyPair>;
|
||||
fn remove_keypair(&self) -> Result<()>;
|
||||
fn add_signing_key(
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
mod data;
|
||||
pub use data::Data;
|
||||
use ruma::{
|
||||
OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedServerName, OwnedServerSigningKeyId, OwnedUserId,
|
||||
serde::Base64, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedServerName,
|
||||
OwnedServerSigningKeyId, OwnedUserId,
|
||||
};
|
||||
use sha2::Digest;
|
||||
|
||||
use crate::api::server_server::FedDest;
|
||||
|
||||
use crate::{services, Config, Error, Result};
|
||||
use futures_util::FutureExt;
|
||||
use hyper::{
|
||||
client::connect::dns::{GaiResolver, Name},
|
||||
service::Service as HyperService,
|
||||
};
|
||||
use reqwest::dns::{Addrs, Resolve, Resolving};
|
||||
use ruma::{
|
||||
api::{
|
||||
client::sync::sync_events,
|
||||
|
@ -17,20 +24,24 @@ use ruma::{
|
|||
};
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
error::Error as StdError,
|
||||
fs,
|
||||
future::Future,
|
||||
future::{self, Future},
|
||||
iter,
|
||||
net::{IpAddr, SocketAddr},
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
atomic::{self, AtomicBool},
|
||||
Arc, Mutex, RwLock,
|
||||
Arc, Mutex, RwLock as StdRwLock,
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tokio::sync::{broadcast, watch::Receiver, Mutex as TokioMutex, Semaphore};
|
||||
use tokio::sync::{broadcast, watch::Receiver, Mutex, RwLock, Semaphore};
|
||||
use tracing::{error, info};
|
||||
use trust_dns_resolver::TokioAsyncResolver;
|
||||
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
|
||||
type WellKnownMap = HashMap<OwnedServerName, (FedDest, String)>;
|
||||
type TlsNameMap = HashMap<String, (Vec<IpAddr>, u16)>;
|
||||
type RateLimitState = (Instant, u32); // Time if last failed try, number of failed tries
|
||||
|
@ -43,7 +54,7 @@ pub struct Service {
|
|||
pub db: &'static dyn Data,
|
||||
|
||||
pub actual_destination_cache: Arc<RwLock<WellKnownMap>>, // actual_destination, host
|
||||
pub tls_name_override: Arc<RwLock<TlsNameMap>>,
|
||||
pub tls_name_override: Arc<StdRwLock<TlsNameMap>>,
|
||||
pub config: Config,
|
||||
keypair: Arc<ruma::signatures::Ed25519KeyPair>,
|
||||
dns_resolver: TokioAsyncResolver,
|
||||
|
@ -54,11 +65,12 @@ pub struct Service {
|
|||
pub unstable_room_versions: Vec<RoomVersionId>,
|
||||
pub bad_event_ratelimiter: Arc<RwLock<HashMap<OwnedEventId, RateLimitState>>>,
|
||||
pub bad_signature_ratelimiter: Arc<RwLock<HashMap<Vec<String>, RateLimitState>>>,
|
||||
pub bad_query_ratelimiter: Arc<RwLock<HashMap<OwnedServerName, RateLimitState>>>,
|
||||
pub servername_ratelimiter: Arc<RwLock<HashMap<OwnedServerName, Arc<Semaphore>>>>,
|
||||
pub sync_receivers: RwLock<HashMap<(OwnedUserId, OwnedDeviceId), SyncHandle>>,
|
||||
pub roomid_mutex_insert: RwLock<HashMap<OwnedRoomId, Arc<Mutex<()>>>>,
|
||||
pub roomid_mutex_state: RwLock<HashMap<OwnedRoomId, Arc<TokioMutex<()>>>>,
|
||||
pub roomid_mutex_federation: RwLock<HashMap<OwnedRoomId, Arc<TokioMutex<()>>>>, // this lock will be held longer
|
||||
pub roomid_mutex_state: RwLock<HashMap<OwnedRoomId, Arc<Mutex<()>>>>,
|
||||
pub roomid_mutex_federation: RwLock<HashMap<OwnedRoomId, Arc<Mutex<()>>>>, // this lock will be held longer
|
||||
pub roomid_federationhandletime: RwLock<HashMap<OwnedRoomId, (OwnedEventId, Instant)>>,
|
||||
pub stateres_mutex: Arc<Mutex<()>>,
|
||||
pub rotate: RotationHandler,
|
||||
|
@ -96,6 +108,45 @@ impl Default for RotationHandler {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Resolver {
|
||||
inner: GaiResolver,
|
||||
overrides: Arc<StdRwLock<TlsNameMap>>,
|
||||
}
|
||||
|
||||
impl Resolver {
|
||||
pub fn new(overrides: Arc<StdRwLock<TlsNameMap>>) -> Self {
|
||||
Resolver {
|
||||
inner: GaiResolver::new(),
|
||||
overrides,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve for Resolver {
|
||||
fn resolve(&self, name: Name) -> Resolving {
|
||||
self.overrides
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(name.as_str())
|
||||
.and_then(|(override_name, port)| {
|
||||
override_name.first().map(|first_name| {
|
||||
let x: Box<dyn Iterator<Item = SocketAddr> + Send> =
|
||||
Box::new(iter::once(SocketAddr::new(*first_name, *port)));
|
||||
let x: Resolving = Box::pin(future::ready(Ok(x)));
|
||||
x
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
let this = &mut self.inner.clone();
|
||||
Box::pin(HyperService::<Name>::call(this, name).map(|result| {
|
||||
result
|
||||
.map(|addrs| -> Addrs { Box::new(addrs) })
|
||||
.map_err(|err| -> Box<dyn StdError + Send + Sync> { Box::new(err) })
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Service {
|
||||
pub fn load(db: &'static dyn Data, config: Config) -> Result<Self> {
|
||||
let keypair = db.load_keypair();
|
||||
|
@ -109,7 +160,7 @@ impl Service {
|
|||
}
|
||||
};
|
||||
|
||||
let tls_name_override = Arc::new(RwLock::new(TlsNameMap::new()));
|
||||
let tls_name_override = Arc::new(StdRwLock::new(TlsNameMap::new()));
|
||||
|
||||
let jwt_decoding_key = config
|
||||
.jwt_secret
|
||||
|
@ -117,14 +168,8 @@ impl Service {
|
|||
.map(|secret| jsonwebtoken::DecodingKey::from_secret(secret.as_bytes()));
|
||||
|
||||
let default_client = reqwest_client_builder(&config)?.build()?;
|
||||
let name_override = Arc::clone(&tls_name_override);
|
||||
let federation_client = reqwest_client_builder(&config)?
|
||||
.resolve_fn(move |domain| {
|
||||
let read_guard = name_override.read().unwrap();
|
||||
let (override_name, port) = read_guard.get(&domain)?;
|
||||
let first_name = override_name.get(0)?;
|
||||
Some(SocketAddr::new(*first_name, *port))
|
||||
})
|
||||
.dns_resolver(Arc::new(Resolver::new(tls_name_override.clone())))
|
||||
.build()?;
|
||||
|
||||
// Supported and stable room versions
|
||||
|
@ -158,6 +203,7 @@ impl Service {
|
|||
unstable_room_versions,
|
||||
bad_event_ratelimiter: Arc::new(RwLock::new(HashMap::new())),
|
||||
bad_signature_ratelimiter: Arc::new(RwLock::new(HashMap::new())),
|
||||
bad_query_ratelimiter: Arc::new(RwLock::new(HashMap::new())),
|
||||
servername_ratelimiter: Arc::new(RwLock::new(HashMap::new())),
|
||||
roomid_mutex_state: RwLock::new(HashMap::new()),
|
||||
roomid_mutex_insert: RwLock::new(HashMap::new()),
|
||||
|
@ -209,6 +255,16 @@ impl Service {
|
|||
self.db.current_count()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn last_check_for_updates_id(&self) -> Result<u64> {
|
||||
self.db.last_check_for_updates_id()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn update_check_for_updates_id(&self, id: u64) -> Result<()> {
|
||||
self.db.update_check_for_updates_id(id)
|
||||
}
|
||||
|
||||
pub async fn watch(&self, user_id: &UserId, device_id: &DeviceId) -> Result<()> {
|
||||
self.db.watch(user_id, device_id).await
|
||||
}
|
||||
|
@ -217,10 +273,6 @@ impl Service {
|
|||
self.db.cleanup()
|
||||
}
|
||||
|
||||
pub fn memory_usage(&self) -> Result<String> {
|
||||
self.db.memory_usage()
|
||||
}
|
||||
|
||||
pub fn server_name(&self) -> &ServerName {
|
||||
self.config.server_name.as_ref()
|
||||
}
|
||||
|
@ -261,6 +313,10 @@ impl Service {
|
|||
self.config.enable_lightning_bolt
|
||||
}
|
||||
|
||||
pub fn allow_check_for_updates(&self) -> bool {
|
||||
self.config.allow_check_for_updates
|
||||
}
|
||||
|
||||
pub fn trusted_servers(&self) -> &[OwnedServerName] {
|
||||
&self.config.trusted_servers
|
||||
}
|
||||
|
@ -323,7 +379,19 @@ impl Service {
|
|||
&self,
|
||||
origin: &ServerName,
|
||||
) -> Result<BTreeMap<OwnedServerSigningKeyId, VerifyKey>> {
|
||||
self.db.signing_keys_for(origin)
|
||||
let mut keys = self.db.signing_keys_for(origin)?;
|
||||
if origin == self.server_name() {
|
||||
keys.insert(
|
||||
format!("ed25519:{}", services().globals.keypair().version())
|
||||
.try_into()
|
||||
.expect("found invalid server signing keys in DB"),
|
||||
VerifyKey {
|
||||
key: Base64::new(self.keypair.public_key().to_vec()),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Ok(keys)
|
||||
}
|
||||
|
||||
pub fn database_version(&self) -> Result<u64> {
|
||||
|
@ -345,12 +413,9 @@ impl Service {
|
|||
let mut r = PathBuf::new();
|
||||
r.push(self.config.database_path.clone());
|
||||
r.push("media");
|
||||
r.push(base64::encode_config(
|
||||
// Using the hash of the key as the filename
|
||||
// This is to prevent the total length of the path from exceeding the maximum length
|
||||
sha2::Sha256::digest(key),
|
||||
base64::URL_SAFE_NO_PAD,
|
||||
));
|
||||
// Using the hash of the key as the filename
|
||||
// This is to prevent the total length of the path from exceeding the maximum length
|
||||
r.push(general_purpose::URL_SAFE_NO_PAD.encode(sha2::Sha256::digest(key));
|
||||
r
|
||||
}
|
||||
|
||||
|
@ -363,10 +428,14 @@ impl Service {
|
|||
let mut r = PathBuf::new();
|
||||
r.push(self.config.database_path.clone());
|
||||
r.push("media");
|
||||
r.push(base64::encode_config(key, base64::URL_SAFE_NO_PAD));
|
||||
r.push(general_purpose::URL_SAFE_NO_PAD.encode(key));
|
||||
r
|
||||
}
|
||||
|
||||
pub fn well_known_client(&self) -> &Option<String> {
|
||||
&self.config.well_known_client
|
||||
}
|
||||
|
||||
pub fn shutdown(&self) {
|
||||
self.shutdown.store(true, atomic::Ordering::Relaxed);
|
||||
// On shutdown
|
||||
|
|
|
@ -8,7 +8,7 @@ use image::imageops::FilterType;
|
|||
|
||||
use tokio::{
|
||||
fs::File,
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
io::{AsyncReadExt, AsyncWriteExt, BufReader},
|
||||
};
|
||||
|
||||
pub struct FileMeta {
|
||||
|
@ -70,7 +70,9 @@ impl Service {
|
|||
{
|
||||
let path = services().globals.get_media_file(&key);
|
||||
let mut file = Vec::new();
|
||||
File::open(path).await?.read_to_end(&mut file).await?;
|
||||
BufReader::new(File::open(path).await?)
|
||||
.read_to_end(&mut file)
|
||||
.await?;
|
||||
|
||||
Ok(Some(FileMeta {
|
||||
content_disposition,
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
collections::{BTreeMap, HashMap},
|
||||
sync::{Arc, Mutex as StdMutex},
|
||||
};
|
||||
|
||||
use lru_cache::LruCache;
|
||||
use tokio::sync::{broadcast, Mutex};
|
||||
|
||||
use crate::{Config, Result};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub mod account_data;
|
||||
pub mod admin;
|
||||
|
@ -55,7 +57,7 @@ impl Services {
|
|||
config: Config,
|
||||
) -> Result<Self> {
|
||||
Ok(Self {
|
||||
appservice: appservice::Service { db },
|
||||
appservice: appservice::Service::build(db)?,
|
||||
pusher: pusher::Service { db },
|
||||
rooms: rooms::Service {
|
||||
alias: rooms::alias::Service { db },
|
||||
|
@ -64,7 +66,11 @@ impl Services {
|
|||
edus: rooms::edus::Service {
|
||||
presence: rooms::edus::presence::Service { db },
|
||||
read_receipt: rooms::edus::read_receipt::Service { db },
|
||||
typing: rooms::edus::typing::Service { db },
|
||||
typing: rooms::edus::typing::Service {
|
||||
typing: RwLock::new(BTreeMap::new()),
|
||||
last_typing_update: RwLock::new(BTreeMap::new()),
|
||||
typing_update_sender: broadcast::channel(100).0,
|
||||
},
|
||||
},
|
||||
event_handler: rooms::event_handler::Service,
|
||||
lazy_loading: rooms::lazy_loading::Service {
|
||||
|
@ -79,17 +85,17 @@ impl Services {
|
|||
state: rooms::state::Service { db },
|
||||
state_accessor: rooms::state_accessor::Service {
|
||||
db,
|
||||
server_visibility_cache: Mutex::new(LruCache::new(
|
||||
server_visibility_cache: StdMutex::new(LruCache::new(
|
||||
(100.0 * config.conduit_cache_capacity_modifier) as usize,
|
||||
)),
|
||||
user_visibility_cache: Mutex::new(LruCache::new(
|
||||
user_visibility_cache: StdMutex::new(LruCache::new(
|
||||
(100.0 * config.conduit_cache_capacity_modifier) as usize,
|
||||
)),
|
||||
},
|
||||
state_cache: rooms::state_cache::Service { db },
|
||||
state_compressor: rooms::state_compressor::Service {
|
||||
db,
|
||||
stateinfo_cache: Mutex::new(LruCache::new(
|
||||
stateinfo_cache: StdMutex::new(LruCache::new(
|
||||
(100.0 * config.conduit_cache_capacity_modifier) as usize,
|
||||
)),
|
||||
},
|
||||
|
@ -97,11 +103,18 @@ impl Services {
|
|||
db,
|
||||
lasttimelinecount_cache: Mutex::new(HashMap::new()),
|
||||
},
|
||||
threads: rooms::threads::Service { db },
|
||||
spaces: rooms::spaces::Service {
|
||||
roomid_spacechunk_cache: Mutex::new(LruCache::new(200)),
|
||||
},
|
||||
user: rooms::user::Service { db },
|
||||
},
|
||||
transaction_ids: transaction_ids::Service { db },
|
||||
uiaa: uiaa::Service { db },
|
||||
users: users::Service { db },
|
||||
users: users::Service {
|
||||
db,
|
||||
connections: StdMutex::new(BTreeMap::new()),
|
||||
},
|
||||
account_data: account_data::Service { db },
|
||||
admin: admin::Service::build(),
|
||||
key_backups: key_backups::Service { db },
|
||||
|
@ -111,4 +124,97 @@ impl Services {
|
|||
globals: globals::Service::load(db, config)?,
|
||||
})
|
||||
}
|
||||
async fn memory_usage(&self) -> String {
|
||||
let lazy_load_waiting = self.rooms.lazy_loading.lazy_load_waiting.lock().await.len();
|
||||
let server_visibility_cache = self
|
||||
.rooms
|
||||
.state_accessor
|
||||
.server_visibility_cache
|
||||
.lock()
|
||||
.unwrap()
|
||||
.len();
|
||||
let user_visibility_cache = self
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_visibility_cache
|
||||
.lock()
|
||||
.unwrap()
|
||||
.len();
|
||||
let stateinfo_cache = self
|
||||
.rooms
|
||||
.state_compressor
|
||||
.stateinfo_cache
|
||||
.lock()
|
||||
.unwrap()
|
||||
.len();
|
||||
let lasttimelinecount_cache = self
|
||||
.rooms
|
||||
.timeline
|
||||
.lasttimelinecount_cache
|
||||
.lock()
|
||||
.await
|
||||
.len();
|
||||
let roomid_spacechunk_cache = self.rooms.spaces.roomid_spacechunk_cache.lock().await.len();
|
||||
|
||||
format!(
|
||||
"\
|
||||
lazy_load_waiting: {lazy_load_waiting}
|
||||
server_visibility_cache: {server_visibility_cache}
|
||||
user_visibility_cache: {user_visibility_cache}
|
||||
stateinfo_cache: {stateinfo_cache}
|
||||
lasttimelinecount_cache: {lasttimelinecount_cache}
|
||||
roomid_spacechunk_cache: {roomid_spacechunk_cache}\
|
||||
"
|
||||
)
|
||||
}
|
||||
async fn clear_caches(&self, amount: u32) {
|
||||
if amount > 0 {
|
||||
self.rooms
|
||||
.lazy_loading
|
||||
.lazy_load_waiting
|
||||
.lock()
|
||||
.await
|
||||
.clear();
|
||||
}
|
||||
if amount > 1 {
|
||||
self.rooms
|
||||
.state_accessor
|
||||
.server_visibility_cache
|
||||
.lock()
|
||||
.unwrap()
|
||||
.clear();
|
||||
}
|
||||
if amount > 2 {
|
||||
self.rooms
|
||||
.state_accessor
|
||||
.user_visibility_cache
|
||||
.lock()
|
||||
.unwrap()
|
||||
.clear();
|
||||
}
|
||||
if amount > 3 {
|
||||
self.rooms
|
||||
.state_compressor
|
||||
.stateinfo_cache
|
||||
.lock()
|
||||
.unwrap()
|
||||
.clear();
|
||||
}
|
||||
if amount > 4 {
|
||||
self.rooms
|
||||
.timeline
|
||||
.lasttimelinecount_cache
|
||||
.lock()
|
||||
.await
|
||||
.clear();
|
||||
}
|
||||
if amount > 5 {
|
||||
self.rooms
|
||||
.spaces
|
||||
.roomid_spacechunk_cache
|
||||
.lock()
|
||||
.await
|
||||
.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue