Compare commits
1 commit
next
...
Nyaaori/de
Author | SHA1 | Date | |
---|---|---|---|
|
a2cffa9da3 |
124 changed files with 4220 additions and 9175 deletions
4
.envrc
4
.envrc
|
@ -1,5 +1 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
use flake
|
||||
|
||||
PATH_add bin
|
||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -31,6 +31,7 @@ modules.xml
|
|||
|
||||
### vscode ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
@ -61,13 +62,9 @@ conduit.db
|
|||
|
||||
# Etc.
|
||||
**/*.rs.bk
|
||||
cached_target
|
||||
|
||||
# Nix artifacts
|
||||
/result*
|
||||
|
||||
# Direnv cache
|
||||
/.direnv
|
||||
|
||||
# Gitlab CI cache
|
||||
/.gitlab-ci.d
|
||||
|
|
394
.gitlab-ci.yml
394
.gitlab-ci.yml
|
@ -1,180 +1,238 @@
|
|||
stages:
|
||||
- ci
|
||||
- artifacts
|
||||
- publish
|
||||
- build
|
||||
- build docker image
|
||||
- test
|
||||
- upload artifacts
|
||||
|
||||
variables:
|
||||
# Makes some things print in color
|
||||
TERM: ansi
|
||||
# Make GitLab CI go fast:
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
FF_USE_FASTZIP: 1
|
||||
CACHE_COMPRESSION_LEVEL: fastest
|
||||
|
||||
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
|
||||
# --------------------------------------------------------------------- #
|
||||
# Create and publish docker image #
|
||||
# --------------------------------------------------------------------- #
|
||||
|
||||
# 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 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.19.2
|
||||
script:
|
||||
- direnv exec . engage
|
||||
cache:
|
||||
key: nix
|
||||
paths:
|
||||
- target
|
||||
- .gitlab-ci.d
|
||||
|
||||
static:x86_64-unknown-linux-musl:
|
||||
stage: artifacts
|
||||
image: nixos/nix:2.19.2
|
||||
script:
|
||||
# Push artifacts and build requirements to binary cache
|
||||
- ./bin/nix-build-and-cache .#static-x86_64-unknown-linux-musl
|
||||
|
||||
# Make the output less difficult to find
|
||||
- cp result/bin/conduit conduit
|
||||
artifacts:
|
||||
paths:
|
||||
- conduit
|
||||
|
||||
static:aarch64-unknown-linux-musl:
|
||||
stage: artifacts
|
||||
image: nixos/nix:2.19.2
|
||||
script:
|
||||
# Push artifacts and build requirements to binary cache
|
||||
- ./bin/nix-build-and-cache .#static-aarch64-unknown-linux-musl
|
||||
|
||||
# Make the output less difficult to find
|
||||
- cp result/bin/conduit conduit
|
||||
artifacts:
|
||||
paths:
|
||||
- conduit
|
||||
|
||||
# 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.
|
||||
oci-image:x86_64-unknown-linux-gnu:
|
||||
stage: artifacts
|
||||
image: nixos/nix:2.19.2
|
||||
script:
|
||||
# Push artifacts and build requirements to binary cache
|
||||
#
|
||||
# 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.
|
||||
- ./bin/nix-build-and-cache .#oci-image
|
||||
|
||||
# Make the output less difficult to find
|
||||
- cp result oci-image-amd64.tar.gz
|
||||
artifacts:
|
||||
paths:
|
||||
- oci-image-amd64.tar.gz
|
||||
|
||||
oci-image:aarch64-unknown-linux-musl:
|
||||
stage: artifacts
|
||||
needs:
|
||||
# Wait for the static binary job to finish before starting so we don't have
|
||||
# to build that twice for no reason
|
||||
- static:aarch64-unknown-linux-musl
|
||||
image: nixos/nix:2.19.2
|
||||
script:
|
||||
# Push artifacts and build requirements to binary cache
|
||||
- ./bin/nix-build-and-cache .#oci-image-aarch64-unknown-linux-musl
|
||||
|
||||
# Make the output less difficult to find
|
||||
- cp result oci-image-arm64v8.tar.gz
|
||||
artifacts:
|
||||
paths:
|
||||
- oci-image-arm64v8.tar.gz
|
||||
|
||||
debian:x86_64-unknown-linux-gnu:
|
||||
stage: artifacts
|
||||
# See also `rust-toolchain.toml`
|
||||
image: rust:1.75.0
|
||||
script:
|
||||
- apt-get update && apt-get install -y --no-install-recommends libclang-dev
|
||||
- cargo install cargo-deb
|
||||
- cargo deb
|
||||
|
||||
# Make the output less difficult to find
|
||||
- mv target/debian/*.deb conduit.deb
|
||||
artifacts:
|
||||
paths:
|
||||
- conduit.deb
|
||||
cache:
|
||||
key: debian
|
||||
paths:
|
||||
- target
|
||||
- .gitlab-ci.d
|
||||
|
||||
.push-oci-image:
|
||||
stage: publish
|
||||
image: docker:25.0.0
|
||||
.docker-shared-settings:
|
||||
stage: "build docker image"
|
||||
image: jdrouet/docker-with-buildx:20.10.21-0.9.1
|
||||
needs: []
|
||||
tags: ["docker"]
|
||||
variables:
|
||||
# Docker in Docker:
|
||||
DOCKER_HOST: tcp://docker:2375/
|
||||
DOCKER_TLS_CERTDIR: ""
|
||||
DOCKER_DRIVER: overlay2
|
||||
services:
|
||||
- docker:25.0.0-dind
|
||||
variables:
|
||||
IMAGE_SUFFIX_AMD64: amd64
|
||||
IMAGE_SUFFIX_ARM64V8: arm64v8
|
||||
- docker:dind
|
||||
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:
|
||||
- oci-image:x86_64-unknown-linux-gnu
|
||||
- oci-image:aarch64-unknown-linux-musl
|
||||
only:
|
||||
- next
|
||||
- master
|
||||
- tags
|
||||
- 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/"
|
||||
|
||||
oci-image:push-gitlab:
|
||||
extends: .push-oci-image
|
||||
docker:next:
|
||||
extends: .docker-shared-settings
|
||||
rules:
|
||||
- if: '$BUILD_SERVER_SSH_PRIVATE_KEY && $CI_COMMIT_BRANCH == "next"'
|
||||
variables:
|
||||
IMAGE_NAME: $CI_REGISTRY_IMAGE/matrix-conduit
|
||||
before_script:
|
||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||
TAG: "matrix-conduit:next"
|
||||
|
||||
oci-image:push-dockerhub:
|
||||
extends: .push-oci-image
|
||||
docker:master:
|
||||
extends: .docker-shared-settings
|
||||
rules:
|
||||
- if: '$BUILD_SERVER_SSH_PRIVATE_KEY && $CI_COMMIT_BRANCH == "master"'
|
||||
variables:
|
||||
IMAGE_NAME: matrixconduit/matrix-conduit
|
||||
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:
|
||||
- docker login -u $DOCKER_HUB_USER -p $DOCKER_HUB_PASSWORD
|
||||
- "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"'
|
||||
|
||||
# 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"
|
||||
when: never
|
||||
- if: "$CI_COMMIT_BRANCH"
|
||||
- if: "$CI_COMMIT_TAG"
|
||||
|
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"rust-analyzer.procMacro.enable": true,
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement over email at
|
||||
coc@koesters.xyz or over Matrix at @timo:conduit.rs.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
|
2038
Cargo.lock
generated
2038
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
109
Cargo.toml
109
Cargo.toml
|
@ -1,14 +1,3 @@
|
|||
# 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"
|
||||
|
@ -17,119 +6,103 @@ authors = ["timokoesters <timo@koesters.xyz>"]
|
|||
homepage = "https://conduit.rs"
|
||||
repository = "https://gitlab.com/famedly/conduit"
|
||||
readme = "README.md"
|
||||
version = "0.7.0-alpha"
|
||||
version = "0.4.0-next"
|
||||
rust-version = "1.64"
|
||||
edition = "2021"
|
||||
|
||||
# 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.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"] }
|
||||
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"] }
|
||||
|
||||
# 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 = "1a1c61ee1e8f0936e956a3b69c931ce12ee28475", 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" ] }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "2bd5c131f49b2239750c39ed63b623cd5a01c965", 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"] }
|
||||
|
||||
# Async runtime and utilities
|
||||
tokio = { version = "1.28.1", features = ["fs", "macros", "signal", "sync"] }
|
||||
tokio = { version = "1.11.0", 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.4.4", optional = true, features = ["background_ops"] }
|
||||
persy = { version = "1.0.0", optional = true, features = ["background_ops"] }
|
||||
|
||||
# Used for the http request / response body type for Ruma endpoints used with reqwest
|
||||
bytes = "1.4.0"
|
||||
http = "0.2.9"
|
||||
bytes = "1.1.0"
|
||||
http = "0.2.4"
|
||||
# Used to find data directory for default db path
|
||||
directories = "4.0.1"
|
||||
directories = "4.0.0"
|
||||
# Used for ruma wrapper
|
||||
serde_json = { version = "1.0.96", features = ["raw_value"] }
|
||||
serde_json = { version = "1.0.68", features = ["raw_value"] }
|
||||
# Used for appservice registration files
|
||||
serde_yaml = "0.9.21"
|
||||
serde_yaml = "0.9.13"
|
||||
# Used for pdu definition
|
||||
serde = { version = "1.0.163", features = ["rc"] }
|
||||
serde = { version = "1.0.130", features = ["rc"] }
|
||||
# Used for secure identifiers
|
||||
rand = "0.8.5"
|
||||
rand = "0.8.4"
|
||||
# Used to hash passwords
|
||||
rust-argon2 = "1.0.0"
|
||||
# Used to send requests
|
||||
hyper = "0.14.26"
|
||||
reqwest = { version = "0.11.18", default-features = false, features = ["rustls-tls-native-roots", "socks"] }
|
||||
reqwest = { default-features = false, features = ["rustls-tls-native-roots", "socks"], git = "https://github.com/timokoesters/reqwest", rev = "57b7cf4feb921573dfafad7d34b9ac6e44ead0bd" }
|
||||
# Used for conduit::Error type
|
||||
thiserror = "1.0.40"
|
||||
thiserror = "1.0.29"
|
||||
# Used to generate thumbnails for images
|
||||
image = { version = "0.24.6", default-features = false, features = ["jpeg", "png", "gif"] }
|
||||
image = { version = "0.24.4", default-features = false, features = ["jpeg", "png", "gif"] }
|
||||
# Used to encode server public key
|
||||
base64 = "0.21.2"
|
||||
base64 = "0.13.0"
|
||||
# Used when hashing the state
|
||||
ring = "0.17.7"
|
||||
ring = "0.16.20"
|
||||
# Used when querying the SRV record of other servers
|
||||
trust-dns-resolver = "0.22.0"
|
||||
# Used to find matching events for appservices
|
||||
regex = "1.8.1"
|
||||
regex = "1.5.4"
|
||||
# jwt jsonwebtokens
|
||||
jsonwebtoken = "9.2.0"
|
||||
jsonwebtoken = "8.1.1"
|
||||
# Performance measurements
|
||||
tracing = { version = "0.1.37", features = [] }
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
tracing = { version = "0.1.27", features = [] }
|
||||
tracing-subscriber = { version = "0.3.16", 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.29.0", optional = true, features = ["bundled"] }
|
||||
rusqlite = { version = "0.28.0", optional = true, features = ["bundled"] }
|
||||
parking_lot = { version = "0.12.1", optional = true }
|
||||
# crossbeam = { version = "0.8.2", optional = true }
|
||||
num_cpus = "1.15.0"
|
||||
crossbeam = { version = "0.8.1", optional = true }
|
||||
num_cpus = "1.13.0"
|
||||
threadpool = "1.8.1"
|
||||
# heed = { git = "https://github.com/timokoesters/heed.git", rev = "f6f825da7fb2c758867e05ad973ef800a6fe1d5d", optional = true }
|
||||
# Used for ruma wrapper
|
||||
serde_html_form = "0.2.0"
|
||||
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 }
|
||||
|
||||
rocksdb = { version = "0.21.0", default-features = true, features = ["multi-threaded-cf", "zstd"], optional = true }
|
||||
|
||||
thread_local = "1.1.7"
|
||||
thread_local = "1.1.3"
|
||||
# used for TURN server authentication
|
||||
hmac = "0.12.1"
|
||||
sha-1 = "0.10.1"
|
||||
sha-1 = "0.10.0"
|
||||
# used for conduit's CLI and admin room command parsing
|
||||
clap = { version = "4.3.0", default-features = false, features = ["std", "derive", "help", "usage", "error-context"] }
|
||||
futures-util = { version = "0.3.28", default-features = false }
|
||||
clap = { version = "4.0.11", default-features = false, features = ["std", "derive", "help", "usage", "error-context"] }
|
||||
futures-util = { version = "0.3.17", default-features = false }
|
||||
# Used for reading the configuration from conduit.toml & environment variables
|
||||
figment = { version = "0.10.8", features = ["env", "toml"] }
|
||||
figment = { version = "0.10.6", 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.68"
|
||||
|
||||
sd-notify = { version = "0.4.1", optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = { version = "0.26.2", features = ["resource"] }
|
||||
async-trait = "0.1.57"
|
||||
|
||||
[features]
|
||||
default = ["conduit_bin", "backend_sqlite", "backend_rocksdb", "systemd"]
|
||||
default = ["conduit_bin", "backend_sqlite", "backend_rocksdb", "jemalloc"]
|
||||
#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"]
|
||||
conduit_bin = ["axum"]
|
||||
systemd = ["sd-notify"]
|
||||
|
||||
[[bin]]
|
||||
name = "conduit"
|
||||
|
@ -152,7 +125,7 @@ instead of a server that has high scalability."""
|
|||
section = "net"
|
||||
priority = "optional"
|
||||
assets = [
|
||||
["debian/README.md", "usr/share/doc/matrix-conduit/README.Debian", "644"],
|
||||
["debian/README.Debian", "usr/share/doc/matrix-conduit/", "644"],
|
||||
["README.md", "usr/share/doc/matrix-conduit/", "644"],
|
||||
["target/release/conduit", "usr/sbin/matrix-conduit", "755"],
|
||||
]
|
||||
|
|
23
Cross.toml
Normal file
23
Cross.toml
Normal file
|
@ -0,0 +1,23 @@
|
|||
[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"
|
96
DEPLOY.md
96
DEPLOY.md
|
@ -2,59 +2,49 @@
|
|||
|
||||
> ## Getting help
|
||||
>
|
||||
> If you run into any problems while setting up Conduit, write an email to `conduit@koesters.xyz`, ask us
|
||||
> If you run into any problems while setting up Conduit, write an email to `timo@koesters.xyz`, ask us
|
||||
> in `#conduit:fachschaften.org` or [open an issue on GitLab](https://gitlab.com/famedly/conduit/-/issues/new).
|
||||
|
||||
## 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 appropriate url:
|
||||
You may simply download the binary that fits your machine. Run `uname -m` to see what you need. Now copy the right url:
|
||||
|
||||
**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] |
|
||||
| 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] |
|
||||
|
||||
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 (e.g. RHEL8), you might need to compile Conduit yourself.
|
||||
If you use a system with an older glibc version, 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
|
||||
|
||||
**Latest versions:**
|
||||
|
||||
| Target | Type | Download |
|
||||
|-|-|-|
|
||||
| `x86_64-unknown-linux-gnu` | Dynamically linked Debian package | [link](https://gitlab.com/api/v4/projects/famedly%2Fconduit/jobs/artifacts/next/raw/conduit.deb?job=debian:x86_64-unknown-linux-gnu) |
|
||||
| `x86_64-unknown-linux-musl` | Statically linked binary | [link](https://gitlab.com/api/v4/projects/famedly%2Fconduit/jobs/artifacts/next/raw/conduit?job=static:x86_64-unknown-linux-musl) |
|
||||
| `aarch64-unknown-linux-musl` | Statically linked binary | [link](https://gitlab.com/api/v4/projects/famedly%2Fconduit/jobs/artifacts/next/raw/conduit?job=static:aarch64-unknown-linux-musl) |
|
||||
| `x86_64-unknown-linux-musl` | OCI image | [link](https://gitlab.com/api/v4/projects/famedly%2Fconduit/jobs/artifacts/next/raw/oci-image-amd64.tar.gz?job=oci-image:x86_64-unknown-linux-musl) |
|
||||
| `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=oci-image:aarch64-unknown-linux-musl) |
|
||||
[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
|
||||
|
||||
```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. First, install any dependencies:
|
||||
Alternatively, you may compile the binary yourself
|
||||
|
||||
```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
|
||||
```
|
||||
|
@ -84,10 +74,10 @@ cross build --release --no-default-features --features conduit_bin,backend_rocks
|
|||
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 or RHEL, you can use this command to create a Conduit user:
|
||||
In Debian you can use this command to create a Conduit user:
|
||||
|
||||
```bash
|
||||
sudo adduser --system conduit --group --disabled-login --no-create-home
|
||||
sudo adduser --system conduit --no-create-home
|
||||
```
|
||||
|
||||
## Forwarding ports in the firewall or the router
|
||||
|
@ -96,19 +86,6 @@ 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
|
||||
|
@ -123,7 +100,7 @@ After=network.target
|
|||
[Service]
|
||||
Environment="CONDUIT_CONFIG=/etc/matrix-conduit/conduit.toml"
|
||||
User=conduit
|
||||
Group=conduit
|
||||
Group=nogroup
|
||||
Restart=always
|
||||
ExecStart=/usr/local/bin/matrix-conduit
|
||||
|
||||
|
@ -178,9 +155,7 @@ max_request_size = 20_000_000 # in bytes
|
|||
allow_registration = true
|
||||
|
||||
allow_federation = true
|
||||
allow_check_for_updates = true
|
||||
|
||||
# Server to get public keys from. You probably shouldn't change this
|
||||
trusted_servers = ["matrix.org"]
|
||||
|
||||
#max_concurrent_requests = 100 # How many requests Conduit sends to other servers at the same time
|
||||
|
@ -193,7 +168,7 @@ address = "127.0.0.1" # This makes sure Conduit can only be reached using the re
|
|||
## 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 or RHEL:
|
||||
Debian:
|
||||
|
||||
```bash
|
||||
sudo chown -R root:root /etc/matrix-conduit
|
||||
|
@ -204,7 +179,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:conduit /var/lib/matrix-conduit/
|
||||
sudo chown -R conduit:nogroup /var/lib/matrix-conduit/
|
||||
sudo chmod 700 /var/lib/matrix-conduit/
|
||||
```
|
||||
|
||||
|
@ -217,11 +192,6 @@ 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>
|
||||
|
@ -229,7 +199,7 @@ Listen 8448
|
|||
ServerName your.server.name # EDIT THIS
|
||||
|
||||
AllowEncodedSlashes NoDecode
|
||||
ProxyPass /_matrix/ http://127.0.0.1:6167/_matrix/ timeout=300 nocanon
|
||||
ProxyPass /_matrix/ http://127.0.0.1:6167/_matrix/ nocanon
|
||||
ProxyPassReverse /_matrix/ http://127.0.0.1:6167/_matrix/
|
||||
|
||||
</VirtualHost>
|
||||
|
@ -238,11 +208,7 @@ 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
|
||||
|
@ -275,14 +241,12 @@ 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;
|
||||
proxy_pass http://127.0.0.1:6167$request_uri;
|
||||
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
|
||||
|
@ -302,19 +266,11 @@ $ 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](https://certbot.eff.org/instructions) `certbot` and run this:
|
||||
The easiest way to get an SSL certificate, if you don't have one already, is to install `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!
|
||||
|
||||
|
@ -338,8 +294,6 @@ 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
|
||||
```
|
||||
|
||||
|
|
130
Dockerfile
Normal file
130
Dockerfile
Normal file
|
@ -0,0 +1,130 @@
|
|||
# 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" ]
|
29
README.md
29
README.md
|
@ -1,11 +1,6 @@
|
|||
# Conduit
|
||||
### A Matrix homeserver written in Rust
|
||||
|
||||
#### 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)
|
||||
to communicate with users outside of Matrix, like a community on Discord.
|
||||
### A Matrix homeserver written in Rust
|
||||
|
||||
#### What is the goal?
|
||||
|
||||
|
@ -16,9 +11,11 @@ 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. The registration token is "for_testing_only". Don't share personal information.
|
||||
example) and registering on the `conduit.rs` homeserver.
|
||||
|
||||
Server hosting for conduit.rs is donated by the Matrix.org Foundation.
|
||||
It is hosted on a ODROID HC 2 with 2GB RAM and a SAMSUNG Exynos 5422 CPU, which
|
||||
was used in the Samsung Galaxy S5. It joined many big rooms including Matrix
|
||||
HQ.
|
||||
|
||||
#### What is the current status?
|
||||
|
||||
|
@ -28,15 +25,15 @@ from time to time.
|
|||
|
||||
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)
|
||||
- E2EE verification over federation
|
||||
- Outgoing read receipts, typing, presence over federation
|
||||
|
||||
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.md](debian/README.md)
|
||||
- Debian package: [debian/README.Debian](debian/README.Debian)
|
||||
- Nix/NixOS: [nix/README.md](nix/README.md)
|
||||
- Docker: [docker/README.md](docker/README.md)
|
||||
|
||||
|
@ -53,21 +50,13 @@ If you want to connect an Appservice to Conduit, take a look at [APPSERVICES.md]
|
|||
|
||||
#### Thanks to
|
||||
|
||||
Thanks to FUTO, Famedly, Prototype Fund (DLR and German BMBF) and all individuals for financially supporting this project.
|
||||
Thanks to Famedly, Prototype Fund (DLR and German BMBF) and all other individuals for financially supporting this project.
|
||||
|
||||
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 `timokoesters@fachschaften.org` on Matrix
|
||||
- [Open an issue on GitLab](https://gitlab.com/famedly/conduit/-/issues/new)
|
||||
|
||||
#### Donate
|
||||
|
||||
Liberapay: <https://liberapay.com/timokoesters/>\
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
#!/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"
|
|
@ -1,31 +0,0 @@
|
|||
#!/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 \
|
||||
https://nix.computer.surgery/conduit \
|
||||
"$ATTIC_TOKEN"
|
||||
|
||||
push_args=(
|
||||
# Attic and its build dependencies
|
||||
"$(nix path-info --inputs-from . attic)"
|
||||
"$(nix path-info --inputs-from . attic --derivation)"
|
||||
|
||||
# The target installable and its build dependencies
|
||||
"$(nix path-info "$INSTALLABLE" --derivation)"
|
||||
"$(nix path-info "$INSTALLABLE")"
|
||||
)
|
||||
|
||||
nix run --inputs-from . attic -- push conduit "${push_args[@]}"
|
||||
else
|
||||
echo "\$ATTIC_TOKEN is unset, skipping uploading to the binary cache"
|
||||
fi
|
|
@ -1,45 +0,0 @@
|
|||
FROM rust:1.75.0
|
||||
|
||||
WORKDIR /workdir
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libclang-dev
|
||||
|
||||
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
|
||||
|
||||
COPY conduit-example.toml conduit.toml
|
||||
COPY complement/caddy.json caddy.json
|
||||
|
||||
ENV SERVER_NAME=localhost
|
||||
ENV CONDUIT_CONFIG=/workdir/conduit.toml
|
||||
|
||||
RUN sed -i "s/port = 6167/port = 8008/g" 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
|
||||
|
||||
EXPOSE 8008 8448
|
||||
|
||||
CMD uname -a && \
|
||||
sed -i "s/#server_name = \"your.server.name\"/server_name = \"${SERVER_NAME}\"/g" conduit.toml && \
|
||||
sed -i "s/your.server.name/${SERVER_NAME}/g" caddy.json && \
|
||||
caddy start --config caddy.json > /dev/null && \
|
||||
/workdir/conduit
|
|
@ -1,11 +0,0 @@
|
|||
# Complement
|
||||
|
||||
## What's that?
|
||||
|
||||
Have a look at [its repository](https://github.com/matrix-org/complement).
|
||||
|
||||
## How do I use it with Conduit?
|
||||
|
||||
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.
|
|
@ -1,72 +0,0 @@
|
|||
{
|
||||
"logging": {
|
||||
"logs": {
|
||||
"default": {
|
||||
"level": "WARN"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"https_port": 8448,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [":8448"],
|
||||
"routes": [{
|
||||
"match": [{
|
||||
"host": ["your.server.name"]
|
||||
}],
|
||||
"handle": [{
|
||||
"handler": "subroute",
|
||||
"routes": [{
|
||||
"handle": [{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [{
|
||||
"dial": "127.0.0.1:8008"
|
||||
}]
|
||||
}]
|
||||
}]
|
||||
}],
|
||||
"terminal": true
|
||||
}],
|
||||
"tls_connection_policies": [{
|
||||
"match": {
|
||||
"sni": ["your.server.name"]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities": {
|
||||
"local": {
|
||||
"name": "Complement CA",
|
||||
"root": {
|
||||
"certificate": "/complement/ca/ca.crt",
|
||||
"private_key": "/complement/ca/ca.key"
|
||||
},
|
||||
"intermediate": {
|
||||
"certificate": "/complement/ca/ca.crt",
|
||||
"private_key": "/complement/ca/ca.key"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"automation": {
|
||||
"policies": [{
|
||||
"subjects": ["your.server.name"],
|
||||
"issuers": [{
|
||||
"module": "internal"
|
||||
}],
|
||||
"on_demand": true
|
||||
}, {
|
||||
"issuers": [{
|
||||
"module": "internal",
|
||||
"ca": "local"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -39,15 +39,10 @@ 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
|
||||
|
||||
# Servers listed here will be used to gather public keys of other servers.
|
||||
# Generally, copying this exactly should be enough. (Currently, Conduit doesn't
|
||||
# support batched key requests, so this list should only contain Synapse
|
||||
# servers.)
|
||||
trusted_servers = ["matrix.org"]
|
||||
|
||||
#max_concurrent_requests = 100 # How many requests Conduit sends to other servers at the same time
|
||||
|
|
18
debian/README.md → debian/README.Debian
vendored
18
debian/README.md → debian/README.Debian
vendored
|
@ -1,36 +1,28 @@
|
|||
Conduit for Debian
|
||||
==================
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Information about downloading, building and deploying the Debian package, see
|
||||
the "Installing Conduit" section in [DEPLOY.md](../DEPLOY.md).
|
||||
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
|
11
debian/postinst
vendored
11
debian/postinst
vendored
|
@ -19,11 +19,11 @@ case "$1" in
|
|||
_matrix-conduit
|
||||
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"
|
||||
# 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
|
||||
|
||||
if [ ! -e "$CONDUIT_CONFIG_FILE" ]; then
|
||||
# Write the debconf values in the config.
|
||||
|
@ -73,7 +73,6 @@ max_request_size = 20_000_000 # in bytes
|
|||
allow_registration = true
|
||||
|
||||
allow_federation = true
|
||||
allow_check_for_updates = true
|
||||
|
||||
trusted_servers = ["matrix.org"]
|
||||
|
||||
|
|
10
default.nix
10
default.nix
|
@ -1,10 +0,0 @@
|
|||
(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
|
|
@ -29,7 +29,6 @@ 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
|
114
docker/README.md
114
docker/README.md
|
@ -4,36 +4,7 @@
|
|||
|
||||
## Docker
|
||||
|
||||
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
|
||||
### Build & Dockerfile
|
||||
|
||||
The Dockerfile provided by Conduit has two stages, each of which creates an image.
|
||||
|
||||
|
@ -48,11 +19,9 @@ docker build --tag matrixconduit/matrix-conduit:latest .
|
|||
|
||||
which also will tag the resulting image as `matrixconduit/matrix-conduit:latest`.
|
||||
|
||||
|
||||
|
||||
### Run
|
||||
|
||||
When you have the image you can simply run it with
|
||||
After building the image you can simply run it with
|
||||
|
||||
```bash
|
||||
docker run -d -p 8448:6167 \
|
||||
|
@ -65,10 +34,19 @@ docker run -d -p 8448:6167 \
|
|||
-e CONDUIT_TRUSTED_SERVERS="[\"matrix.org\"]" \
|
||||
-e CONDUIT_MAX_CONCURRENT_REQUESTS="100" \
|
||||
-e CONDUIT_LOG="warn,rocket=off,_=off,sled=off" \
|
||||
--name conduit <link>
|
||||
--name conduit matrixconduit/matrix-conduit:latest
|
||||
```
|
||||
|
||||
or you can use [docker-compose](#docker-compose).
|
||||
or you can skip the build step and pull the image from one of the following registries:
|
||||
|
||||
| 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).
|
||||
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
|
||||
|
@ -76,7 +54,7 @@ to pass `-e CONDUIT_CONFIG=""` into your container. For an overview of possible
|
|||
|
||||
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.
|
||||
|
||||
|
@ -117,7 +95,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.yml), it is equally easy to deploy
|
||||
[`docker-compose.override.yml`](docker-compose.override.traefik.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
|
||||
|
@ -128,8 +106,7 @@ With the service `well-known` we use a single `nginx` container that will serve
|
|||
|
||||
So...step by step:
|
||||
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
4. Uncomment the `element-web` service if you want to host your own Element Web Client and create a `element_config.json`.
|
||||
|
@ -144,12 +121,12 @@ So...step by step:
|
|||
|
||||
location /.well-known/matrix/server {
|
||||
return 200 '{"m.server": "<SUBDOMAIN>.<DOMAIN>:443"}';
|
||||
types { } default_type "application/json; charset=utf-8";
|
||||
add_header Content-Type application/json;
|
||||
}
|
||||
|
||||
location /.well-known/matrix/client {
|
||||
return 200 '{"m.homeserver": {"base_url": "https://<SUBDOMAIN>.<DOMAIN>"}}';
|
||||
types { } default_type "application/json; charset=utf-8";
|
||||
add_header Content-Type application/json;
|
||||
add_header "Access-Control-Allow-Origin" *;
|
||||
}
|
||||
|
||||
|
@ -161,58 +138,3 @@ 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).
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@ 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
|
||||
|
|
|
@ -35,9 +35,8 @@ services:
|
|||
# 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: 'true'
|
||||
# CONDUIT_ALLOW_FEDERATION: 'true'
|
||||
# CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
# CONDUIT_ALLOW_ENCRYPTION: 'false'
|
||||
# CONDUIT_ALLOW_FEDERATION: 'false'
|
||||
# CONDUIT_DATABASE_PATH: /srv/conduit/.local/share/conduit
|
||||
# CONDUIT_WORKERS: 10
|
||||
# CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
|
|
|
@ -6,14 +6,9 @@ if [ -z "${CONDUIT_PORT}" ]; then
|
|||
CONDUIT_PORT=$(ss -tlpn | grep conduit | grep -m1 -o ':[0-9]*' | grep -m1 -o '[0-9]*')
|
||||
fi
|
||||
|
||||
# If CONDUIT_ADDRESS is not set try to get the address from the process list
|
||||
if [ -z "${CONDUIT_ADDRESS}" ]; then
|
||||
CONDUIT_ADDRESS=$(ss -tlpn | awk -F ' +|:' '/conduit/ { print $4 }')
|
||||
fi
|
||||
|
||||
# The actual health check.
|
||||
# We try to first get a response on HTTP and when that fails on HTTPS and when that fails, we exit with code 1.
|
||||
# TODO: Change this to a single wget call. Do we have a config value that we can check for that?
|
||||
wget --no-verbose --tries=1 --spider "http://${CONDUIT_ADDRESS}:${CONDUIT_PORT}/_matrix/client/versions" || \
|
||||
wget --no-verbose --tries=1 --spider "https://${CONDUIT_ADDRESS}:${CONDUIT_PORT}/_matrix/client/versions" || \
|
||||
wget --no-verbose --tries=1 --spider "http://localhost:${CONDUIT_PORT}/_matrix/client/versions" || \
|
||||
wget --no-verbose --tries=1 --spider "https://localhost:${CONDUIT_PORT}/_matrix/client/versions" || \
|
||||
exit 1
|
||||
|
|
64
engage.toml
64
engage.toml
|
@ -1,64 +0,0 @@
|
|||
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 = "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 = "cargo"
|
||||
group = "tests"
|
||||
script = """
|
||||
cargo test \
|
||||
--workspace \
|
||||
--all-targets \
|
||||
--color=always \
|
||||
-- \
|
||||
--color=always
|
||||
"""
|
213
flake.lock
213
flake.lock
|
@ -1,70 +1,5 @@
|
|||
{
|
||||
"nodes": {
|
||||
"attic": {
|
||||
"inputs": {
|
||||
"crane": "crane",
|
||||
"flake-compat": "flake-compat",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1705617092,
|
||||
"narHash": "sha256-n9PK4O4X4S1JkwpkMuYm1wHZYJzRqif8g3RuVIPD+rY=",
|
||||
"owner": "zhaofengli",
|
||||
"repo": "attic",
|
||||
"rev": "fbe252a5c21febbe920c025560cbd63b20e24f3b",
|
||||
"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": "7195c00c272fdd92fc74e7d5a0a2844b9fadb2fb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"crane_2": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1706473964,
|
||||
"narHash": "sha256-Fq6xleee/TsX6NbtoRuI96bBuDHMU57PrcK9z1QEKbk=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "c798790eabec3e3da48190ae3698ac227aab770c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ipetkov",
|
||||
"ref": "master",
|
||||
"repo": "crane",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
|
@ -73,11 +8,11 @@
|
|||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1705559032,
|
||||
"narHash": "sha256-Cb+Jd1+Gz4Wi+8elPnUIHnqQmE1qjDRZ+PsJaPaAffY=",
|
||||
"lastModified": 1665815894,
|
||||
"narHash": "sha256-Vboo1L4NMGLKZKVLnOPi9OHlae7uoNyfgvyIUm+SVXE=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "e132ea0eb0c799a2109a91688e499d7bf4962801",
|
||||
"rev": "2348450241a5f945f0ba07e44ecbfac2f541d7f4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -86,45 +21,13 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1673956053,
|
||||
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"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": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
"lastModified": 1659877975,
|
||||
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -133,106 +36,57 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"naersk": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1705309234,
|
||||
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
|
||||
"lastModified": 1662220400,
|
||||
"narHash": "sha256-9o2OGQqu4xyLZP9K6kNe1pTHnyPz0Wr3raGYnr9AIgY=",
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"rev": "6944160c19cb591eb85bbf9b2f2768a935623ed3",
|
||||
"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",
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1702539185,
|
||||
"narHash": "sha256-KnIRG5NMdLIpEkZTnN5zovNYc0hhXjAgv6pfd5Z4c7U=",
|
||||
"lastModified": 1665856037,
|
||||
"narHash": "sha256-/RvIWnGKdTSoIq5Xc2HwPIL0TzRslzU6Rqk4Img6UNg=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "aa9d4729cbc99dabacb50e3994dcefb3ea0f7447",
|
||||
"rev": "c95ebc5125ffffcd431df0ad8620f0926b8125b8",
|
||||
"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": 1705496572,
|
||||
"narHash": "sha256-rPIe9G5EBLXdBdn9ilGc0nq082lzQd0xGGe092R/5QE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "842d9d80cfd4560648c785f8a4e6f3b096790e19",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"attic": "attic",
|
||||
"crane": "crane_2",
|
||||
"fenix": "fenix",
|
||||
"flake-compat": "flake-compat_2",
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nix-filter": "nix-filter",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
"flake-utils": "flake-utils",
|
||||
"naersk": "naersk",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1705523001,
|
||||
"narHash": "sha256-TWq5vJ6m+9HGSDMsQAmz1TMegMi79R3TTyKjnPWsQp8=",
|
||||
"lastModified": 1665765556,
|
||||
"narHash": "sha256-w9L5j0TIB5ay4aRwzGCp8mgvGsu5dVJQvbEFutwr6xE=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "9d9b34354d2f13e33568c9c55b226dd014a146a0",
|
||||
"rev": "018b8429cf3fa9d8aed916704e41dfedeb0f4f78",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -241,21 +95,6 @@
|
|||
"repo": "rust-analyzer",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
|
258
flake.nix
258
flake.nix
|
@ -1,259 +1,75 @@
|
|||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs";
|
||||
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?ref=master";
|
||||
naersk = {
|
||||
url = "github:nix-community/naersk";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
attic.url = "github:zhaofengli/attic?ref=main";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{ self
|
||||
, nixpkgs
|
||||
, flake-utils
|
||||
, nix-filter
|
||||
|
||||
, fenix
|
||||
, crane
|
||||
, ...
|
||||
, naersk
|
||||
}: flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgsHost = nixpkgs.legacyPackages.${system};
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
|
||||
# Nix-accessible `Cargo.toml`
|
||||
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
|
||||
|
||||
# The Rust toolchain to use
|
||||
toolchain = fenix.packages.${system}.fromToolchainFile {
|
||||
file = ./rust-toolchain.toml;
|
||||
toolchain = fenix.packages.${system}.toolchainOf {
|
||||
# Use the Rust version defined in `Cargo.toml`
|
||||
channel = cargoToml.package.rust-version;
|
||||
|
||||
# See also `rust-toolchain.toml`
|
||||
sha256 = "sha256-SXRtAuO4IqNOQq+nLbrsDFbVk+3aVA8NNpSZsKlVH/8=";
|
||||
# This will need to be updated when `package.rust-version` is changed in
|
||||
# `Cargo.toml`
|
||||
sha256 = "sha256-KXx+ID0y4mg2B3LHp7IyaiMrdexF6octADnAtFIOjrY=";
|
||||
};
|
||||
|
||||
builder = pkgs:
|
||||
((crane.mkLib pkgs).overrideToolchain toolchain).buildPackage;
|
||||
|
||||
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.buildPackages.rustPlatform.bindgenHook
|
||||
];
|
||||
|
||||
env = pkgs: {
|
||||
ROCKSDB_INCLUDE_DIR = "${pkgs.rocksdb}/include";
|
||||
ROCKSDB_LIB_DIR = "${pkgs.rocksdb}/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/612f97239e2cc474c13c9dafa0df378058c5ad8d/pkgs/build-support/rust/lib/default.nix#L36-L39
|
||||
(
|
||||
# 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/612f97239e2cc474c13c9dafa0df378058c5ad8d/pkgs/build-support/rust/lib/default.nix#L64-L78
|
||||
// (
|
||||
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.buildPackages.stdenv.cc}/bin/cc";
|
||||
HOST_CXX = "${pkgs.buildPackages.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}"
|
||||
];
|
||||
};
|
||||
};
|
||||
builder = (pkgs.callPackage naersk {
|
||||
inherit (toolchain) rustc cargo;
|
||||
}).buildPackage;
|
||||
in
|
||||
{
|
||||
packages = {
|
||||
default = package pkgsHost;
|
||||
oci-image = mkOciImage pkgsHost self.packages.${system}.default;
|
||||
}
|
||||
//
|
||||
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;
|
||||
}
|
||||
packages.default = builder {
|
||||
src = ./.;
|
||||
|
||||
# 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"
|
||||
]
|
||||
)
|
||||
);
|
||||
nativeBuildInputs = (with pkgs.rustPlatform; [
|
||||
bindgenHook
|
||||
]);
|
||||
};
|
||||
|
||||
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";
|
||||
};
|
||||
devShells.default = pkgs.mkShell {
|
||||
# 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";
|
||||
|
||||
# Development tools
|
||||
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
|
||||
|
||||
toolchain
|
||||
] ++ (with pkgsHost; [
|
||||
engage
|
||||
|
||||
# Needed for Complement
|
||||
go
|
||||
olm
|
||||
|
||||
# Needed for our script for Complement
|
||||
jq
|
||||
nativeBuildInputs = (with pkgs.rustPlatform; [
|
||||
bindgenHook
|
||||
]) ++ (with toolchain; [
|
||||
cargo
|
||||
clippy
|
||||
rust-src
|
||||
rustc
|
||||
rustfmt
|
||||
]);
|
||||
};
|
||||
|
||||
checks = {
|
||||
packagesDefault = self.packages.${system}.default;
|
||||
devShellsDefault = self.devShells.${system}.default;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -107,7 +107,7 @@ in
|
|||
recommendedProxySettings = true;
|
||||
|
||||
virtualHosts = {
|
||||
"${matrix_hostname}" = {
|
||||
"${server_name}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
|
||||
|
@ -118,21 +118,20 @@ in
|
|||
ssl = true;
|
||||
}
|
||||
{
|
||||
addr = "[::]";
|
||||
port = 443;
|
||||
ssl = true;
|
||||
} {
|
||||
addr = "0.0.0.0";
|
||||
port = 8448;
|
||||
ssl = true;
|
||||
}
|
||||
{
|
||||
addr = "[::]";
|
||||
port = 8448;
|
||||
ssl = true;
|
||||
}
|
||||
];
|
||||
|
||||
extraConfig = ''
|
||||
merge_slashes off;
|
||||
'';
|
||||
|
||||
"${matrix_hostname}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
|
||||
locations."/_matrix/" = {
|
||||
proxyPass = "http://backend_conduit$request_uri";
|
||||
proxyWebsockets = true;
|
||||
|
@ -142,15 +141,6 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
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}";
|
||||
|
@ -179,7 +169,7 @@ in
|
|||
upstreams = {
|
||||
"backend_conduit" = {
|
||||
servers = {
|
||||
"[::1]:${toString config.services.matrix-conduit.settings.global.port}" = { };
|
||||
"localhost:${toString config.services.matrix-conduit.settings.global.port}" = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
# 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",
|
||||
]
|
|
@ -18,7 +18,7 @@ where
|
|||
let mut http_request = request
|
||||
.try_into_http_request::<BytesMut>(
|
||||
destination,
|
||||
SendAccessToken::IfRequired(hs_token),
|
||||
SendAccessToken::IfRequired(""),
|
||||
&[MatrixVersion::V1_0],
|
||||
)
|
||||
.unwrap()
|
||||
|
|
|
@ -30,7 +30,7 @@ const RANDOM_USER_ID_LENGTH: usize = 10;
|
|||
///
|
||||
/// Note: This will not reserve the username, so the username might become invalid when trying to register
|
||||
pub async fn get_register_available_route(
|
||||
body: Ruma<get_username_availability::v3::Request>,
|
||||
body: Ruma<get_username_availability::v3::IncomingRequest>,
|
||||
) -> Result<get_username_availability::v3::Response> {
|
||||
// Validate user id
|
||||
let user_id = UserId::parse_with_server_name(
|
||||
|
@ -73,11 +73,10 @@ pub async fn get_register_available_route(
|
|||
/// - If type is not guest and no username is given: Always fails after UIAA check
|
||||
/// - Creates a new account and populates it with default account data
|
||||
/// - If `inhibit_login` is false: Creates a device and returns device id and access_token
|
||||
pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<register::v3::Response> {
|
||||
if !services().globals.allow_registration()
|
||||
&& !body.from_appservice
|
||||
&& services().globals.config.registration_token.is_none()
|
||||
{
|
||||
pub async fn register_route(
|
||||
body: Ruma<register::v3::IncomingRequest>,
|
||||
) -> Result<register::v3::Response> {
|
||||
if !services().globals.allow_registration() && !body.from_appservice {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Registration has been disabled.",
|
||||
|
@ -124,11 +123,7 @@ pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<registe
|
|||
// UIAA
|
||||
let mut uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow {
|
||||
stages: if services().globals.config.registration_token.is_some() {
|
||||
vec![AuthType::RegistrationToken]
|
||||
} else {
|
||||
vec![AuthType::Dummy]
|
||||
},
|
||||
stages: vec![AuthType::Dummy],
|
||||
}],
|
||||
completed: Vec::new(),
|
||||
params: Default::default(),
|
||||
|
@ -136,7 +131,7 @@ pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<registe
|
|||
auth_error: None,
|
||||
};
|
||||
|
||||
if !body.from_appservice && !is_guest {
|
||||
if !body.from_appservice {
|
||||
if let Some(auth) = &body.auth {
|
||||
let (worked, uiaainfo) = services().uiaa.try_auth(
|
||||
&UserId::parse_with_server_name("", services().globals.server_name())
|
||||
|
@ -229,13 +224,12 @@ pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<registe
|
|||
)?;
|
||||
|
||||
info!("New user {} registered on this server.", user_id);
|
||||
if !body.from_appservice && !is_guest {
|
||||
services()
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
"New user {user_id} registered on this server."
|
||||
)));
|
||||
}
|
||||
services()
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
"New user {} registered on this server.",
|
||||
user_id
|
||||
)));
|
||||
|
||||
// If this is the first real user, grant them admin privileges
|
||||
// Note: the server user, @conduit:servername, is generated first
|
||||
|
@ -272,7 +266,7 @@ pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<registe
|
|||
/// - Forgets to-device events
|
||||
/// - Triggers device list updates
|
||||
pub async fn change_password_route(
|
||||
body: Ruma<change_password::v3::Request>,
|
||||
body: Ruma<change_password::v3::IncomingRequest>,
|
||||
) -> Result<change_password::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||
|
@ -326,7 +320,8 @@ pub async fn change_password_route(
|
|||
services()
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
"User {sender_user} changed their password."
|
||||
"User {} changed their password.",
|
||||
sender_user
|
||||
)));
|
||||
|
||||
Ok(change_password::v3::Response {})
|
||||
|
@ -359,7 +354,7 @@ pub async fn whoami_route(body: Ruma<whoami::v3::Request>) -> Result<whoami::v3:
|
|||
/// - Triggers device list updates
|
||||
/// - Removes ability to log in again
|
||||
pub async fn deactivate_route(
|
||||
body: Ruma<deactivate::v3::Request>,
|
||||
body: Ruma<deactivate::v3::IncomingRequest>,
|
||||
) -> Result<deactivate::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||
|
@ -403,7 +398,8 @@ pub async fn deactivate_route(
|
|||
services()
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
"User {sender_user} deactivated their account."
|
||||
"User {} deactivated their account.",
|
||||
sender_user
|
||||
)));
|
||||
|
||||
Ok(deactivate::v3::Response {
|
||||
|
@ -430,7 +426,7 @@ pub async fn third_party_route(
|
|||
///
|
||||
/// - 403 signals that The homeserver does not allow the third party identifier as a contact option.
|
||||
pub async fn request_3pid_management_token_via_email_route(
|
||||
_body: Ruma<request_3pid_management_token_via_email::v3::Request>,
|
||||
_body: Ruma<request_3pid_management_token_via_email::v3::IncomingRequest>,
|
||||
) -> Result<request_3pid_management_token_via_email::v3::Response> {
|
||||
Err(Error::BadRequest(
|
||||
ErrorKind::ThreepidDenied,
|
||||
|
@ -444,7 +440,7 @@ pub async fn request_3pid_management_token_via_email_route(
|
|||
///
|
||||
/// - 403 signals that The homeserver does not allow the third party identifier as a contact option.
|
||||
pub async fn request_3pid_management_token_via_msisdn_route(
|
||||
_body: Ruma<request_3pid_management_token_via_msisdn::v3::Request>,
|
||||
_body: Ruma<request_3pid_management_token_via_msisdn::v3::IncomingRequest>,
|
||||
) -> Result<request_3pid_management_token_via_msisdn::v3::Response> {
|
||||
Err(Error::BadRequest(
|
||||
ErrorKind::ThreepidDenied,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use crate::{services, Error, Result, Ruma};
|
||||
use rand::seq::SliceRandom;
|
||||
use regex::Regex;
|
||||
use ruma::{
|
||||
api::{
|
||||
|
@ -10,14 +9,14 @@ use ruma::{
|
|||
},
|
||||
federation,
|
||||
},
|
||||
OwnedRoomAliasId,
|
||||
RoomAliasId,
|
||||
};
|
||||
|
||||
/// # `PUT /_matrix/client/r0/directory/room/{roomAlias}`
|
||||
///
|
||||
/// Creates a new room alias on this server.
|
||||
pub async fn create_alias_route(
|
||||
body: Ruma<create_alias::v3::Request>,
|
||||
body: Ruma<create_alias::v3::IncomingRequest>,
|
||||
) -> Result<create_alias::v3::Response> {
|
||||
if body.room_alias.server_name() != services().globals.server_name() {
|
||||
return Err(Error::BadRequest(
|
||||
|
@ -50,7 +49,7 @@ pub async fn create_alias_route(
|
|||
/// - TODO: additional access control checks
|
||||
/// - TODO: Update canonical alias event
|
||||
pub async fn delete_alias_route(
|
||||
body: Ruma<delete_alias::v3::Request>,
|
||||
body: Ruma<delete_alias::v3::IncomingRequest>,
|
||||
) -> Result<delete_alias::v3::Response> {
|
||||
if body.room_alias.server_name() != services().globals.server_name() {
|
||||
return Err(Error::BadRequest(
|
||||
|
@ -72,33 +71,29 @@ pub async fn delete_alias_route(
|
|||
///
|
||||
/// - TODO: Suggest more servers to join via
|
||||
pub async fn get_alias_route(
|
||||
body: Ruma<get_alias::v3::Request>,
|
||||
body: Ruma<get_alias::v3::IncomingRequest>,
|
||||
) -> Result<get_alias::v3::Response> {
|
||||
get_alias_helper(body.body.room_alias).await
|
||||
get_alias_helper(&body.room_alias).await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_alias_helper(
|
||||
room_alias: OwnedRoomAliasId,
|
||||
) -> Result<get_alias::v3::Response> {
|
||||
pub(crate) async fn get_alias_helper(room_alias: &RoomAliasId) -> Result<get_alias::v3::Response> {
|
||||
if room_alias.server_name() != services().globals.server_name() {
|
||||
let response = services()
|
||||
.sending
|
||||
.send_federation_request(
|
||||
room_alias.server_name(),
|
||||
federation::query::get_room_information::v1::Request {
|
||||
room_alias: room_alias.to_owned(),
|
||||
},
|
||||
federation::query::get_room_information::v1::Request { room_alias },
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut servers = response.servers;
|
||||
servers.shuffle(&mut rand::thread_rng());
|
||||
|
||||
return Ok(get_alias::v3::Response::new(response.room_id, servers));
|
||||
return Ok(get_alias::v3::Response::new(
|
||||
response.room_id,
|
||||
response.servers,
|
||||
));
|
||||
}
|
||||
|
||||
let mut room_id = None;
|
||||
match services().rooms.alias.resolve_local_alias(&room_alias)? {
|
||||
match services().rooms.alias.resolve_local_alias(room_alias)? {
|
||||
Some(r) => room_id = Some(r),
|
||||
None => {
|
||||
for (_id, registration) in services().appservice.all()? {
|
||||
|
@ -120,9 +115,7 @@ pub(crate) async fn get_alias_helper(
|
|||
.sending
|
||||
.send_appservice_request(
|
||||
registration,
|
||||
appservice::query::query_room_alias::v1::Request {
|
||||
room_alias: room_alias.clone(),
|
||||
},
|
||||
appservice::query::query_room_alias::v1::Request { room_alias },
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
|
@ -131,7 +124,7 @@ pub(crate) async fn get_alias_helper(
|
|||
services()
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_local_alias(&room_alias)?
|
||||
.resolve_local_alias(room_alias)?
|
||||
.ok_or_else(|| {
|
||||
Error::bad_config("Appservice lied to us. Room does not exist.")
|
||||
})?,
|
||||
|
|
|
@ -28,7 +28,7 @@ pub async fn create_backup_version_route(
|
|||
///
|
||||
/// Update information about an existing backup. Only `auth_data` can be modified.
|
||||
pub async fn update_backup_version_route(
|
||||
body: Ruma<update_backup_version::v3::Request>,
|
||||
body: Ruma<update_backup_version::v3::IncomingRequest>,
|
||||
) -> Result<update_backup_version::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
services()
|
||||
|
@ -66,7 +66,7 @@ pub async fn get_latest_backup_info_route(
|
|||
///
|
||||
/// Get information about an existing backup.
|
||||
pub async fn get_backup_info_route(
|
||||
body: Ruma<get_backup_info::v3::Request>,
|
||||
body: Ruma<get_backup_info::v3::IncomingRequest>,
|
||||
) -> Result<get_backup_info::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let algorithm = services()
|
||||
|
@ -96,7 +96,7 @@ pub async fn get_backup_info_route(
|
|||
///
|
||||
/// - Deletes both information about the backup, as well as all key data related to the backup
|
||||
pub async fn delete_backup_version_route(
|
||||
body: Ruma<delete_backup_version::v3::Request>,
|
||||
body: Ruma<delete_backup_version::v3::IncomingRequest>,
|
||||
) -> Result<delete_backup_version::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -115,7 +115,7 @@ pub async fn delete_backup_version_route(
|
|||
/// - Adds the keys to the backup
|
||||
/// - Returns the new number of keys in this backup and the etag
|
||||
pub async fn add_backup_keys_route(
|
||||
body: Ruma<add_backup_keys::v3::Request>,
|
||||
body: Ruma<add_backup_keys::v3::IncomingRequest>,
|
||||
) -> Result<add_backup_keys::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -162,7 +162,7 @@ pub async fn add_backup_keys_route(
|
|||
/// - Adds the keys to the backup
|
||||
/// - Returns the new number of keys in this backup and the etag
|
||||
pub async fn add_backup_keys_for_room_route(
|
||||
body: Ruma<add_backup_keys_for_room::v3::Request>,
|
||||
body: Ruma<add_backup_keys_for_room::v3::IncomingRequest>,
|
||||
) -> Result<add_backup_keys_for_room::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -207,7 +207,7 @@ pub async fn add_backup_keys_for_room_route(
|
|||
/// - Adds the keys to the backup
|
||||
/// - Returns the new number of keys in this backup and the etag
|
||||
pub async fn add_backup_keys_for_session_route(
|
||||
body: Ruma<add_backup_keys_for_session::v3::Request>,
|
||||
body: Ruma<add_backup_keys_for_session::v3::IncomingRequest>,
|
||||
) -> Result<add_backup_keys_for_session::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -246,7 +246,7 @@ pub async fn add_backup_keys_for_session_route(
|
|||
///
|
||||
/// Retrieves all keys from the backup.
|
||||
pub async fn get_backup_keys_route(
|
||||
body: Ruma<get_backup_keys::v3::Request>,
|
||||
body: Ruma<get_backup_keys::v3::IncomingRequest>,
|
||||
) -> Result<get_backup_keys::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -259,7 +259,7 @@ pub async fn get_backup_keys_route(
|
|||
///
|
||||
/// Retrieves all keys from the backup for a given room.
|
||||
pub async fn get_backup_keys_for_room_route(
|
||||
body: Ruma<get_backup_keys_for_room::v3::Request>,
|
||||
body: Ruma<get_backup_keys_for_room::v3::IncomingRequest>,
|
||||
) -> Result<get_backup_keys_for_room::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -274,7 +274,7 @@ pub async fn get_backup_keys_for_room_route(
|
|||
///
|
||||
/// Retrieves a key from the backup.
|
||||
pub async fn get_backup_keys_for_session_route(
|
||||
body: Ruma<get_backup_keys_for_session::v3::Request>,
|
||||
body: Ruma<get_backup_keys_for_session::v3::IncomingRequest>,
|
||||
) -> Result<get_backup_keys_for_session::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -293,7 +293,7 @@ pub async fn get_backup_keys_for_session_route(
|
|||
///
|
||||
/// Delete the keys from the backup.
|
||||
pub async fn delete_backup_keys_route(
|
||||
body: Ruma<delete_backup_keys::v3::Request>,
|
||||
body: Ruma<delete_backup_keys::v3::IncomingRequest>,
|
||||
) -> Result<delete_backup_keys::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -316,7 +316,7 @@ pub async fn delete_backup_keys_route(
|
|||
///
|
||||
/// Delete the keys from the backup for a given room.
|
||||
pub async fn delete_backup_keys_for_room_route(
|
||||
body: Ruma<delete_backup_keys_for_room::v3::Request>,
|
||||
body: Ruma<delete_backup_keys_for_room::v3::IncomingRequest>,
|
||||
) -> Result<delete_backup_keys_for_room::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -339,7 +339,7 @@ pub async fn delete_backup_keys_for_room_route(
|
|||
///
|
||||
/// Delete a key from the backup.
|
||||
pub async fn delete_backup_keys_for_session_route(
|
||||
body: Ruma<delete_backup_keys_for_session::v3::Request>,
|
||||
body: Ruma<delete_backup_keys_for_session::v3::IncomingRequest>,
|
||||
) -> Result<delete_backup_keys_for_session::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ use std::collections::BTreeMap;
|
|||
///
|
||||
/// Get information on the supported feature set and other relevent capabilities of this server.
|
||||
pub async fn get_capabilities_route(
|
||||
_body: Ruma<get_capabilities::v3::Request>,
|
||||
_body: Ruma<get_capabilities::v3::IncomingRequest>,
|
||||
) -> Result<get_capabilities::v3::Response> {
|
||||
let mut available = BTreeMap::new();
|
||||
for room_version in &services().globals.unstable_room_versions {
|
||||
|
|
|
@ -17,7 +17,7 @@ use serde_json::{json, value::RawValue as RawJsonValue};
|
|||
///
|
||||
/// Sets some account data for the sender user.
|
||||
pub async fn set_global_account_data_route(
|
||||
body: Ruma<set_global_account_data::v3::Request>,
|
||||
body: Ruma<set_global_account_data::v3::IncomingRequest>,
|
||||
) -> Result<set_global_account_data::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -43,7 +43,7 @@ pub async fn set_global_account_data_route(
|
|||
///
|
||||
/// Sets some room account data for the sender user.
|
||||
pub async fn set_room_account_data_route(
|
||||
body: Ruma<set_room_account_data::v3::Request>,
|
||||
body: Ruma<set_room_account_data::v3::IncomingRequest>,
|
||||
) -> Result<set_room_account_data::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -69,13 +69,13 @@ pub async fn set_room_account_data_route(
|
|||
///
|
||||
/// Gets some account data for the sender user.
|
||||
pub async fn get_global_account_data_route(
|
||||
body: Ruma<get_global_account_data::v3::Request>,
|
||||
body: Ruma<get_global_account_data::v3::IncomingRequest>,
|
||||
) -> Result<get_global_account_data::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let event: Box<RawJsonValue> = services()
|
||||
.account_data
|
||||
.get(None, sender_user, body.event_type.to_string().into())?
|
||||
.get(None, sender_user, body.event_type.clone().into())?
|
||||
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
|
||||
|
||||
let account_data = serde_json::from_str::<ExtractGlobalEventContent>(event.get())
|
||||
|
@ -89,13 +89,17 @@ pub async fn get_global_account_data_route(
|
|||
///
|
||||
/// Gets some room account data for the sender user.
|
||||
pub async fn get_room_account_data_route(
|
||||
body: Ruma<get_room_account_data::v3::Request>,
|
||||
body: Ruma<get_room_account_data::v3::IncomingRequest>,
|
||||
) -> Result<get_room_account_data::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let event: Box<RawJsonValue> = services()
|
||||
.account_data
|
||||
.get(Some(&body.room_id), sender_user, body.event_type.clone())?
|
||||
.get(
|
||||
Some(&body.room_id),
|
||||
sender_user,
|
||||
body.event_type.clone().into(),
|
||||
)?
|
||||
.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;
|
||||
use std::{collections::HashSet, convert::TryFrom};
|
||||
use tracing::error;
|
||||
|
||||
/// # `GET /_matrix/client/r0/rooms/{roomId}/context`
|
||||
|
@ -13,7 +13,7 @@ use tracing::error;
|
|||
/// - Only works if the user is joined (TODO: always allow, but only show events if the user was
|
||||
/// joined, depending on history_visibility)
|
||||
pub async fn get_context_route(
|
||||
body: Ruma<get_context::v3::Request>,
|
||||
body: Ruma<get_context::v3::IncomingRequest>,
|
||||
) -> Result<get_context::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||
|
@ -27,35 +27,36 @@ pub async fn get_context_route(
|
|||
|
||||
let mut lazy_loaded = HashSet::new();
|
||||
|
||||
let base_token = services()
|
||||
let base_pdu_id = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_count(&body.event_id)?
|
||||
.get_pdu_id(&body.event_id)?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"Base event id not found.",
|
||||
))?;
|
||||
|
||||
let base_event =
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu(&body.event_id)?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"Base event not found.",
|
||||
))?;
|
||||
let base_token = services().rooms.timeline.pdu_count(&base_pdu_id)?;
|
||||
|
||||
let base_event = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_from_id(&base_pdu_id)?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"Base event not found.",
|
||||
))?;
|
||||
|
||||
let room_id = base_event.room_id.clone();
|
||||
|
||||
if !services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_event(sender_user, &room_id, &body.event_id)?
|
||||
.state_cache
|
||||
.is_joined(sender_user, &room_id)?
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"You don't have permission to view this event.",
|
||||
"You don't have permission to view this room.",
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -69,24 +70,19 @@ 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(limit / 2)
|
||||
.take(
|
||||
u32::try_from(body.limit).map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::InvalidParam, "Limit value is invalid.")
|
||||
})? as usize
|
||||
/ 2,
|
||||
)
|
||||
.filter_map(|r| r.ok()) // Remove buggy events
|
||||
.filter(|(_, pdu)| {
|
||||
services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_event(sender_user, &room_id, &pdu.event_id)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.collect();
|
||||
|
||||
for (_, event) in &events_before {
|
||||
|
@ -103,8 +99,8 @@ pub async fn get_context_route(
|
|||
|
||||
let start_token = events_before
|
||||
.last()
|
||||
.map(|(count, _)| count.stringify())
|
||||
.unwrap_or_else(|| base_token.stringify());
|
||||
.and_then(|(pdu_id, _)| services().rooms.timeline.pdu_count(pdu_id).ok())
|
||||
.map(|count| count.to_string());
|
||||
|
||||
let events_before: Vec<_> = events_before
|
||||
.into_iter()
|
||||
|
@ -115,15 +111,13 @@ pub async fn get_context_route(
|
|||
.rooms
|
||||
.timeline
|
||||
.pdus_after(sender_user, &room_id, base_token)?
|
||||
.take(limit / 2)
|
||||
.take(
|
||||
u32::try_from(body.limit).map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::InvalidParam, "Limit value is invalid.")
|
||||
})? as usize
|
||||
/ 2,
|
||||
)
|
||||
.filter_map(|r| r.ok()) // Remove buggy events
|
||||
.filter(|(_, pdu)| {
|
||||
services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_event(sender_user, &room_id, &pdu.event_id)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.collect();
|
||||
|
||||
for (_, event) in &events_after {
|
||||
|
@ -159,8 +153,8 @@ pub async fn get_context_route(
|
|||
|
||||
let end_token = events_after
|
||||
.last()
|
||||
.map(|(count, _)| count.stringify())
|
||||
.unwrap_or_else(|| base_token.stringify());
|
||||
.and_then(|(pdu_id, _)| services().rooms.timeline.pdu_count(pdu_id).ok())
|
||||
.map(|count| count.to_string());
|
||||
|
||||
let events_after: Vec<_> = events_after
|
||||
.into_iter()
|
||||
|
@ -197,8 +191,8 @@ pub async fn get_context_route(
|
|||
}
|
||||
|
||||
let resp = get_context::v3::Response {
|
||||
start: Some(start_token),
|
||||
end: Some(end_token),
|
||||
start: start_token,
|
||||
end: end_token,
|
||||
events_before,
|
||||
event: Some(base_event),
|
||||
events_after,
|
||||
|
|
|
@ -28,7 +28,7 @@ pub async fn get_devices_route(
|
|||
///
|
||||
/// Get metadata on a single device of the sender user.
|
||||
pub async fn get_device_route(
|
||||
body: Ruma<get_device::v3::Request>,
|
||||
body: Ruma<get_device::v3::IncomingRequest>,
|
||||
) -> Result<get_device::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -44,7 +44,7 @@ pub async fn get_device_route(
|
|||
///
|
||||
/// Updates the metadata on a given device of the sender user.
|
||||
pub async fn update_device_route(
|
||||
body: Ruma<update_device::v3::Request>,
|
||||
body: Ruma<update_device::v3::IncomingRequest>,
|
||||
) -> Result<update_device::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -72,7 +72,7 @@ pub async fn update_device_route(
|
|||
/// - Forgets to-device events
|
||||
/// - Triggers device list updates
|
||||
pub async fn delete_device_route(
|
||||
body: Ruma<delete_device::v3::Request>,
|
||||
body: Ruma<delete_device::v3::IncomingRequest>,
|
||||
) -> Result<delete_device::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||
|
@ -126,7 +126,7 @@ pub async fn delete_device_route(
|
|||
/// - Forgets to-device events
|
||||
/// - Triggers device list updates
|
||||
pub async fn delete_devices_route(
|
||||
body: Ruma<delete_devices::v3::Request>,
|
||||
body: Ruma<delete_devices::v3::IncomingRequest>,
|
||||
) -> Result<delete_devices::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||
|
|
|
@ -11,7 +11,10 @@ use ruma::{
|
|||
},
|
||||
federation,
|
||||
},
|
||||
directory::{Filter, PublicRoomJoinRule, PublicRoomsChunk, RoomNetwork},
|
||||
directory::{
|
||||
Filter, IncomingFilter, IncomingRoomNetwork, PublicRoomJoinRule, PublicRoomsChunk,
|
||||
RoomNetwork,
|
||||
},
|
||||
events::{
|
||||
room::{
|
||||
avatar::RoomAvatarEventContent,
|
||||
|
@ -20,6 +23,7 @@ use ruma::{
|
|||
guest_access::{GuestAccess, RoomGuestAccessEventContent},
|
||||
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
||||
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||
name::RoomNameEventContent,
|
||||
topic::RoomTopicEventContent,
|
||||
},
|
||||
StateEventType,
|
||||
|
@ -34,7 +38,7 @@ use tracing::{error, info, warn};
|
|||
///
|
||||
/// - Rooms are ordered by the number of joined members
|
||||
pub async fn get_public_rooms_filtered_route(
|
||||
body: Ruma<get_public_rooms_filtered::v3::Request>,
|
||||
body: Ruma<get_public_rooms_filtered::v3::IncomingRequest>,
|
||||
) -> Result<get_public_rooms_filtered::v3::Response> {
|
||||
get_public_rooms_filtered_helper(
|
||||
body.server.as_deref(),
|
||||
|
@ -52,14 +56,14 @@ pub async fn get_public_rooms_filtered_route(
|
|||
///
|
||||
/// - Rooms are ordered by the number of joined members
|
||||
pub async fn get_public_rooms_route(
|
||||
body: Ruma<get_public_rooms::v3::Request>,
|
||||
body: Ruma<get_public_rooms::v3::IncomingRequest>,
|
||||
) -> Result<get_public_rooms::v3::Response> {
|
||||
let response = get_public_rooms_filtered_helper(
|
||||
body.server.as_deref(),
|
||||
body.limit,
|
||||
body.since.as_deref(),
|
||||
&Filter::default(),
|
||||
&RoomNetwork::Matrix,
|
||||
&IncomingFilter::default(),
|
||||
&IncomingRoomNetwork::Matrix,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
@ -77,7 +81,7 @@ pub async fn get_public_rooms_route(
|
|||
///
|
||||
/// - TODO: Access control checks
|
||||
pub async fn set_room_visibility_route(
|
||||
body: Ruma<set_room_visibility::v3::Request>,
|
||||
body: Ruma<set_room_visibility::v3::IncomingRequest>,
|
||||
) -> Result<set_room_visibility::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -107,7 +111,7 @@ pub async fn set_room_visibility_route(
|
|||
///
|
||||
/// Gets the visibility of a given room in the room directory.
|
||||
pub async fn get_room_visibility_route(
|
||||
body: Ruma<get_room_visibility::v3::Request>,
|
||||
body: Ruma<get_room_visibility::v3::IncomingRequest>,
|
||||
) -> Result<get_room_visibility::v3::Response> {
|
||||
if !services().rooms.metadata.exists(&body.room_id)? {
|
||||
// Return 404 if the room doesn't exist
|
||||
|
@ -127,8 +131,8 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
|||
server: Option<&ServerName>,
|
||||
limit: Option<UInt>,
|
||||
since: Option<&str>,
|
||||
filter: &Filter,
|
||||
_network: &RoomNetwork,
|
||||
filter: &IncomingFilter,
|
||||
_network: &IncomingRoomNetwork,
|
||||
) -> Result<get_public_rooms_filtered::v3::Response> {
|
||||
if let Some(other_server) =
|
||||
server.filter(|server| *server != services().globals.server_name().as_str())
|
||||
|
@ -139,9 +143,9 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
|||
other_server,
|
||||
federation::directory::get_public_rooms_filtered::v1::Request {
|
||||
limit,
|
||||
since: since.map(ToOwned::to_owned),
|
||||
since,
|
||||
filter: Filter {
|
||||
generic_search_term: filter.generic_search_term.clone(),
|
||||
generic_search_term: filter.generic_search_term.as_deref(),
|
||||
room_types: filter.room_types.clone(),
|
||||
},
|
||||
room_network: RoomNetwork::Matrix,
|
||||
|
@ -202,7 +206,17 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
|||
Error::bad_database("Invalid canonical alias event in database.")
|
||||
})
|
||||
})?,
|
||||
name: services().rooms.state_accessor.get_name(&room_id)?,
|
||||
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.")
|
||||
})
|
||||
})?,
|
||||
num_joined_members: services()
|
||||
.rooms
|
||||
.state_cache
|
||||
|
@ -221,7 +235,6 @@ 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.")
|
||||
})
|
||||
})?,
|
||||
|
@ -351,7 +364,7 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
|||
let prev_batch = if num_since == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(format!("p{num_since}"))
|
||||
Some(format!("p{}", num_since))
|
||||
};
|
||||
|
||||
let next_batch = if chunk.len() < limit as usize {
|
||||
|
|
|
@ -10,7 +10,7 @@ use ruma::api::client::{
|
|||
///
|
||||
/// - A user can only access their own filters
|
||||
pub async fn get_filter_route(
|
||||
body: Ruma<get_filter::v3::Request>,
|
||||
body: Ruma<get_filter::v3::IncomingRequest>,
|
||||
) -> Result<get_filter::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let filter = match services().users.get_filter(sender_user, &body.filter_id)? {
|
||||
|
@ -25,7 +25,7 @@ pub async fn get_filter_route(
|
|||
///
|
||||
/// Creates a new filter to be used by other endpoints.
|
||||
pub async fn create_filter_route(
|
||||
body: Ruma<create_filter::v3::Request>,
|
||||
body: Ruma<create_filter::v3::IncomingRequest>,
|
||||
) -> Result<create_filter::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
Ok(create_filter::v3::Response::new(
|
||||
|
|
|
@ -17,11 +17,7 @@ use ruma::{
|
|||
DeviceKeyAlgorithm, OwnedDeviceId, OwnedUserId, UserId,
|
||||
};
|
||||
use serde_json::json;
|
||||
use std::{
|
||||
collections::{hash_map, BTreeMap, HashMap, HashSet},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tracing::debug;
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
|
||||
/// # `POST /_matrix/client/r0/keys/upload`
|
||||
///
|
||||
|
@ -69,7 +65,9 @@ pub async fn upload_keys_route(
|
|||
/// - Always fetches users from other servers over federation
|
||||
/// - Gets master keys, self-signing keys, user signing keys and device keys.
|
||||
/// - The master and self-signing keys contain signatures that the user is allowed to see
|
||||
pub async fn get_keys_route(body: Ruma<get_keys::v3::Request>) -> Result<get_keys::v3::Response> {
|
||||
pub async fn get_keys_route(
|
||||
body: Ruma<get_keys::v3::IncomingRequest>,
|
||||
) -> Result<get_keys::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let response =
|
||||
|
@ -95,7 +93,7 @@ pub async fn claim_keys_route(
|
|||
///
|
||||
/// - Requires UIAA to verify password
|
||||
pub async fn upload_signing_keys_route(
|
||||
body: Ruma<upload_signing_keys::v3::Request>,
|
||||
body: Ruma<upload_signing_keys::v3::IncomingRequest>,
|
||||
) -> Result<upload_signing_keys::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||
|
@ -136,7 +134,6 @@ 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
|
||||
)?;
|
||||
}
|
||||
|
||||
|
@ -156,6 +153,18 @@ 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(
|
||||
|
@ -205,7 +214,7 @@ pub async fn upload_signatures_route(
|
|||
///
|
||||
/// - TODO: left users
|
||||
pub async fn get_key_changes_route(
|
||||
body: Ruma<get_key_changes::v3::Request>,
|
||||
body: Ruma<get_key_changes::v3::IncomingRequest>,
|
||||
) -> Result<get_key_changes::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -316,17 +325,15 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(master_key) =
|
||||
services()
|
||||
.users
|
||||
.get_master_key(sender_user, user_id, &allowed_signatures)?
|
||||
if let Some(master_key) = services()
|
||||
.users
|
||||
.get_master_key(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(sender_user, user_id, &allowed_signatures)?
|
||||
if let Some(self_signing_key) = services()
|
||||
.users
|
||||
.get_self_signing_key(user_id, &allowed_signatures)?
|
||||
{
|
||||
self_signing_keys.insert(user_id.to_owned(), self_signing_key);
|
||||
}
|
||||
|
@ -339,96 +346,36 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
|
|||
|
||||
let mut failures = BTreeMap::new();
|
||||
|
||||
let back_off = |id| match services()
|
||||
.globals
|
||||
.bad_query_ratelimiter
|
||||
.write()
|
||||
.unwrap()
|
||||
.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()
|
||||
.unwrap()
|
||||
.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,
|
||||
tokio::time::timeout(
|
||||
Duration::from_secs(25),
|
||||
services().sending.send_federation_request(
|
||||
services()
|
||||
.sending
|
||||
.send_federation_request(
|
||||
server,
|
||||
federation::keys::get_keys::v1::Request {
|
||||
device_keys: device_keys_input_fed,
|
||||
},
|
||||
),
|
||||
)
|
||||
.await
|
||||
.map_err(|_e| Error::BadServerResponse("Query took too long")),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
while let Some((server, response)) = futures.next().await {
|
||||
match response {
|
||||
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);
|
||||
}
|
||||
|
||||
Ok(response) => {
|
||||
master_keys.extend(response.master_keys);
|
||||
self_signing_keys.extend(response.self_signing_keys);
|
||||
device_keys.extend(response.device_keys);
|
||||
}
|
||||
_ => {
|
||||
back_off(server.to_owned());
|
||||
Err(_e) => {
|
||||
failures.insert(server.to_string(), json!({}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use crate::{service::media::FileMeta, services, utils, Error, Result, Ruma};
|
||||
use ruma::api::client::{
|
||||
error::ErrorKind,
|
||||
|
@ -29,7 +27,7 @@ pub async fn get_media_config_route(
|
|||
/// - Some metadata will be saved in the database
|
||||
/// - Media will be saved in the media/ directory
|
||||
pub async fn create_content_route(
|
||||
body: Ruma<create_content::v3::Request>,
|
||||
body: Ruma<create_content::v3::IncomingRequest>,
|
||||
) -> Result<create_content::v3::Response> {
|
||||
let mxc = format!(
|
||||
"mxc://{}/{}",
|
||||
|
@ -51,7 +49,7 @@ pub async fn create_content_route(
|
|||
.await?;
|
||||
|
||||
Ok(create_content::v3::Response {
|
||||
content_uri: mxc.into(),
|
||||
content_uri: mxc.try_into().expect("Invalid mxc:// URI"),
|
||||
blurhash: None,
|
||||
})
|
||||
}
|
||||
|
@ -59,7 +57,7 @@ pub async fn create_content_route(
|
|||
pub async fn get_remote_content(
|
||||
mxc: &str,
|
||||
server_name: &ruma::ServerName,
|
||||
media_id: String,
|
||||
media_id: &str,
|
||||
) -> Result<get_content::v3::Response, Error> {
|
||||
let content_response = services()
|
||||
.sending
|
||||
|
@ -67,10 +65,8 @@ pub async fn get_remote_content(
|
|||
server_name,
|
||||
get_content::v3::Request {
|
||||
allow_remote: false,
|
||||
server_name: server_name.to_owned(),
|
||||
server_name,
|
||||
media_id,
|
||||
timeout_ms: Duration::from_secs(20),
|
||||
allow_redirect: false,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
@ -94,7 +90,7 @@ pub async fn get_remote_content(
|
|||
///
|
||||
/// - Only allows federation if `allow_remote` is true
|
||||
pub async fn get_content_route(
|
||||
body: Ruma<get_content::v3::Request>,
|
||||
body: Ruma<get_content::v3::IncomingRequest>,
|
||||
) -> Result<get_content::v3::Response> {
|
||||
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
|
||||
|
||||
|
@ -112,7 +108,7 @@ pub async fn get_content_route(
|
|||
})
|
||||
} else if &*body.server_name != services().globals.server_name() && body.allow_remote {
|
||||
let remote_content_response =
|
||||
get_remote_content(&mxc, &body.server_name, body.media_id.clone()).await?;
|
||||
get_remote_content(&mxc, &body.server_name, &body.media_id).await?;
|
||||
Ok(remote_content_response)
|
||||
} else {
|
||||
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
|
||||
|
@ -125,7 +121,7 @@ pub async fn get_content_route(
|
|||
///
|
||||
/// - Only allows federation if `allow_remote` is true
|
||||
pub async fn get_content_as_filename_route(
|
||||
body: Ruma<get_content_as_filename::v3::Request>,
|
||||
body: Ruma<get_content_as_filename::v3::IncomingRequest>,
|
||||
) -> Result<get_content_as_filename::v3::Response> {
|
||||
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
|
||||
|
||||
|
@ -143,7 +139,7 @@ pub async fn get_content_as_filename_route(
|
|||
})
|
||||
} else if &*body.server_name != services().globals.server_name() && body.allow_remote {
|
||||
let remote_content_response =
|
||||
get_remote_content(&mxc, &body.server_name, body.media_id.clone()).await?;
|
||||
get_remote_content(&mxc, &body.server_name, &body.media_id).await?;
|
||||
|
||||
Ok(get_content_as_filename::v3::Response {
|
||||
content_disposition: Some(format!("inline: filename={}", body.filename)),
|
||||
|
@ -162,7 +158,7 @@ pub async fn get_content_as_filename_route(
|
|||
///
|
||||
/// - Only allows federation if `allow_remote` is true
|
||||
pub async fn get_content_thumbnail_route(
|
||||
body: Ruma<get_content_thumbnail::v3::Request>,
|
||||
body: Ruma<get_content_thumbnail::v3::IncomingRequest>,
|
||||
) -> Result<get_content_thumbnail::v3::Response> {
|
||||
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
|
||||
|
||||
|
@ -196,10 +192,8 @@ pub async fn get_content_thumbnail_route(
|
|||
height: body.height,
|
||||
width: body.width,
|
||||
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,
|
||||
server_name: &body.server_name,
|
||||
media_id: &body.media_id,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,13 +1,10 @@
|
|||
use crate::{
|
||||
service::{pdu::PduBuilder, rooms::timeline::PduCount},
|
||||
services, utils, Error, Result, Ruma,
|
||||
};
|
||||
use crate::{service::pdu::PduBuilder, services, utils, Error, Result, Ruma};
|
||||
use ruma::{
|
||||
api::client::{
|
||||
error::ErrorKind,
|
||||
message::{get_message_events, send_message_event},
|
||||
},
|
||||
events::{StateEventType, TimelineEventType},
|
||||
events::{RoomEventType, StateEventType},
|
||||
};
|
||||
use std::{
|
||||
collections::{BTreeMap, HashSet},
|
||||
|
@ -22,7 +19,7 @@ use std::{
|
|||
/// - The only requirement for the content is that it has to be valid json
|
||||
/// - Tries to send the event into the room, auth rules will determine if it is allowed
|
||||
pub async fn send_message_event_route(
|
||||
body: Ruma<send_message_event::v3::Request>,
|
||||
body: Ruma<send_message_event::v3::IncomingRequest>,
|
||||
) -> Result<send_message_event::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let sender_device = body.sender_device.as_deref();
|
||||
|
@ -39,7 +36,7 @@ pub async fn send_message_event_route(
|
|||
let state_lock = mutex_state.lock().await;
|
||||
|
||||
// Forbid m.room.encrypted if encryption is disabled
|
||||
if TimelineEventType::RoomEncrypted == body.event_type.to_string().into()
|
||||
if RoomEventType::RoomEncrypted == body.event_type.to_string().into()
|
||||
&& !services().globals.allow_encryption()
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
|
@ -108,23 +105,34 @@ pub async fn send_message_event_route(
|
|||
/// - Only works if the user is joined (TODO: always allow, but only show events where the user was
|
||||
/// joined, depending on history_visibility)
|
||||
pub async fn get_message_events_route(
|
||||
body: Ruma<get_message_events::v3::Request>,
|
||||
body: Ruma<get_message_events::v3::IncomingRequest>,
|
||||
) -> Result<get_message_events::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||
|
||||
if !services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(sender_user, &body.room_id)?
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"You don't have permission to view this room.",
|
||||
));
|
||||
}
|
||||
|
||||
let from = match body.from.clone() {
|
||||
Some(from) => PduCount::try_from_string(&from)?,
|
||||
Some(from) => from
|
||||
.parse()
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `from` value."))?,
|
||||
|
||||
None => match body.dir {
|
||||
ruma::api::Direction::Forward => PduCount::min(),
|
||||
ruma::api::Direction::Backward => PduCount::max(),
|
||||
ruma::api::client::Direction::Forward => 0,
|
||||
ruma::api::client::Direction::Backward => u64::MAX,
|
||||
},
|
||||
};
|
||||
|
||||
let to = body
|
||||
.to
|
||||
.as_ref()
|
||||
.and_then(|t| PduCount::try_from_string(t).ok());
|
||||
let to = body.to.as_ref().map(|t| t.parse());
|
||||
|
||||
services().rooms.lazy_loading.lazy_load_confirm_delivery(
|
||||
sender_user,
|
||||
|
@ -133,7 +141,8 @@ pub async fn get_message_events_route(
|
|||
from,
|
||||
)?;
|
||||
|
||||
let limit = u64::from(body.limit).min(100) as usize;
|
||||
// Use limit or else 10
|
||||
let limit = body.limit.try_into().map_or(10_usize, |l: u32| l as usize);
|
||||
|
||||
let next_token;
|
||||
|
||||
|
@ -142,21 +151,22 @@ pub async fn get_message_events_route(
|
|||
let mut lazy_loaded = HashSet::new();
|
||||
|
||||
match body.dir {
|
||||
ruma::api::Direction::Forward => {
|
||||
ruma::api::client::Direction::Forward => {
|
||||
let events_after: Vec<_> = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_after(sender_user, &body.room_id, from)?
|
||||
.take(limit)
|
||||
.filter_map(|r| r.ok()) // Filter out buggy events
|
||||
.filter(|(_, pdu)| {
|
||||
.filter_map(|(pdu_id, pdu)| {
|
||||
services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_event(sender_user, &body.room_id, &pdu.event_id)
|
||||
.unwrap_or(false)
|
||||
.timeline
|
||||
.pdu_count(&pdu_id)
|
||||
.map(|pdu_count| (pdu_count, pdu))
|
||||
.ok()
|
||||
})
|
||||
.take_while(|&(k, _)| Some(k) != to) // Stop at `to`
|
||||
.take_while(|&(k, _)| Some(Ok(k)) != to) // Stop at `to`
|
||||
.collect();
|
||||
|
||||
for (_, event) in &events_after {
|
||||
|
@ -182,30 +192,26 @@ pub async fn get_message_events_route(
|
|||
.map(|(_, pdu)| pdu.to_room_event())
|
||||
.collect();
|
||||
|
||||
resp.start = from.stringify();
|
||||
resp.end = next_token.map(|count| count.stringify());
|
||||
resp.start = from.to_string();
|
||||
resp.end = next_token.map(|count| count.to_string());
|
||||
resp.chunk = events_after;
|
||||
}
|
||||
ruma::api::Direction::Backward => {
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.backfill_if_required(&body.room_id, from)
|
||||
.await?;
|
||||
ruma::api::client::Direction::Backward => {
|
||||
let events_before: Vec<_> = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_until(sender_user, &body.room_id, from)?
|
||||
.take(limit)
|
||||
.filter_map(|r| r.ok()) // Filter out buggy events
|
||||
.filter(|(_, pdu)| {
|
||||
.filter_map(|(pdu_id, pdu)| {
|
||||
services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_event(sender_user, &body.room_id, &pdu.event_id)
|
||||
.unwrap_or(false)
|
||||
.timeline
|
||||
.pdu_count(&pdu_id)
|
||||
.map(|pdu_count| (pdu_count, pdu))
|
||||
.ok()
|
||||
})
|
||||
.take_while(|&(k, _)| Some(k) != to) // Stop at `to`
|
||||
.take_while(|&(k, _)| Some(Ok(k)) != to) // Stop at `to`
|
||||
.collect();
|
||||
|
||||
for (_, event) in &events_before {
|
||||
|
@ -231,8 +237,8 @@ pub async fn get_message_events_route(
|
|||
.map(|(_, pdu)| pdu.to_room_event())
|
||||
.collect();
|
||||
|
||||
resp.start = from.stringify();
|
||||
resp.end = next_token.map(|count| count.stringify());
|
||||
resp.start = from.to_string();
|
||||
resp.end = next_token.map(|count| count.to_string());
|
||||
resp.chunk = events_before;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,17 +16,14 @@ 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;
|
||||
|
@ -51,17 +48,14 @@ 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,15 +1,12 @@
|
|||
use crate::{services, utils, Error, Result, Ruma};
|
||||
use ruma::api::client::{
|
||||
error::ErrorKind,
|
||||
presence::{get_presence, set_presence},
|
||||
};
|
||||
use crate::{services, utils, Result, Ruma};
|
||||
use ruma::api::client::presence::{get_presence, set_presence};
|
||||
use std::time::Duration;
|
||||
|
||||
/// # `PUT /_matrix/client/r0/presence/{userId}/status`
|
||||
///
|
||||
/// Sets the presence state of the sender user.
|
||||
pub async fn set_presence_route(
|
||||
body: Ruma<set_presence::v3::Request>,
|
||||
body: Ruma<set_presence::v3::IncomingRequest>,
|
||||
) -> Result<set_presence::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -46,7 +43,7 @@ pub async fn set_presence_route(
|
|||
///
|
||||
/// - Only works if you share a room with the user
|
||||
pub async fn get_presence_route(
|
||||
body: Ruma<get_presence::v3::Request>,
|
||||
body: Ruma<get_presence::v3::IncomingRequest>,
|
||||
) -> Result<get_presence::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -82,9 +79,6 @@ pub async fn get_presence_route(
|
|||
presence: presence.content.presence,
|
||||
})
|
||||
} else {
|
||||
Err(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"Presence state for this user was not found",
|
||||
))
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use ruma::{
|
|||
},
|
||||
federation::{self, query::get_profile_information::v1::ProfileField},
|
||||
},
|
||||
events::{room::member::RoomMemberEventContent, StateEventType, TimelineEventType},
|
||||
events::{room::member::RoomMemberEventContent, RoomEventType, StateEventType},
|
||||
};
|
||||
use serde_json::value::to_raw_value;
|
||||
use std::sync::Arc;
|
||||
|
@ -20,7 +20,7 @@ use std::sync::Arc;
|
|||
///
|
||||
/// - Also makes sure other users receive the update using presence EDUs
|
||||
pub async fn set_displayname_route(
|
||||
body: Ruma<set_display_name::v3::Request>,
|
||||
body: Ruma<set_display_name::v3::IncomingRequest>,
|
||||
) -> Result<set_display_name::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -37,7 +37,7 @@ pub async fn set_displayname_route(
|
|||
.map(|room_id| {
|
||||
Ok::<_, Error>((
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
event_type: RoomEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
displayname: body.displayname.clone(),
|
||||
..serde_json::from_str(
|
||||
|
@ -121,7 +121,7 @@ pub async fn set_displayname_route(
|
|||
///
|
||||
/// - If user is on another server: Fetches displayname over federation
|
||||
pub async fn get_displayname_route(
|
||||
body: Ruma<get_display_name::v3::Request>,
|
||||
body: Ruma<get_display_name::v3::IncomingRequest>,
|
||||
) -> Result<get_display_name::v3::Response> {
|
||||
if body.user_id.server_name() != services().globals.server_name() {
|
||||
let response = services()
|
||||
|
@ -129,8 +129,8 @@ pub async fn get_displayname_route(
|
|||
.send_federation_request(
|
||||
body.user_id.server_name(),
|
||||
federation::query::get_profile_information::v1::Request {
|
||||
user_id: body.user_id.clone(),
|
||||
field: Some(ProfileField::DisplayName),
|
||||
user_id: &body.user_id,
|
||||
field: Some(&ProfileField::DisplayName),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
@ -151,7 +151,7 @@ pub async fn get_displayname_route(
|
|||
///
|
||||
/// - Also makes sure other users receive the update using presence EDUs
|
||||
pub async fn set_avatar_url_route(
|
||||
body: Ruma<set_avatar_url::v3::Request>,
|
||||
body: Ruma<set_avatar_url::v3::IncomingRequest>,
|
||||
) -> Result<set_avatar_url::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -172,7 +172,7 @@ pub async fn set_avatar_url_route(
|
|||
.map(|room_id| {
|
||||
Ok::<_, Error>((
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
event_type: RoomEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
avatar_url: body.avatar_url.clone(),
|
||||
..serde_json::from_str(
|
||||
|
@ -256,7 +256,7 @@ pub async fn set_avatar_url_route(
|
|||
///
|
||||
/// - If user is on another server: Fetches avatar_url and blurhash over federation
|
||||
pub async fn get_avatar_url_route(
|
||||
body: Ruma<get_avatar_url::v3::Request>,
|
||||
body: Ruma<get_avatar_url::v3::IncomingRequest>,
|
||||
) -> Result<get_avatar_url::v3::Response> {
|
||||
if body.user_id.server_name() != services().globals.server_name() {
|
||||
let response = services()
|
||||
|
@ -264,8 +264,8 @@ pub async fn get_avatar_url_route(
|
|||
.send_federation_request(
|
||||
body.user_id.server_name(),
|
||||
federation::query::get_profile_information::v1::Request {
|
||||
user_id: body.user_id.clone(),
|
||||
field: Some(ProfileField::AvatarUrl),
|
||||
user_id: &body.user_id,
|
||||
field: Some(&ProfileField::AvatarUrl),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
@ -288,7 +288,7 @@ pub async fn get_avatar_url_route(
|
|||
///
|
||||
/// - If user is on another server: Fetches profile over federation
|
||||
pub async fn get_profile_route(
|
||||
body: Ruma<get_profile::v3::Request>,
|
||||
body: Ruma<get_profile::v3::IncomingRequest>,
|
||||
) -> Result<get_profile::v3::Response> {
|
||||
if body.user_id.server_name() != services().globals.server_name() {
|
||||
let response = services()
|
||||
|
@ -296,7 +296,7 @@ pub async fn get_profile_route(
|
|||
.send_federation_request(
|
||||
body.user_id.server_name(),
|
||||
federation::query::get_profile_information::v1::Request {
|
||||
user_id: body.user_id.clone(),
|
||||
user_id: &body.user_id,
|
||||
field: None,
|
||||
},
|
||||
)
|
||||
|
|
|
@ -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, RuleScope,
|
||||
set_pushrule_enabled, RuleKind,
|
||||
},
|
||||
},
|
||||
events::{push_rules::PushRulesEvent, GlobalAccountDataEventType},
|
||||
push::{InsertPushRuleError, RemovePushRuleError},
|
||||
push::{ConditionalPushRuleInit, PatternedPushRuleInit, SimplePushRuleInit},
|
||||
};
|
||||
|
||||
/// # `GET /_matrix/client/r0/pushrules`
|
||||
|
@ -45,7 +45,7 @@ pub async fn get_pushrules_all_route(
|
|||
///
|
||||
/// Retrieves a single specified push rule for this user.
|
||||
pub async fn get_pushrule_route(
|
||||
body: Ruma<get_pushrule::v3::Request>,
|
||||
body: Ruma<get_pushrule::v3::IncomingRequest>,
|
||||
) -> Result<get_pushrule::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -65,10 +65,30 @@ pub async fn get_pushrule_route(
|
|||
.map_err(|_| Error::bad_database("Invalid account data event in db."))?
|
||||
.content;
|
||||
|
||||
let rule = account_data
|
||||
.global
|
||||
.get(body.kind.clone(), &body.rule_id)
|
||||
.map(Into::into);
|
||||
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,
|
||||
};
|
||||
|
||||
if let Some(rule) = rule {
|
||||
Ok(get_pushrule::v3::Response { rule })
|
||||
|
@ -84,12 +104,12 @@ pub async fn get_pushrule_route(
|
|||
///
|
||||
/// Creates a single specified push rule for this user.
|
||||
pub async fn set_pushrule_route(
|
||||
body: Ruma<set_pushrule::v3::Request>,
|
||||
body: Ruma<set_pushrule::v3::IncomingRequest>,
|
||||
) -> Result<set_pushrule::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let body = body.body;
|
||||
|
||||
if body.scope != RuleScope::Global {
|
||||
if body.scope != "global" {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Scopes other than 'global' are not supported.",
|
||||
|
@ -111,36 +131,67 @@ 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."))?;
|
||||
|
||||
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);
|
||||
let global = &mut account_data.content.global;
|
||||
match body.kind {
|
||||
RuleKind::Override => {
|
||||
global.override_.replace(
|
||||
ConditionalPushRuleInit {
|
||||
actions: body.actions,
|
||||
default: false,
|
||||
enabled: true,
|
||||
rule_id: body.rule_id,
|
||||
conditions: body.conditions,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
RuleKind::Underride => {
|
||||
global.underride.replace(
|
||||
ConditionalPushRuleInit {
|
||||
actions: body.actions,
|
||||
default: false,
|
||||
enabled: true,
|
||||
rule_id: body.rule_id,
|
||||
conditions: body.conditions,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
RuleKind::Sender => {
|
||||
global.sender.replace(
|
||||
SimplePushRuleInit {
|
||||
actions: body.actions,
|
||||
default: false,
|
||||
enabled: true,
|
||||
rule_id: body.rule_id,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
RuleKind::Room => {
|
||||
global.room.replace(
|
||||
SimplePushRuleInit {
|
||||
actions: body.actions,
|
||||
default: false,
|
||||
enabled: true,
|
||||
rule_id: body.rule_id,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
RuleKind::Content => {
|
||||
global.content.replace(
|
||||
PatternedPushRuleInit {
|
||||
actions: body.actions,
|
||||
default: false,
|
||||
enabled: true,
|
||||
rule_id: body.rule_id,
|
||||
pattern: body.pattern.unwrap_or_default(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
services().account_data.update(
|
||||
|
@ -157,11 +208,11 @@ pub async fn set_pushrule_route(
|
|||
///
|
||||
/// Gets the actions of a single specified push rule for this user.
|
||||
pub async fn get_pushrule_actions_route(
|
||||
body: Ruma<get_pushrule_actions::v3::Request>,
|
||||
body: Ruma<get_pushrule_actions::v3::IncomingRequest>,
|
||||
) -> Result<get_pushrule_actions::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
if body.scope != RuleScope::Global {
|
||||
if body.scope != "global" {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Scopes other than 'global' are not supported.",
|
||||
|
@ -185,26 +236,44 @@ pub async fn get_pushrule_actions_route(
|
|||
.content;
|
||||
|
||||
let global = account_data.global;
|
||||
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.",
|
||||
))?;
|
||||
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,
|
||||
};
|
||||
|
||||
Ok(get_pushrule_actions::v3::Response { actions })
|
||||
Ok(get_pushrule_actions::v3::Response {
|
||||
actions: actions.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// # `PUT /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/actions`
|
||||
///
|
||||
/// Sets the actions of a single specified push rule for this user.
|
||||
pub async fn set_pushrule_actions_route(
|
||||
body: Ruma<set_pushrule_actions::v3::Request>,
|
||||
body: Ruma<set_pushrule_actions::v3::IncomingRequest>,
|
||||
) -> Result<set_pushrule_actions::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
if body.scope != RuleScope::Global {
|
||||
if body.scope != "global" {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Scopes other than 'global' are not supported.",
|
||||
|
@ -226,17 +295,40 @@ 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."))?;
|
||||
|
||||
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.",
|
||||
));
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
services().account_data.update(
|
||||
None,
|
||||
|
@ -252,11 +344,11 @@ pub async fn set_pushrule_actions_route(
|
|||
///
|
||||
/// Gets the enabled status of a single specified push rule for this user.
|
||||
pub async fn get_pushrule_enabled_route(
|
||||
body: Ruma<get_pushrule_enabled::v3::Request>,
|
||||
body: Ruma<get_pushrule_enabled::v3::IncomingRequest>,
|
||||
) -> Result<get_pushrule_enabled::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
if body.scope != RuleScope::Global {
|
||||
if body.scope != "global" {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Scopes other than 'global' are not supported.",
|
||||
|
@ -279,13 +371,34 @@ 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 = global
|
||||
.get(body.kind.clone(), &body.rule_id)
|
||||
.map(|r| r.enabled())
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"Push rule not found.",
|
||||
))?;
|
||||
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,
|
||||
};
|
||||
|
||||
Ok(get_pushrule_enabled::v3::Response { enabled })
|
||||
}
|
||||
|
@ -294,11 +407,11 @@ pub async fn get_pushrule_enabled_route(
|
|||
///
|
||||
/// Sets the enabled status of a single specified push rule for this user.
|
||||
pub async fn set_pushrule_enabled_route(
|
||||
body: Ruma<set_pushrule_enabled::v3::Request>,
|
||||
body: Ruma<set_pushrule_enabled::v3::IncomingRequest>,
|
||||
) -> Result<set_pushrule_enabled::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
if body.scope != RuleScope::Global {
|
||||
if body.scope != "global" {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Scopes other than 'global' are not supported.",
|
||||
|
@ -320,16 +433,44 @@ 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."))?;
|
||||
|
||||
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.",
|
||||
));
|
||||
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);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
services().account_data.update(
|
||||
|
@ -346,11 +487,11 @@ pub async fn set_pushrule_enabled_route(
|
|||
///
|
||||
/// Deletes a single specified push rule for this user.
|
||||
pub async fn delete_pushrule_route(
|
||||
body: Ruma<delete_pushrule::v3::Request>,
|
||||
body: Ruma<delete_pushrule::v3::IncomingRequest>,
|
||||
) -> Result<delete_pushrule::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
if body.scope != RuleScope::Global {
|
||||
if body.scope != "global" {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Scopes other than 'global' are not supported.",
|
||||
|
@ -372,23 +513,34 @@ 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."))?;
|
||||
|
||||
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.")
|
||||
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);
|
||||
}
|
||||
_ => Error::BadRequest(ErrorKind::InvalidParam, "Invalid data."),
|
||||
};
|
||||
|
||||
return Err(err);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
services().account_data.update(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{service::rooms::timeline::PduCount, services, Error, Result, Ruma};
|
||||
use crate::{services, Error, Result, Ruma};
|
||||
use ruma::{
|
||||
api::client::{error::ErrorKind, read_marker::set_read_marker, receipt::create_receipt},
|
||||
events::{
|
||||
|
@ -16,7 +16,7 @@ use std::collections::BTreeMap;
|
|||
/// - Updates fully-read account data event to `fully_read`
|
||||
/// - If `read_receipt` is set: Update private marker and public read receipt EDU
|
||||
pub async fn set_read_marker_route(
|
||||
body: Ruma<set_read_marker::v3::Request>,
|
||||
body: Ruma<set_read_marker::v3::IncomingRequest>,
|
||||
) -> Result<set_read_marker::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -42,28 +42,18 @@ pub async fn set_read_marker_route(
|
|||
}
|
||||
|
||||
if let Some(event) = &body.private_read_receipt {
|
||||
let count = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_count(event)?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Event does not exist.",
|
||||
))?;
|
||||
let count = match count {
|
||||
PduCount::Backfilled(_) => {
|
||||
return Err(Error::BadRequest(
|
||||
services().rooms.edus.read_receipt.private_read_set(
|
||||
&body.room_id,
|
||||
sender_user,
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_count(event)?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Read receipt is in backfilled timeline",
|
||||
))
|
||||
}
|
||||
PduCount::Normal(c) => c,
|
||||
};
|
||||
services()
|
||||
.rooms
|
||||
.edus
|
||||
.read_receipt
|
||||
.private_read_set(&body.room_id, sender_user, count)?;
|
||||
"Event does not exist.",
|
||||
))?,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(event) = &body.read_receipt {
|
||||
|
@ -99,7 +89,7 @@ pub async fn set_read_marker_route(
|
|||
///
|
||||
/// Sets private read marker and public read receipt EDU.
|
||||
pub async fn create_receipt_route(
|
||||
body: Ruma<create_receipt::v3::Request>,
|
||||
body: Ruma<create_receipt::v3::IncomingRequest>,
|
||||
) -> Result<create_receipt::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -152,27 +142,17 @@ pub async fn create_receipt_route(
|
|||
)?;
|
||||
}
|
||||
create_receipt::v3::ReceiptType::ReadPrivate => {
|
||||
let count = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_count(&body.event_id)?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Event does not exist.",
|
||||
))?;
|
||||
let count = match count {
|
||||
PduCount::Backfilled(_) => {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Read receipt is in backfilled timeline",
|
||||
))
|
||||
}
|
||||
PduCount::Normal(c) => c,
|
||||
};
|
||||
services().rooms.edus.read_receipt.private_read_set(
|
||||
&body.room_id,
|
||||
sender_user,
|
||||
count,
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_count(&body.event_id)?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Event does not exist.",
|
||||
))?,
|
||||
)?;
|
||||
}
|
||||
_ => return Err(Error::bad_database("Unsupported receipt type")),
|
||||
|
|
|
@ -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, TimelineEventType},
|
||||
events::{room::redaction::RoomRedactionEventContent, RoomEventType},
|
||||
};
|
||||
|
||||
use serde_json::value::to_raw_value;
|
||||
|
@ -14,7 +14,7 @@ use serde_json::value::to_raw_value;
|
|||
///
|
||||
/// - TODO: Handle txn id
|
||||
pub async fn redact_event_route(
|
||||
body: Ruma<redact_event::v3::Request>,
|
||||
body: Ruma<redact_event::v3::IncomingRequest>,
|
||||
) -> Result<redact_event::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let body = body.body;
|
||||
|
@ -32,9 +32,8 @@ pub async fn redact_event_route(
|
|||
|
||||
let event_id = services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomRedaction,
|
||||
event_type: RoomEventType::RoomRedaction,
|
||||
content: to_raw_value(&RoomRedactionEventContent {
|
||||
redacts: Some(body.event_id.clone()),
|
||||
reason: body.reason.clone(),
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
|
|
|
@ -1,146 +0,0 @@
|
|||
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,
|
||||
)
|
||||
}
|
|
@ -10,7 +10,7 @@ use ruma::{
|
|||
/// Reports an inappropriate event to homeserver admins
|
||||
///
|
||||
pub async fn report_event_route(
|
||||
body: Ruma<report_content::v3::Request>,
|
||||
body: Ruma<report_content::v3::IncomingRequest>,
|
||||
) -> Result<report_content::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ use ruma::{
|
|||
tombstone::RoomTombstoneEventContent,
|
||||
topic::RoomTopicEventContent,
|
||||
},
|
||||
StateEventType, TimelineEventType,
|
||||
RoomEventType, StateEventType,
|
||||
},
|
||||
int,
|
||||
serde::JsonObject,
|
||||
|
@ -46,7 +46,7 @@ use tracing::{info, warn};
|
|||
/// - Send events implied by `name` and `topic`
|
||||
/// - Send invite events
|
||||
pub async fn create_room_route(
|
||||
body: Ruma<create_room::v3::Request>,
|
||||
body: Ruma<create_room::v3::IncomingRequest>,
|
||||
) -> Result<create_room::v3::Response> {
|
||||
use create_room::v3::RoomPreset;
|
||||
|
||||
|
@ -142,9 +142,8 @@ 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_v1(sender_user.clone()))
|
||||
to_raw_value(&RoomCreateEventContent::new(sender_user.clone()))
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid creation content"))?
|
||||
.get(),
|
||||
)
|
||||
|
@ -176,7 +175,7 @@ pub async fn create_room_route(
|
|||
// 1. The room create event
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomCreate,
|
||||
event_type: RoomEventType::RoomCreate,
|
||||
content: to_raw_value(&content).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
|
@ -190,7 +189,7 @@ pub async fn create_room_route(
|
|||
// 2. Let the room creator join
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
event_type: RoomEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
membership: MembershipState::Join,
|
||||
displayname: services().users.displayname(sender_user)?,
|
||||
|
@ -248,7 +247,7 @@ pub async fn create_room_route(
|
|||
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomPowerLevels,
|
||||
event_type: RoomEventType::RoomPowerLevels,
|
||||
content: to_raw_value(&power_levels_content)
|
||||
.expect("to_raw_value always works on serde_json::Value"),
|
||||
unsigned: None,
|
||||
|
@ -264,7 +263,7 @@ pub async fn create_room_route(
|
|||
if let Some(room_alias_id) = &alias {
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomCanonicalAlias,
|
||||
event_type: RoomEventType::RoomCanonicalAlias,
|
||||
content: to_raw_value(&RoomCanonicalAliasEventContent {
|
||||
alias: Some(room_alias_id.to_owned()),
|
||||
alt_aliases: vec![],
|
||||
|
@ -285,7 +284,7 @@ pub async fn create_room_route(
|
|||
// 5.1 Join Rules
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomJoinRules,
|
||||
event_type: RoomEventType::RoomJoinRules,
|
||||
content: to_raw_value(&RoomJoinRulesEventContent::new(match preset {
|
||||
RoomPreset::PublicChat => JoinRule::Public,
|
||||
// according to spec "invite" is the default
|
||||
|
@ -304,7 +303,7 @@ pub async fn create_room_route(
|
|||
// 5.2 History Visibility
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomHistoryVisibility,
|
||||
event_type: RoomEventType::RoomHistoryVisibility,
|
||||
content: to_raw_value(&RoomHistoryVisibilityEventContent::new(
|
||||
HistoryVisibility::Shared,
|
||||
))
|
||||
|
@ -321,7 +320,7 @@ pub async fn create_room_route(
|
|||
// 5.3 Guest Access
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomGuestAccess,
|
||||
event_type: RoomEventType::RoomGuestAccess,
|
||||
content: to_raw_value(&RoomGuestAccessEventContent::new(match preset {
|
||||
RoomPreset::PublicChat => GuestAccess::Forbidden,
|
||||
_ => GuestAccess::CanJoin,
|
||||
|
@ -347,7 +346,7 @@ 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 == TimelineEventType::RoomEncryption
|
||||
if pdu_builder.event_type == RoomEventType::RoomEncryption
|
||||
&& !services().globals.allow_encryption()
|
||||
{
|
||||
continue;
|
||||
|
@ -365,8 +364,8 @@ pub async fn create_room_route(
|
|||
if let Some(name) = &body.name {
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomName,
|
||||
content: to_raw_value(&RoomNameEventContent::new(name.clone()))
|
||||
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()),
|
||||
|
@ -381,7 +380,7 @@ pub async fn create_room_route(
|
|||
if let Some(topic) = &body.topic {
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomTopic,
|
||||
event_type: RoomEventType::RoomTopic,
|
||||
content: to_raw_value(&RoomTopicEventContent {
|
||||
topic: topic.clone(),
|
||||
})
|
||||
|
@ -399,7 +398,7 @@ pub async fn create_room_route(
|
|||
// 8. Events implied by invite (and TODO: invite_3pid)
|
||||
drop(state_lock);
|
||||
for user_id in &body.invite {
|
||||
let _ = invite_helper(sender_user, user_id, &room_id, None, body.is_direct).await;
|
||||
let _ = invite_helper(sender_user, user_id, &room_id, body.is_direct).await;
|
||||
}
|
||||
|
||||
// Homeserver specific stuff
|
||||
|
@ -422,35 +421,28 @@ pub async fn create_room_route(
|
|||
///
|
||||
/// - You have to currently be joined to the room (TODO: Respect history visibility)
|
||||
pub async fn get_room_event_route(
|
||||
body: Ruma<get_room_event::v3::Request>,
|
||||
body: Ruma<get_room_event::v3::IncomingRequest>,
|
||||
) -> Result<get_room_event::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let event = services()
|
||||
if !services()
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu(&body.event_id)?
|
||||
.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,
|
||||
&event.room_id,
|
||||
&body.event_id,
|
||||
)? {
|
||||
.state_cache
|
||||
.is_joined(sender_user, &body.room_id)?
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"You don't have permission to view this event.",
|
||||
"You don't have permission to view this room.",
|
||||
));
|
||||
}
|
||||
|
||||
let mut event = (*event).clone();
|
||||
event.add_age()?;
|
||||
|
||||
Ok(get_room_event::v3::Response {
|
||||
event: event.to_room_event(),
|
||||
event: services()
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu(&body.event_id)?
|
||||
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Event not found."))?
|
||||
.to_room_event(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -460,7 +452,7 @@ pub async fn get_room_event_route(
|
|||
///
|
||||
/// - Only users joined to the room are allowed to call this TODO: Allow any user to call it if history_visibility is world readable
|
||||
pub async fn get_room_aliases_route(
|
||||
body: Ruma<aliases::v3::Request>,
|
||||
body: Ruma<aliases::v3::IncomingRequest>,
|
||||
) -> Result<aliases::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -496,7 +488,7 @@ pub async fn get_room_aliases_route(
|
|||
/// - Moves local aliases
|
||||
/// - Modifies old room power levels to prevent users from speaking
|
||||
pub async fn upgrade_room_route(
|
||||
body: Ruma<upgrade_room::v3::Request>,
|
||||
body: Ruma<upgrade_room::v3::IncomingRequest>,
|
||||
) -> Result<upgrade_room::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -533,7 +525,7 @@ pub async fn upgrade_room_route(
|
|||
// Fail if the sender does not have the required permissions
|
||||
let tombstone_event_id = services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomTombstone,
|
||||
event_type: RoomEventType::RoomTombstone,
|
||||
content: to_raw_value(&RoomTombstoneEventContent {
|
||||
body: "This room has been replaced".to_owned(),
|
||||
replacement_room: replacement_room.clone(),
|
||||
|
@ -615,7 +607,7 @@ pub async fn upgrade_room_route(
|
|||
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomCreate,
|
||||
event_type: RoomEventType::RoomCreate,
|
||||
content: to_raw_value(&create_event_content)
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
|
@ -630,7 +622,7 @@ pub async fn upgrade_room_route(
|
|||
// Join the new room
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
event_type: RoomEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
membership: MembershipState::Join,
|
||||
displayname: services().users.displayname(sender_user)?,
|
||||
|
@ -723,7 +715,7 @@ pub async fn upgrade_room_route(
|
|||
// 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: TimelineEventType::RoomPowerLevels,
|
||||
event_type: RoomEventType::RoomPowerLevels,
|
||||
content: to_raw_value(&power_levels_event_content)
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
|
|
|
@ -15,7 +15,7 @@ use std::collections::BTreeMap;
|
|||
///
|
||||
/// - Only works if the user is currently joined to the room (TODO: Respect history visibility)
|
||||
pub async fn search_events_route(
|
||||
body: Ruma<search_events::v3::Request>,
|
||||
body: Ruma<search_events::v3::IncomingRequest>,
|
||||
) -> Result<search_events::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -31,8 +31,7 @@ pub async fn search_events_route(
|
|||
.collect()
|
||||
});
|
||||
|
||||
// Use limit or else 10, with maximum 100
|
||||
let limit = filter.limit.map_or(10, u64::from).min(100) as usize;
|
||||
let limit = filter.limit.map_or(10, |l| u64::from(l) as usize);
|
||||
|
||||
let mut searches = Vec::new();
|
||||
|
||||
|
@ -82,21 +81,6 @@ pub async fn search_events_route(
|
|||
|
||||
let results: Vec<_> = results
|
||||
.iter()
|
||||
.filter_map(|result| {
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_from_id(result)
|
||||
.ok()?
|
||||
.filter(|pdu| {
|
||||
services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_event(sender_user, &pdu.room_id, &pdu.event_id)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.map(|pdu| pdu.to_room_event())
|
||||
})
|
||||
.map(|result| {
|
||||
Ok::<_, Error>(SearchResult {
|
||||
context: EventContextResult {
|
||||
|
@ -107,7 +91,11 @@ pub async fn search_events_route(
|
|||
start: None,
|
||||
},
|
||||
rank: None,
|
||||
result: Some(result),
|
||||
result: services()
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_from_id(result)?
|
||||
.map(|pdu| pdu.to_room_event()),
|
||||
})
|
||||
})
|
||||
.filter_map(|r| r.ok())
|
||||
|
|
|
@ -4,12 +4,12 @@ use ruma::{
|
|||
api::client::{
|
||||
error::ErrorKind,
|
||||
session::{get_login_types, login, logout, logout_all},
|
||||
uiaa::UserIdentifier,
|
||||
uiaa::IncomingUserIdentifier,
|
||||
},
|
||||
UserId,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use tracing::{info, warn};
|
||||
use tracing::info;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Claims {
|
||||
|
@ -22,11 +22,10 @@ struct Claims {
|
|||
/// Get the supported login types of this server. One of these should be used as the `type` field
|
||||
/// when logging in.
|
||||
pub async fn get_login_types_route(
|
||||
_body: Ruma<get_login_types::v3::Request>,
|
||||
_body: Ruma<get_login_types::v3::IncomingRequest>,
|
||||
) -> 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,32 +40,24 @@ 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)]
|
||||
pub async fn login_route(body: Ruma<login::v3::IncomingRequest>) -> Result<login::v3::Response> {
|
||||
// Validate login method
|
||||
// TODO: Other login methods
|
||||
let user_id = match &body.login_info {
|
||||
login::v3::LoginInfo::Password(login::v3::Password {
|
||||
login::v3::IncomingLoginInfo::Password(login::v3::IncomingPassword {
|
||||
identifier,
|
||||
password,
|
||||
user,
|
||||
address: _,
|
||||
medium: _,
|
||||
}) => {
|
||||
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)
|
||||
let username = if let IncomingUserIdentifier::UserIdOrLocalpart(user_id) = identifier {
|
||||
user_id.to_lowercase()
|
||||
} 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."))?;
|
||||
|
||||
};
|
||||
let user_id =
|
||||
UserId::parse_with_server_name(username, services().globals.server_name())
|
||||
.map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid.")
|
||||
})?;
|
||||
let hash = services()
|
||||
.users
|
||||
.password_hash(&user_id)?
|
||||
|
@ -93,7 +84,7 @@ pub async fn login_route(body: Ruma<login::v3::Request>) -> Result<login::v3::Re
|
|||
|
||||
user_id
|
||||
}
|
||||
login::v3::LoginInfo::Token(login::v3::Token { token }) => {
|
||||
login::v3::IncomingLoginInfo::Token(login::v3::IncomingToken { token }) => {
|
||||
if let Some(jwt_decoding_key) = services().globals.jwt_decoding_key() {
|
||||
let token = jsonwebtoken::decode::<Claims>(
|
||||
token,
|
||||
|
@ -112,31 +103,7 @@ 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::Forbidden,
|
||||
"Forbidden login type.",
|
||||
));
|
||||
};
|
||||
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.",
|
||||
|
@ -174,8 +141,6 @@ 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,
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -7,12 +7,15 @@ use ruma::{
|
|||
state::{get_state_events, get_state_events_for_key, send_state_event},
|
||||
},
|
||||
events::{
|
||||
room::canonical_alias::RoomCanonicalAliasEventContent, AnyStateEventContent, StateEventType,
|
||||
room::{
|
||||
canonical_alias::RoomCanonicalAliasEventContent,
|
||||
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
||||
},
|
||||
AnyStateEventContent, StateEventType,
|
||||
},
|
||||
serde::Raw,
|
||||
EventId, RoomId, UserId,
|
||||
};
|
||||
use tracing::log::warn;
|
||||
|
||||
/// # `PUT /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey}`
|
||||
///
|
||||
|
@ -22,7 +25,7 @@ use tracing::log::warn;
|
|||
/// - Tries to send the event into the room, auth rules will determine if it is allowed
|
||||
/// - If event is new canonical_alias: Rejects if alias is incorrect
|
||||
pub async fn send_state_event_for_key_route(
|
||||
body: Ruma<send_state_event::v3::Request>,
|
||||
body: Ruma<send_state_event::v3::IncomingRequest>,
|
||||
) -> Result<send_state_event::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -47,7 +50,7 @@ pub async fn send_state_event_for_key_route(
|
|||
/// - Tries to send the event into the room, auth rules will determine if it is allowed
|
||||
/// - If event is new canonical_alias: Rejects if alias is incorrect
|
||||
pub async fn send_state_event_for_empty_key_route(
|
||||
body: Ruma<send_state_event::v3::Request>,
|
||||
body: Ruma<send_state_event::v3::IncomingRequest>,
|
||||
) -> Result<RumaResponse<send_state_event::v3::Response>> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -78,14 +81,33 @@ pub async fn send_state_event_for_empty_key_route(
|
|||
///
|
||||
/// - If not joined: Only works if current room history visibility is world readable
|
||||
pub async fn get_state_events_route(
|
||||
body: Ruma<get_state_events::v3::Request>,
|
||||
body: Ruma<get_state_events::v3::IncomingRequest>,
|
||||
) -> Result<get_state_events::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
#[allow(clippy::blocks_in_if_conditions)]
|
||||
// Users not in the room should not be able to access the state unless history_visibility is
|
||||
// WorldReadable
|
||||
if !services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_state_events(sender_user, &body.room_id)?
|
||||
.state_cache
|
||||
.is_joined(sender_user, &body.room_id)?
|
||||
&& !matches!(
|
||||
services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(&body.room_id, &StateEventType::RoomHistoryVisibility, "")?
|
||||
.map(|event| {
|
||||
serde_json::from_str(event.content.get())
|
||||
.map(|e: RoomHistoryVisibilityEventContent| e.history_visibility)
|
||||
.map_err(|_| {
|
||||
Error::bad_database(
|
||||
"Invalid room history visibility event in database.",
|
||||
)
|
||||
})
|
||||
}),
|
||||
Some(Ok(HistoryVisibility::WorldReadable))
|
||||
)
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
|
@ -111,14 +133,33 @@ pub async fn get_state_events_route(
|
|||
///
|
||||
/// - If not joined: Only works if current room history visibility is world readable
|
||||
pub async fn get_state_events_for_key_route(
|
||||
body: Ruma<get_state_events_for_key::v3::Request>,
|
||||
body: Ruma<get_state_events_for_key::v3::IncomingRequest>,
|
||||
) -> Result<get_state_events_for_key::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
#[allow(clippy::blocks_in_if_conditions)]
|
||||
// Users not in the room should not be able to access the state unless history_visibility is
|
||||
// WorldReadable
|
||||
if !services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_state_events(sender_user, &body.room_id)?
|
||||
.state_cache
|
||||
.is_joined(sender_user, &body.room_id)?
|
||||
&& !matches!(
|
||||
services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(&body.room_id, &StateEventType::RoomHistoryVisibility, "")?
|
||||
.map(|event| {
|
||||
serde_json::from_str(event.content.get())
|
||||
.map(|e: RoomHistoryVisibilityEventContent| e.history_visibility)
|
||||
.map_err(|_| {
|
||||
Error::bad_database(
|
||||
"Invalid room history visibility event in database.",
|
||||
)
|
||||
})
|
||||
}),
|
||||
Some(Ok(HistoryVisibility::WorldReadable))
|
||||
)
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
|
@ -130,13 +171,10 @@ 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_else(|| {
|
||||
warn!(
|
||||
"State event {:?} not found in room {:?}",
|
||||
&body.event_type, &body.room_id
|
||||
);
|
||||
Error::BadRequest(ErrorKind::NotFound, "State event not found.")
|
||||
})?;
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"State event not found.",
|
||||
))?;
|
||||
|
||||
Ok(get_state_events_for_key::v3::Response {
|
||||
content: serde_json::from_str(event.content.get())
|
||||
|
@ -150,14 +188,33 @@ pub async fn get_state_events_for_key_route(
|
|||
///
|
||||
/// - If not joined: Only works if current room history visibility is world readable
|
||||
pub async fn get_state_events_for_empty_key_route(
|
||||
body: Ruma<get_state_events_for_key::v3::Request>,
|
||||
body: Ruma<get_state_events_for_key::v3::IncomingRequest>,
|
||||
) -> Result<RumaResponse<get_state_events_for_key::v3::Response>> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
#[allow(clippy::blocks_in_if_conditions)]
|
||||
// Users not in the room should not be able to access the state unless history_visibility is
|
||||
// WorldReadable
|
||||
if !services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_state_events(sender_user, &body.room_id)?
|
||||
.state_cache
|
||||
.is_joined(sender_user, &body.room_id)?
|
||||
&& !matches!(
|
||||
services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(&body.room_id, &StateEventType::RoomHistoryVisibility, "")?
|
||||
.map(|event| {
|
||||
serde_json::from_str(event.content.get())
|
||||
.map(|e: RoomHistoryVisibilityEventContent| e.history_visibility)
|
||||
.map_err(|_| {
|
||||
Error::bad_database(
|
||||
"Invalid room history visibility event in database.",
|
||||
)
|
||||
})
|
||||
}),
|
||||
Some(Ok(HistoryVisibility::WorldReadable))
|
||||
)
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
|
@ -169,13 +226,10 @@ pub async fn get_state_events_for_empty_key_route(
|
|||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(&body.room_id, &body.event_type, "")?
|
||||
.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_or(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"State event not found.",
|
||||
))?;
|
||||
|
||||
Ok(get_state_events_for_key::v3::Response {
|
||||
content: serde_json::from_str(event.content.get())
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -14,7 +14,7 @@ use std::collections::BTreeMap;
|
|||
///
|
||||
/// - Inserts the tag into the tag event of the room account data.
|
||||
pub async fn update_tag_route(
|
||||
body: Ruma<create_tag::v3::Request>,
|
||||
body: Ruma<create_tag::v3::IncomingRequest>,
|
||||
) -> Result<create_tag::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -58,7 +58,7 @@ pub async fn update_tag_route(
|
|||
///
|
||||
/// - Removes the tag from the tag event of the room account data.
|
||||
pub async fn delete_tag_route(
|
||||
body: Ruma<delete_tag::v3::Request>,
|
||||
body: Ruma<delete_tag::v3::IncomingRequest>,
|
||||
) -> Result<delete_tag::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -98,7 +98,9 @@ pub async fn delete_tag_route(
|
|||
/// Returns tags on the room.
|
||||
///
|
||||
/// - Gets the tag event of the room account data.
|
||||
pub async fn get_tags_route(body: Ruma<get_tags::v3::Request>) -> Result<get_tags::v3::Response> {
|
||||
pub async fn get_tags_route(
|
||||
body: Ruma<get_tags::v3::IncomingRequest>,
|
||||
) -> Result<get_tags::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let event = services().account_data.get(
|
||||
|
|
|
@ -7,7 +7,7 @@ use std::collections::BTreeMap;
|
|||
///
|
||||
/// TODO: Fetches all metadata about protocols supported by the homeserver.
|
||||
pub async fn get_protocols_route(
|
||||
_body: Ruma<get_protocols::v3::Request>,
|
||||
_body: Ruma<get_protocols::v3::IncomingRequest>,
|
||||
) -> Result<get_protocols::v3::Response> {
|
||||
// TODO
|
||||
Ok(get_protocols::v3::Response {
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
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,3 +1,4 @@
|
|||
use ruma::events::ToDeviceEventType;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::{services, Error, Result, Ruma};
|
||||
|
@ -13,7 +14,7 @@ use ruma::{
|
|||
///
|
||||
/// Send a to-device event to a set of client devices.
|
||||
pub async fn send_event_to_device_route(
|
||||
body: Ruma<send_event_to_device::v3::Request>,
|
||||
body: Ruma<send_event_to_device::v3::IncomingRequest>,
|
||||
) -> Result<send_event_to_device::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let sender_device = body.sender_device.as_deref();
|
||||
|
@ -41,7 +42,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: body.event_type.clone(),
|
||||
ev_type: ToDeviceEventType::from(&*body.event_type),
|
||||
message_id: count.to_string().into(),
|
||||
messages,
|
||||
},
|
||||
|
@ -59,7 +60,7 @@ pub async fn send_event_to_device_route(
|
|||
sender_user,
|
||||
target_user_id,
|
||||
target_device_id,
|
||||
&body.event_type.to_string(),
|
||||
&body.event_type,
|
||||
event.deserialize_as().map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid")
|
||||
})?,
|
||||
|
@ -72,7 +73,7 @@ pub async fn send_event_to_device_route(
|
|||
sender_user,
|
||||
target_user_id,
|
||||
&target_device_id?,
|
||||
&body.event_type.to_string(),
|
||||
&body.event_type,
|
||||
event.deserialize_as().map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid")
|
||||
})?,
|
||||
|
|
|
@ -5,7 +5,7 @@ use ruma::api::client::{error::ErrorKind, typing::create_typing_event};
|
|||
///
|
||||
/// Sets the typing state of the sender user.
|
||||
pub async fn create_typing_event_route(
|
||||
body: Ruma<create_typing_event::v3::Request>,
|
||||
body: Ruma<create_typing_event::v3::IncomingRequest>,
|
||||
) -> Result<create_typing_event::v3::Response> {
|
||||
use create_typing_event::v3::Typing;
|
||||
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use std::{collections::BTreeMap, iter::FromIterator};
|
||||
|
||||
use axum::{response::IntoResponse, Json};
|
||||
use ruma::api::client::{discovery::get_supported_versions, error::ErrorKind};
|
||||
use ruma::api::client::discovery::get_supported_versions;
|
||||
|
||||
use crate::{services, Error, Result, Ruma};
|
||||
use crate::{Result, Ruma};
|
||||
|
||||
/// # `GET /_matrix/client/versions`
|
||||
///
|
||||
|
@ -16,7 +15,7 @@ use crate::{services, Error, Result, Ruma};
|
|||
/// Note: Unstable features are used while developing new features. Clients should avoid using
|
||||
/// unstable features in their stable releases
|
||||
pub async fn get_supported_versions_route(
|
||||
_body: Ruma<get_supported_versions::Request>,
|
||||
_body: Ruma<get_supported_versions::IncomingRequest>,
|
||||
) -> Result<get_supported_versions::Response> {
|
||||
let resp = get_supported_versions::Response {
|
||||
versions: vec![
|
||||
|
@ -24,27 +23,12 @@ 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)]),
|
||||
unstable_features: BTreeMap::from_iter([
|
||||
("org.matrix.e2e_cross_signing".to_owned(), true),
|
||||
("org.matrix.msc3827.stable".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}
|
||||
})))
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ use ruma::{
|
|||
/// - Hides any local users that aren't in any public rooms (i.e. those that have the join rule set to public)
|
||||
/// and don't share a room with the sender
|
||||
pub async fn search_users_route(
|
||||
body: Ruma<search_users::v3::Request>,
|
||||
body: Ruma<search_users::v3::IncomingRequest>,
|
||||
) -> Result<search_users::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let limit = u64::from(body.limit) as usize;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
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;
|
||||
|
@ -11,7 +10,7 @@ type HmacSha1 = Hmac<Sha1>;
|
|||
///
|
||||
/// TODO: Returns information about the recommended turn server.
|
||||
pub async fn turn_server_route(
|
||||
body: Ruma<get_turn_server_info::v3::Request>,
|
||||
body: Ruma<get_turn_server_info::v3::IncomingRequest>,
|
||||
) -> Result<get_turn_server_info::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
@ -29,7 +28,7 @@ pub async fn turn_server_route(
|
|||
.expect("HMAC can take key of any size");
|
||||
mac.update(username.as_bytes());
|
||||
|
||||
let password: String = general_purpose::STANDARD.encode(mac.finalize().into_bytes());
|
||||
let password: String = base64::encode_config(mac.finalize().into_bytes(), base64::STANDARD);
|
||||
|
||||
(username, password)
|
||||
} else {
|
||||
|
|
|
@ -3,16 +3,18 @@ use std::{collections::BTreeMap, iter::FromIterator, str};
|
|||
use axum::{
|
||||
async_trait,
|
||||
body::{Full, HttpBody},
|
||||
extract::{rejection::TypedHeaderRejectionReason, FromRequest, Path, TypedHeader},
|
||||
extract::{
|
||||
rejection::TypedHeaderRejectionReason, FromRequest, Path, RequestParts, TypedHeader,
|
||||
},
|
||||
headers::{
|
||||
authorization::{Bearer, Credentials},
|
||||
Authorization,
|
||||
},
|
||||
response::{IntoResponse, Response},
|
||||
BoxError, RequestExt, RequestPartsExt,
|
||||
BoxError,
|
||||
};
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
use http::{Request, StatusCode};
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use http::StatusCode;
|
||||
use ruma::{
|
||||
api::{client::error::ErrorKind, AuthScheme, IncomingRequest, OutgoingResponse},
|
||||
CanonicalJsonValue, OwnedDeviceId, OwnedServerName, UserId,
|
||||
|
@ -24,45 +26,28 @@ use super::{Ruma, RumaResponse};
|
|||
use crate::{services, Error, Result};
|
||||
|
||||
#[async_trait]
|
||||
impl<T, S, B> FromRequest<S, B> for Ruma<T>
|
||||
impl<T, B> FromRequest<B> for Ruma<T>
|
||||
where
|
||||
T: IncomingRequest,
|
||||
B: HttpBody + Send + 'static,
|
||||
B: HttpBody + Send,
|
||||
B::Data: Send,
|
||||
B::Error: Into<BoxError>,
|
||||
{
|
||||
type Rejection = Error;
|
||||
|
||||
async fn from_request(req: Request<B>, _state: &S) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
#[derive(Deserialize)]
|
||||
struct QueryParams {
|
||||
access_token: Option<String>,
|
||||
user_id: Option<String>,
|
||||
}
|
||||
|
||||
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 metadata = T::METADATA;
|
||||
let auth_header: Option<TypedHeader<Authorization<Bearer>>> = parts.extract().await?;
|
||||
let path_params: Path<Vec<String>> = parts.extract().await?;
|
||||
let auth_header = Option::<TypedHeader<Authorization<Bearer>>>::from_request(req).await?;
|
||||
let path_params = Path::<Vec<String>>::from_request(req).await?;
|
||||
|
||||
let query = parts.uri.query().unwrap_or_default();
|
||||
let query_params: QueryParams = match serde_html_form::from_str(query) {
|
||||
let query = req.uri().query().unwrap_or_default();
|
||||
let query_params: QueryParams = match ruma::serde::urlencoded::from_str(query) {
|
||||
Ok(params) => params,
|
||||
Err(e) => {
|
||||
error!(%query, "Failed to deserialize query parameters: {}", e);
|
||||
|
@ -78,6 +63,10 @@ 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 mut json_body = serde_json::from_slice::<CanonicalJsonValue>(&body).ok();
|
||||
|
||||
let appservices = services().appservice.all().unwrap();
|
||||
|
@ -149,24 +138,24 @@ where
|
|||
}
|
||||
}
|
||||
AuthScheme::ServerSignatures => {
|
||||
let TypedHeader(Authorization(x_matrix)) = parts
|
||||
.extract::<TypedHeader<Authorization<XMatrix>>>()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Missing or invalid Authorization header: {}", e);
|
||||
let TypedHeader(Authorization(x_matrix)) =
|
||||
TypedHeader::<Authorization<XMatrix>>::from_request(req)
|
||||
.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",
|
||||
};
|
||||
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)
|
||||
})?;
|
||||
Error::BadRequest(ErrorKind::Forbidden, msg)
|
||||
})?;
|
||||
|
||||
let origin_signatures = BTreeMap::from_iter([(
|
||||
x_matrix.key.clone(),
|
||||
|
@ -181,11 +170,11 @@ where
|
|||
let mut request_map = BTreeMap::from_iter([
|
||||
(
|
||||
"method".to_owned(),
|
||||
CanonicalJsonValue::String(parts.method.to_string()),
|
||||
CanonicalJsonValue::String(req.method().to_string()),
|
||||
),
|
||||
(
|
||||
"uri".to_owned(),
|
||||
CanonicalJsonValue::String(parts.uri.to_string()),
|
||||
CanonicalJsonValue::String(req.uri().to_string()),
|
||||
),
|
||||
(
|
||||
"origin".to_owned(),
|
||||
|
@ -235,7 +224,7 @@ where
|
|||
x_matrix.origin, e, request_map
|
||||
);
|
||||
|
||||
if parts.uri.to_string().contains('@') {
|
||||
if req.uri().to_string().contains('@') {
|
||||
warn!(
|
||||
"Request uri contained '@' character. Make sure your \
|
||||
reverse proxy gives Conduit the raw uri (apache: use \
|
||||
|
@ -254,8 +243,8 @@ where
|
|||
}
|
||||
};
|
||||
|
||||
let mut http_request = http::Request::builder().uri(parts.uri).method(parts.method);
|
||||
*http_request.headers_mut().unwrap() = parts.headers;
|
||||
let mut http_request = http::Request::builder().uri(req.uri()).method(req.method());
|
||||
*http_request.headers_mut().unwrap() = req.headers().clone();
|
||||
|
||||
if let Some(CanonicalJsonValue::Object(json_body)) = &mut json_body {
|
||||
let user_id = sender_user.clone().unwrap_or_else(|| {
|
||||
|
@ -292,8 +281,7 @@ where
|
|||
debug!("{:?}", http_request);
|
||||
|
||||
let body = T::try_from_http_request(http_request, &path_params).map_err(|e| {
|
||||
warn!("try_from_http_request failed: {:?}", e);
|
||||
debug!("JSON body: {:?}", json_body);
|
||||
warn!("{:?}\n{:?}", e, json_body);
|
||||
Error::BadRequest(ErrorKind::BadJson, "Failed to deserialize request.")
|
||||
})?;
|
||||
|
||||
|
@ -320,7 +308,8 @@ impl Credentials for XMatrix {
|
|||
fn decode(value: &http::HeaderValue) -> Option<Self> {
|
||||
debug_assert!(
|
||||
value.as_bytes().starts_with(b"X-Matrix "),
|
||||
"HeaderValue to decode should start with \"X-Matrix ..\", received = {value:?}",
|
||||
"HeaderValue to decode should start with \"X-Matrix ..\", received = {:?}",
|
||||
value,
|
||||
);
|
||||
|
||||
let parameters = str::from_utf8(&value.as_bytes()["X-Matrix ".len()..])
|
||||
|
@ -373,55 +362,3 @@ 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,5 +1,3 @@
|
|||
#![allow(deprecated)]
|
||||
|
||||
use crate::{
|
||||
api::client_server::{self, claim_keys_helper, get_keys_helper},
|
||||
service::pdu::{gen_event_id_canonical_json, PduBuilder},
|
||||
|
@ -14,13 +12,16 @@ use ruma::{
|
|||
client::error::{Error as RumaError, ErrorKind},
|
||||
federation::{
|
||||
authorization::get_event_authorization,
|
||||
backfill::get_backfill,
|
||||
device::get_devices::{self, v1::UserDevice},
|
||||
directory::{get_public_rooms, get_public_rooms_filtered},
|
||||
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, prepare_join_event},
|
||||
membership::{
|
||||
create_invite,
|
||||
create_join_event::{self, RoomState},
|
||||
prepare_join_event,
|
||||
},
|
||||
query::{get_profile_information, get_room_information},
|
||||
transactions::{
|
||||
edu::{DeviceListUpdateContent, DirectDeviceContent, Edu, SigningKeyUpdateContent},
|
||||
|
@ -30,20 +31,19 @@ use ruma::{
|
|||
EndpointError, IncomingResponse, MatrixVersion, OutgoingRequest, OutgoingResponse,
|
||||
SendAccessToken,
|
||||
},
|
||||
directory::{Filter, RoomNetwork},
|
||||
directory::{IncomingFilter, IncomingRoomNetwork},
|
||||
events::{
|
||||
receipt::{ReceiptEvent, ReceiptEventContent, ReceiptType},
|
||||
room::{
|
||||
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
StateEventType, TimelineEventType,
|
||||
RoomEventType, StateEventType,
|
||||
},
|
||||
serde::{Base64, JsonObject, Raw},
|
||||
to_device::DeviceIdOrAllDevices,
|
||||
uint, user_id, CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch,
|
||||
OwnedEventId, OwnedRoomId, OwnedServerName, OwnedServerSigningKeyId, OwnedUserId, RoomId,
|
||||
ServerName,
|
||||
CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId,
|
||||
OwnedServerName, OwnedServerSigningKeyId, OwnedUserId, RoomId, ServerName,
|
||||
};
|
||||
use serde_json::value::{to_raw_value, RawValue as RawJsonValue};
|
||||
use std::{
|
||||
|
@ -55,7 +55,7 @@ use std::{
|
|||
time::{Duration, Instant, SystemTime},
|
||||
};
|
||||
|
||||
use tracing::{debug, error, warn};
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
/// Wraps either an literal IP address plus port, or a hostname plus complement
|
||||
/// (colon-plus-port if it was specified).
|
||||
|
@ -84,8 +84,8 @@ pub enum FedDest {
|
|||
impl FedDest {
|
||||
fn into_https_string(self) -> String {
|
||||
match self {
|
||||
Self::Literal(addr) => format!("https://{addr}"),
|
||||
Self::Named(host, port) => format!("https://{host}{port}"),
|
||||
Self::Literal(addr) => format!("https://{}", addr),
|
||||
Self::Named(host, port) => format!("https://{}{}", host, port),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,14 +123,6 @@ 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;
|
||||
|
||||
let cached_result = services()
|
||||
|
@ -157,7 +149,7 @@ where
|
|||
.try_into_http_request::<Vec<u8>>(
|
||||
&actual_destination_str,
|
||||
SendAccessToken::IfRequired(""),
|
||||
&[MatrixVersion::V1_4],
|
||||
&[MatrixVersion::V1_0],
|
||||
)
|
||||
.map_err(|e| {
|
||||
warn!(
|
||||
|
@ -237,13 +229,11 @@ where
|
|||
|
||||
let url = reqwest_request.url().clone();
|
||||
|
||||
debug!("Sending request to {destination} at {url}");
|
||||
let response = services()
|
||||
.globals
|
||||
.federation_client()
|
||||
.execute(reqwest_request)
|
||||
.await;
|
||||
debug!("Received response from {destination} at {url}");
|
||||
|
||||
match response {
|
||||
Ok(mut response) => {
|
||||
|
@ -259,12 +249,10 @@ where
|
|||
.expect("http::response::Builder is usable"),
|
||||
);
|
||||
|
||||
debug!("Getting response bytes from {destination}");
|
||||
let body = response.bytes().await.unwrap_or_else(|e| {
|
||||
warn!("server error {}", e);
|
||||
Vec::new().into()
|
||||
}); // TODO: handle timeout
|
||||
debug!("Got response bytes from {destination}");
|
||||
|
||||
if status != 200 {
|
||||
warn!(
|
||||
|
@ -283,7 +271,6 @@ where
|
|||
.expect("reqwest body is valid http body");
|
||||
|
||||
if status == 200 {
|
||||
debug!("Parsing response bytes from {destination}");
|
||||
let response = T::IncomingResponse::try_from_http_response(http_response);
|
||||
if response.is_ok() && write_destination_to_cache {
|
||||
services()
|
||||
|
@ -305,10 +292,15 @@ where
|
|||
Error::BadServerResponse("Server returned bad 200 response.")
|
||||
})
|
||||
} else {
|
||||
debug!("Returning error from {destination}");
|
||||
Err(Error::FederationError(
|
||||
destination.to_owned(),
|
||||
RumaError::from_http_response(http_response),
|
||||
RumaError::try_from_http_response(http_response).map_err(|e| {
|
||||
warn!(
|
||||
"Invalid {} response from {} on: {} {}",
|
||||
status, &destination, url, e
|
||||
);
|
||||
Error::BadServerResponse("Server returned bad error response.")
|
||||
})?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -341,41 +333,39 @@ 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}");
|
||||
let destination_str = destination.as_str().to_owned();
|
||||
let mut hostname = destination_str.clone();
|
||||
let actual_destination = match get_ip_with_port(&destination_str) {
|
||||
Some(host_port) => {
|
||||
debug!("1: IP literal with provided or default port");
|
||||
// 1: IP literal with provided or default port
|
||||
host_port
|
||||
}
|
||||
None => {
|
||||
if let Some(pos) = destination_str.find(':') {
|
||||
debug!("2: Hostname with included port");
|
||||
// 2: Hostname with included port
|
||||
let (host, port) = destination_str.split_at(pos);
|
||||
FedDest::Named(host.to_owned(), port.to_owned())
|
||||
} else {
|
||||
debug!("Requesting well known for {destination}");
|
||||
match request_well_known(destination.as_str()).await {
|
||||
// 3: A .well-known file is available
|
||||
Some(delegated_hostname) => {
|
||||
debug!("3: A .well-known file is available");
|
||||
hostname = add_port_to_hostname(&delegated_hostname).into_uri_string();
|
||||
match get_ip_with_port(&delegated_hostname) {
|
||||
Some(host_and_port) => host_and_port, // 3.1: IP literal in .well-known file
|
||||
None => {
|
||||
if let Some(pos) = delegated_hostname.find(':') {
|
||||
debug!("3.2: Hostname with port in .well-known file");
|
||||
// 3.2: Hostname with port in .well-known file
|
||||
let (host, port) = delegated_hostname.split_at(pos);
|
||||
FedDest::Named(host.to_owned(), port.to_owned())
|
||||
} else {
|
||||
debug!("Delegated hostname has no port in this branch");
|
||||
// Delegated hostname has no port in this branch
|
||||
if let Some(hostname_override) =
|
||||
query_srv_record(&delegated_hostname).await
|
||||
{
|
||||
debug!("3.3: SRV lookup successful");
|
||||
// 3.3: SRV lookup successful
|
||||
let force_port = hostname_override.port();
|
||||
|
||||
if let Ok(override_ip) = services()
|
||||
|
@ -401,23 +391,23 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
|
|||
}
|
||||
|
||||
if let Some(port) = force_port {
|
||||
FedDest::Named(delegated_hostname, format!(":{port}"))
|
||||
FedDest::Named(delegated_hostname, format!(":{}", port))
|
||||
} else {
|
||||
add_port_to_hostname(&delegated_hostname)
|
||||
}
|
||||
} else {
|
||||
debug!("3.4: No SRV records, just use the hostname from .well-known");
|
||||
// 3.4: No SRV records, just use the hostname from .well-known
|
||||
add_port_to_hostname(&delegated_hostname)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 4: No .well-known or an error occured
|
||||
None => {
|
||||
debug!("4: No .well-known or an error occured");
|
||||
match query_srv_record(&destination_str).await {
|
||||
// 4: SRV record found
|
||||
Some(hostname_override) => {
|
||||
debug!("4: SRV record found");
|
||||
let force_port = hostname_override.port();
|
||||
|
||||
if let Ok(override_ip) = services()
|
||||
|
@ -443,22 +433,19 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
|
|||
}
|
||||
|
||||
if let Some(port) = force_port {
|
||||
FedDest::Named(hostname.clone(), format!(":{port}"))
|
||||
FedDest::Named(hostname.clone(), format!(":{}", port))
|
||||
} else {
|
||||
add_port_to_hostname(&hostname)
|
||||
}
|
||||
}
|
||||
None => {
|
||||
debug!("5: No SRV record found");
|
||||
add_port_to_hostname(&destination_str)
|
||||
}
|
||||
// 5: No SRV record found
|
||||
None => add_port_to_hostname(&destination_str),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
debug!("Actual destination: {actual_destination:?}");
|
||||
|
||||
// Can't use get_ip_with_port here because we don't want to add a port
|
||||
// to an IP address if it wasn't specified
|
||||
|
@ -476,11 +463,10 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
|
|||
}
|
||||
|
||||
async fn query_srv_record(hostname: &'_ str) -> Option<FedDest> {
|
||||
let hostname = hostname.trim_end_matches('.');
|
||||
if let Ok(Some(host_port)) = services()
|
||||
.globals
|
||||
.dns_resolver()
|
||||
.srv_lookup(format!("_matrix._tcp.{hostname}."))
|
||||
.srv_lookup(format!("_matrix._tcp.{}", hostname))
|
||||
.await
|
||||
.map(|srv| {
|
||||
srv.iter().next().map(|result| {
|
||||
|
@ -498,20 +484,22 @@ async fn query_srv_record(hostname: &'_ str) -> Option<FedDest> {
|
|||
}
|
||||
|
||||
async fn request_well_known(destination: &str) -> Option<String> {
|
||||
let response = services()
|
||||
.globals
|
||||
.default_client()
|
||||
.get(&format!("https://{destination}/.well-known/matrix/server"))
|
||||
.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()?;
|
||||
let body: serde_json::Value = serde_json::from_str(
|
||||
&services()
|
||||
.globals
|
||||
.default_client()
|
||||
.get(&format!(
|
||||
"https://{}/.well-known/matrix/server",
|
||||
destination
|
||||
))
|
||||
.send()
|
||||
.await
|
||||
.ok()?
|
||||
.text()
|
||||
.await
|
||||
.ok()?,
|
||||
)
|
||||
.ok()?;
|
||||
Some(body.get("m.server")?.as_str()?.to_owned())
|
||||
}
|
||||
|
||||
|
@ -598,7 +586,7 @@ pub async fn get_server_keys_deprecated_route() -> impl IntoResponse {
|
|||
///
|
||||
/// Lists the public rooms on this server.
|
||||
pub async fn get_public_rooms_filtered_route(
|
||||
body: Ruma<get_public_rooms_filtered::v1::Request>,
|
||||
body: Ruma<get_public_rooms_filtered::v1::IncomingRequest>,
|
||||
) -> Result<get_public_rooms_filtered::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
|
@ -625,7 +613,7 @@ pub async fn get_public_rooms_filtered_route(
|
|||
///
|
||||
/// Lists the public rooms on this server.
|
||||
pub async fn get_public_rooms_route(
|
||||
body: Ruma<get_public_rooms::v1::Request>,
|
||||
body: Ruma<get_public_rooms::v1::IncomingRequest>,
|
||||
) -> Result<get_public_rooms::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
|
@ -635,8 +623,8 @@ pub async fn get_public_rooms_route(
|
|||
None,
|
||||
body.limit,
|
||||
body.since.as_deref(),
|
||||
&Filter::default(),
|
||||
&RoomNetwork::Matrix,
|
||||
&IncomingFilter::default(),
|
||||
&IncomingRoomNetwork::Matrix,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
@ -648,42 +636,11 @@ pub async fn get_public_rooms_route(
|
|||
})
|
||||
}
|
||||
|
||||
pub fn parse_incoming_pdu(
|
||||
pdu: &RawJsonValue,
|
||||
) -> Result<(OwnedEventId, CanonicalJsonObject, OwnedRoomId)> {
|
||||
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",
|
||||
))?;
|
||||
|
||||
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) {
|
||||
Ok(t) => t,
|
||||
Err(_) => {
|
||||
// Event could not be converted to canonical json
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Could not convert event to canonical json.",
|
||||
));
|
||||
}
|
||||
};
|
||||
Ok((event_id, value, room_id))
|
||||
}
|
||||
|
||||
/// # `PUT /_matrix/federation/v1/send/{txnId}`
|
||||
///
|
||||
/// Push EDUs and PDUs to this server.
|
||||
pub async fn send_transaction_message_route(
|
||||
body: Ruma<send_transaction_message::v1::Request>,
|
||||
body: Ruma<send_transaction_message::v1::IncomingRequest>,
|
||||
) -> Result<send_transaction_message::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
|
@ -707,33 +664,35 @@ pub async fn send_transaction_message_route(
|
|||
// let mut auth_cache = EventMap::new();
|
||||
|
||||
for pdu in &body.pdus {
|
||||
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 {
|
||||
// We do not add the event_id field to the pdu here because of signature and hashes checks
|
||||
let (event_id, value) = match gen_event_id_canonical_json(pdu) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
warn!("Could not parse PDU: {e}");
|
||||
warn!("Full PDU: {:?}", &pdu);
|
||||
Err(_) => {
|
||||
// Event could not be converted to canonical json
|
||||
continue;
|
||||
}
|
||||
};
|
||||
// We do not add the event_id field to the pdu here because of signature and hashes checks
|
||||
|
||||
// 0. Check the server is in the room
|
||||
let room_id: OwnedRoomId = match value
|
||||
.get("room_id")
|
||||
.and_then(|id| RoomId::parse(id.as_str()?).ok())
|
||||
{
|
||||
Some(id) => id,
|
||||
None => {
|
||||
// Event is invalid
|
||||
resolved_map.insert(
|
||||
event_id,
|
||||
Err(Error::bad_database("Event needs a valid RoomId.")),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
services()
|
||||
.rooms
|
||||
.event_handler
|
||||
.acl_check(sender_servername, &room_id)?;
|
||||
|
||||
let mutex = Arc::clone(
|
||||
services()
|
||||
|
@ -825,7 +784,7 @@ pub async fn send_transaction_message_route(
|
|||
.readreceipt_update(&user_id, &room_id, event)?;
|
||||
} else {
|
||||
// TODO fetch missing events
|
||||
debug!("No known event ids in read receipt: {:?}", user_updates);
|
||||
info!("No known event ids in read receipt: {:?}", user_updates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -929,7 +888,6 @@ pub async fn send_transaction_message_route(
|
|||
&master_key,
|
||||
&self_signing_key,
|
||||
&None,
|
||||
true,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
@ -940,7 +898,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.sanitized_error())))
|
||||
.map(|(e, r)| (e, r.map_err(|e| e.to_string())))
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
@ -951,7 +909,7 @@ pub async fn send_transaction_message_route(
|
|||
///
|
||||
/// - Only works if a user of this server is currently invited or joined the room
|
||||
pub async fn get_event_route(
|
||||
body: Ruma<get_event::v1::Request>,
|
||||
body: Ruma<get_event::v1::IncomingRequest>,
|
||||
) -> Result<get_event::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
|
@ -966,10 +924,7 @@ pub async fn get_event_route(
|
|||
.rooms
|
||||
.timeline
|
||||
.get_pdu_json(&body.event_id)?
|
||||
.ok_or_else(|| {
|
||||
warn!("Event not found, event ID: {:?}", &body.event_id);
|
||||
Error::BadRequest(ErrorKind::NotFound, "Event not found.")
|
||||
})?;
|
||||
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Event not found."))?;
|
||||
|
||||
let room_id_str = event
|
||||
.get("room_id")
|
||||
|
@ -990,17 +945,6 @@ pub async fn get_event_route(
|
|||
));
|
||||
}
|
||||
|
||||
if !services().rooms.state_accessor.server_can_see_event(
|
||||
sender_servername,
|
||||
room_id,
|
||||
&body.event_id,
|
||||
)? {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Server is not allowed to see event.",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(get_event::v1::Response {
|
||||
origin: services().globals.server_name().to_owned(),
|
||||
origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
|
||||
|
@ -1008,88 +952,11 @@ pub async fn get_event_route(
|
|||
})
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/federation/v1/backfill/<room_id>`
|
||||
///
|
||||
/// Retrieves events from before the sender joined the room, if the room's
|
||||
/// history visibility allows.
|
||||
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");
|
||||
|
||||
debug!("Got backfill request from: {}", sender_servername);
|
||||
|
||||
if !services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(sender_servername, &body.room_id)?
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Server is not in room.",
|
||||
));
|
||||
}
|
||||
|
||||
services()
|
||||
.rooms
|
||||
.event_handler
|
||||
.acl_check(sender_servername, &body.room_id)?;
|
||||
|
||||
let until = body
|
||||
.v
|
||||
.iter()
|
||||
.map(|eventid| services().rooms.timeline.get_pdu_count(eventid))
|
||||
.filter_map(|r| r.ok().flatten())
|
||||
.max()
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"No known eventid in v",
|
||||
))?;
|
||||
|
||||
let limit = body.limit.min(uint!(100));
|
||||
|
||||
let all_events = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_until(user_id!("@doesntmatter:conduit.rs"), &body.room_id, until)?
|
||||
.take(limit.try_into().unwrap());
|
||||
|
||||
let events = all_events
|
||||
.filter_map(|r| r.ok())
|
||||
.filter(|(_, e)| {
|
||||
matches!(
|
||||
services().rooms.state_accessor.server_can_see_event(
|
||||
sender_servername,
|
||||
&e.room_id,
|
||||
&e.event_id,
|
||||
),
|
||||
Ok(true),
|
||||
)
|
||||
})
|
||||
.map(|(_, pdu)| services().rooms.timeline.get_pdu_json(&pdu.event_id))
|
||||
.filter_map(|r| r.ok().flatten())
|
||||
.map(PduEvent::convert_to_outgoing_federation_event)
|
||||
.collect();
|
||||
|
||||
Ok(get_backfill::v1::Response {
|
||||
origin: services().globals.server_name().to_owned(),
|
||||
origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
|
||||
pdus: events,
|
||||
})
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/federation/v1/get_missing_events/{roomId}`
|
||||
///
|
||||
/// Retrieves events that the sender is missing.
|
||||
pub async fn get_missing_events_route(
|
||||
body: Ruma<get_missing_events::v1::Request>,
|
||||
body: Ruma<get_missing_events::v1::IncomingRequest>,
|
||||
) -> Result<get_missing_events::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
|
@ -1145,16 +1012,6 @@ pub async fn get_missing_events_route(
|
|||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if !services().rooms.state_accessor.server_can_see_event(
|
||||
sender_servername,
|
||||
&body.room_id,
|
||||
&queued_events[i],
|
||||
)? {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
queued_events.extend_from_slice(
|
||||
&serde_json::from_value::<Vec<OwnedEventId>>(
|
||||
serde_json::to_value(pdu.get("prev_events").cloned().ok_or_else(|| {
|
||||
|
@ -1178,7 +1035,7 @@ pub async fn get_missing_events_route(
|
|||
///
|
||||
/// - This does not include the event itself
|
||||
pub async fn get_event_authorization_route(
|
||||
body: Ruma<get_event_authorization::v1::Request>,
|
||||
body: Ruma<get_event_authorization::v1::IncomingRequest>,
|
||||
) -> Result<get_event_authorization::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
|
@ -1209,10 +1066,7 @@ pub async fn get_event_authorization_route(
|
|||
.rooms
|
||||
.timeline
|
||||
.get_pdu_json(&body.event_id)?
|
||||
.ok_or_else(|| {
|
||||
warn!("Event not found, event ID: {:?}", &body.event_id);
|
||||
Error::BadRequest(ErrorKind::NotFound, "Event not found.")
|
||||
})?;
|
||||
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Event not found."))?;
|
||||
|
||||
let room_id_str = event
|
||||
.get("room_id")
|
||||
|
@ -1240,7 +1094,7 @@ pub async fn get_event_authorization_route(
|
|||
///
|
||||
/// Retrieves the current state of the room.
|
||||
pub async fn get_room_state_route(
|
||||
body: Ruma<get_room_state::v1::Request>,
|
||||
body: Ruma<get_room_state::v1::IncomingRequest>,
|
||||
) -> Result<get_room_state::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
|
@ -1320,7 +1174,7 @@ pub async fn get_room_state_route(
|
|||
///
|
||||
/// Retrieves the current state of the room.
|
||||
pub async fn get_room_state_ids_route(
|
||||
body: Ruma<get_room_state_ids::v1::Request>,
|
||||
body: Ruma<get_room_state_ids::v1::IncomingRequest>,
|
||||
) -> Result<get_room_state_ids::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
|
@ -1381,7 +1235,7 @@ pub async fn get_room_state_ids_route(
|
|||
///
|
||||
/// Creates a join template.
|
||||
pub async fn create_join_event_template_route(
|
||||
body: Ruma<prepare_join_event::v1::Request>,
|
||||
body: Ruma<prepare_join_event::v1::IncomingRequest>,
|
||||
) -> Result<prepare_join_event::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
|
@ -1468,7 +1322,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: TimelineEventType::RoomMember,
|
||||
event_type: RoomEventType::RoomMember,
|
||||
content,
|
||||
unsigned: None,
|
||||
state_key: Some(body.user_id.to_string()),
|
||||
|
@ -1493,7 +1347,7 @@ async fn create_join_event(
|
|||
sender_servername: &ServerName,
|
||||
room_id: &RoomId,
|
||||
pdu: &RawJsonValue,
|
||||
) -> Result<create_join_event::v1::RoomState> {
|
||||
) -> Result<RoomState> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
@ -1553,8 +1407,7 @@ async fn create_join_event(
|
|||
// let mut auth_cache = EventMap::new();
|
||||
|
||||
// We do not add the event_id field to the pdu here because of signature and hashes checks
|
||||
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) {
|
||||
Ok(t) => t,
|
||||
Err(_) => {
|
||||
// Event could not be converted to canonical json
|
||||
|
@ -1615,7 +1468,7 @@ async fn create_join_event(
|
|||
|
||||
services().sending.send_pdu(servers, &pdu_id)?;
|
||||
|
||||
Ok(create_join_event::v1::RoomState {
|
||||
Ok(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)
|
||||
|
@ -1633,7 +1486,7 @@ async fn create_join_event(
|
|||
///
|
||||
/// Submits a signed join event.
|
||||
pub async fn create_join_event_v1_route(
|
||||
body: Ruma<create_join_event::v1::Request>,
|
||||
body: Ruma<create_join_event::v1::IncomingRequest>,
|
||||
) -> Result<create_join_event::v1::Response> {
|
||||
let sender_servername = body
|
||||
.sender_servername
|
||||
|
@ -1649,25 +1502,14 @@ pub async fn create_join_event_v1_route(
|
|||
///
|
||||
/// Submits a signed join event.
|
||||
pub async fn create_join_event_v2_route(
|
||||
body: Ruma<create_join_event::v2::Request>,
|
||||
body: Ruma<create_join_event::v2::IncomingRequest>,
|
||||
) -> Result<create_join_event::v2::Response> {
|
||||
let sender_servername = body
|
||||
.sender_servername
|
||||
.as_ref()
|
||||
.expect("server is authenticated");
|
||||
|
||||
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,
|
||||
};
|
||||
let room_state = create_join_event(sender_servername, &body.room_id, &body.pdu).await?;
|
||||
|
||||
Ok(create_join_event::v2::Response { room_state })
|
||||
}
|
||||
|
@ -1676,7 +1518,7 @@ pub async fn create_join_event_v2_route(
|
|||
///
|
||||
/// Invites a remote user to a room.
|
||||
pub async fn create_invite_route(
|
||||
body: Ruma<create_invite::v2::Request>,
|
||||
body: Ruma<create_invite::v2::IncomingRequest>,
|
||||
) -> Result<create_invite::v2::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
|
@ -1768,12 +1610,8 @@ pub async fn create_invite_route(
|
|||
|
||||
invite_state.push(pdu.to_stripped_state_event());
|
||||
|
||||
// If we are active in the room, the remote server will notify us about the join via /send
|
||||
if !services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(services().globals.server_name(), &body.room_id)?
|
||||
{
|
||||
// If the room already exists, the remote server will notify us about the join via /send
|
||||
if !services().rooms.metadata.exists(&pdu.room_id)? {
|
||||
services().rooms.state_cache.update_membership(
|
||||
&body.room_id,
|
||||
&invited_user,
|
||||
|
@ -1793,19 +1631,12 @@ pub async fn create_invite_route(
|
|||
///
|
||||
/// Gets information on all devices of the user.
|
||||
pub async fn get_devices_route(
|
||||
body: Ruma<get_devices::v1::Request>,
|
||||
body: Ruma<get_devices::v1::IncomingRequest>,
|
||||
) -> 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
|
||||
.sender_servername
|
||||
.as_ref()
|
||||
|
@ -1834,14 +1665,12 @@ pub async fn get_devices_route(
|
|||
})
|
||||
})
|
||||
.collect(),
|
||||
master_key: services().users.get_master_key(None, &body.user_id, &|u| {
|
||||
u.server_name() == sender_servername
|
||||
})?,
|
||||
master_key: services()
|
||||
.users
|
||||
.get_master_key(&body.user_id, &|u| u.server_name() == sender_servername)?,
|
||||
self_signing_key: services()
|
||||
.users
|
||||
.get_self_signing_key(None, &body.user_id, &|u| {
|
||||
u.server_name() == sender_servername
|
||||
})?,
|
||||
.get_self_signing_key(&body.user_id, &|u| u.server_name() == sender_servername)?,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1849,7 +1678,7 @@ pub async fn get_devices_route(
|
|||
///
|
||||
/// Resolve a room alias to a room id.
|
||||
pub async fn get_room_information_route(
|
||||
body: Ruma<get_room_information::v1::Request>,
|
||||
body: Ruma<get_room_information::v1::IncomingRequest>,
|
||||
) -> Result<get_room_information::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
|
@ -1874,19 +1703,12 @@ pub async fn get_room_information_route(
|
|||
///
|
||||
/// Gets information on a profile.
|
||||
pub async fn get_profile_information_route(
|
||||
body: Ruma<get_profile_information::v1::Request>,
|
||||
body: Ruma<get_profile_information::v1::IncomingRequest>,
|
||||
) -> 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;
|
||||
let mut avatar_url = None;
|
||||
let mut blurhash = None;
|
||||
|
@ -1923,17 +1745,6 @@ pub async fn get_keys_route(body: Ruma<get_keys::v1::Request>) -> Result<get_key
|
|||
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| {
|
||||
Some(u.server_name()) == body.sender_servername.as_deref()
|
||||
})
|
||||
|
@ -1956,17 +1767,6 @@ pub async fn claim_keys_route(
|
|||
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?;
|
||||
|
||||
Ok(claim_keys::v1::Response {
|
||||
|
|
|
@ -28,8 +28,6 @@ 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")]
|
||||
|
@ -42,11 +40,8 @@ pub struct Config {
|
|||
pub max_request_size: u32,
|
||||
#[serde(default = "default_max_concurrent_requests")]
|
||||
pub max_concurrent_requests: u16,
|
||||
#[serde(default = "default_max_fetch_prev_events")]
|
||||
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")]
|
||||
|
@ -57,7 +52,6 @@ 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")]
|
||||
|
@ -65,7 +59,7 @@ pub struct Config {
|
|||
#[serde(default)]
|
||||
pub proxy: ProxyConfig,
|
||||
pub jwt_secret: Option<String>,
|
||||
#[serde(default = "default_trusted_servers")]
|
||||
#[serde(default = "Vec::new")]
|
||||
pub trusted_servers: Vec<OwnedServerName>,
|
||||
#[serde(default = "default_log")]
|
||||
pub log: String,
|
||||
|
@ -203,7 +197,7 @@ impl fmt::Display for Config {
|
|||
msg += &format!("{}: {}\n", line.1 .0, line.1 .1);
|
||||
}
|
||||
|
||||
write!(f, "{msg}")
|
||||
write!(f, "{}", msg)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -228,7 +222,7 @@ fn default_database_backend() -> String {
|
|||
}
|
||||
|
||||
fn default_db_cache_capacity_mb() -> f64 {
|
||||
300.0
|
||||
10.0
|
||||
}
|
||||
|
||||
fn default_conduit_cache_capacity_modifier() -> f64 {
|
||||
|
@ -236,7 +230,7 @@ fn default_conduit_cache_capacity_modifier() -> f64 {
|
|||
}
|
||||
|
||||
fn default_rocksdb_max_open_files() -> i32 {
|
||||
1000
|
||||
20
|
||||
}
|
||||
|
||||
fn default_pdu_cache_capacity() -> u32 {
|
||||
|
@ -255,14 +249,6 @@ fn default_max_concurrent_requests() -> u16 {
|
|||
100
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
|
|
@ -29,9 +29,7 @@ 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")]
|
||||
|
@ -50,6 +48,11 @@ impl ProxyConfig {
|
|||
})
|
||||
}
|
||||
}
|
||||
impl Default for ProxyConfig {
|
||||
fn default() -> Self {
|
||||
ProxyConfig::None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct PartialProxyConfig {
|
||||
|
|
|
@ -38,7 +38,6 @@ 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(), (*val).to_owned()))
|
||||
.map(|val| ((*k).to_owned().into(), (*val).to_owned().into()))
|
||||
.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(), (*val).to_owned()))
|
||||
.map(|val| ((*k).to_owned().into(), (*val).to_owned().into()))
|
||||
.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(), (*val).to_owned()))
|
||||
.map(|val| ((*k).to_owned().into(), (*val).to_owned().into()))
|
||||
.next()
|
||||
}),
|
||||
)
|
||||
|
|
|
@ -45,17 +45,6 @@ fn db_options(max_open_files: i32, rocksdb_cache: &rocksdb::Cache) -> rocksdb::O
|
|||
db_opts.set_compaction_style(rocksdb::DBCompactionStyle::Level);
|
||||
db_opts.optimize_level_style_compaction(10 * 1024 * 1024);
|
||||
|
||||
// https://github.com/facebook/rocksdb/wiki/Setup-Options-and-Basic-Tuning
|
||||
db_opts.set_max_background_jobs(6);
|
||||
db_opts.set_bytes_per_sync(1048576);
|
||||
|
||||
// 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);
|
||||
|
||||
let prefix_extractor = rocksdb::SliceTransform::create_fixed_prefix(1);
|
||||
db_opts.set_prefix_extractor(prefix_extractor);
|
||||
|
||||
|
@ -65,7 +54,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);
|
||||
let rocksdb_cache = rocksdb::Cache::new_lru_cache(cache_capacity_bytes).unwrap();
|
||||
|
||||
let db_opts = db_options(config.rocksdb_max_open_files, &rocksdb_cache);
|
||||
|
||||
|
@ -132,8 +121,6 @@ impl KeyValueDatabaseEngine for Arc<Engine> {
|
|||
self.cache.get_pinned_usage() as f64 / 1024.0 / 1024.0,
|
||||
))
|
||||
}
|
||||
|
||||
fn clear_caches(&self) {}
|
||||
}
|
||||
|
||||
impl RocksDbEngineTree<'_> {
|
||||
|
@ -174,7 +161,7 @@ impl KvTree for RocksDbEngineTree<'_> {
|
|||
self.db
|
||||
.rocks
|
||||
.iterator_cf(&self.cf(), rocksdb::IteratorMode::Start)
|
||||
.map(|r| r.unwrap())
|
||||
//.map(|r| r.unwrap())
|
||||
.map(|(k, v)| (Vec::from(k), Vec::from(v))),
|
||||
)
|
||||
}
|
||||
|
@ -198,7 +185,7 @@ impl KvTree for RocksDbEngineTree<'_> {
|
|||
},
|
||||
),
|
||||
)
|
||||
.map(|r| r.unwrap())
|
||||
//.map(|r| r.unwrap())
|
||||
.map(|(k, v)| (Vec::from(k), Vec::from(v))),
|
||||
)
|
||||
}
|
||||
|
@ -239,7 +226,7 @@ impl KvTree for RocksDbEngineTree<'_> {
|
|||
&self.cf(),
|
||||
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) {
|
||||
drop(unsafe { Box::from_raw(self.0) });
|
||||
unsafe { Box::from_raw(self.0) };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,7 +106,7 @@ impl KeyValueDatabaseEngine for Arc<Engine> {
|
|||
}
|
||||
|
||||
fn open_tree(&self, name: &str) -> Result<Arc<dyn KvTree>> {
|
||||
self.write_lock().execute(&format!("CREATE TABLE IF NOT EXISTS {name} ( \"key\" BLOB PRIMARY KEY, \"value\" BLOB NOT NULL )"), [])?;
|
||||
self.write_lock().execute(&format!("CREATE TABLE IF NOT EXISTS {} ( \"key\" BLOB PRIMARY KEY, \"value\" BLOB NOT NULL )", name), [])?;
|
||||
|
||||
Ok(Arc::new(SqliteTable {
|
||||
engine: Arc::clone(self),
|
||||
|
@ -135,6 +135,7 @@ type TupleOfBytes = (Vec<u8>, Vec<u8>);
|
|||
|
||||
impl SqliteTable {
|
||||
fn get_with_guard(&self, guard: &Connection, key: &[u8]) -> Result<Option<Vec<u8>>> {
|
||||
//dbg!(&self.name);
|
||||
Ok(guard
|
||||
.prepare(format!("SELECT value FROM {} WHERE key = ?", self.name).as_str())?
|
||||
.query_row([key], |row| row.get(0))
|
||||
|
@ -142,6 +143,7 @@ impl SqliteTable {
|
|||
}
|
||||
|
||||
fn insert_with_guard(&self, guard: &Connection, key: &[u8], value: &[u8]) -> Result<()> {
|
||||
//dbg!(&self.name);
|
||||
guard.execute(
|
||||
format!(
|
||||
"INSERT OR REPLACE INTO {} (key, value) VALUES (?, ?)",
|
||||
|
@ -174,7 +176,10 @@ impl SqliteTable {
|
|||
statement
|
||||
.query_map([], |row| Ok((row.get_unwrap(0), row.get_unwrap(1))))
|
||||
.unwrap()
|
||||
.map(move |r| r.unwrap()),
|
||||
.map(move |r| {
|
||||
//dbg!(&name);
|
||||
r.unwrap()
|
||||
}),
|
||||
);
|
||||
|
||||
Box::new(PreparedStatementIterator {
|
||||
|
@ -271,7 +276,10 @@ impl KvTree for SqliteTable {
|
|||
statement
|
||||
.query_map([from], |row| Ok((row.get_unwrap(0), row.get_unwrap(1))))
|
||||
.unwrap()
|
||||
.map(move |r| r.unwrap()),
|
||||
.map(move |r| {
|
||||
//dbg!(&name);
|
||||
r.unwrap()
|
||||
}),
|
||||
);
|
||||
Box::new(PreparedStatementIterator {
|
||||
iterator,
|
||||
|
@ -293,7 +301,10 @@ impl KvTree for SqliteTable {
|
|||
statement
|
||||
.query_map([from], |row| Ok((row.get_unwrap(0), row.get_unwrap(1))))
|
||||
.unwrap()
|
||||
.map(move |r| r.unwrap()),
|
||||
.map(move |r| {
|
||||
//dbg!(&name);
|
||||
r.unwrap()
|
||||
}),
|
||||
);
|
||||
|
||||
Box::new(PreparedStatementIterator {
|
||||
|
|
|
@ -8,7 +8,6 @@ use tokio::sync::watch;
|
|||
|
||||
#[derive(Default)]
|
||||
pub(super) struct Watchers {
|
||||
#[allow(clippy::type_complexity)]
|
||||
watchers: RwLock<HashMap<Vec<u8>, (watch::Sender<()>, watch::Receiver<()>)>>,
|
||||
}
|
||||
|
||||
|
|
|
@ -123,12 +123,13 @@ impl service::account_data::Data for KeyValueDatabase {
|
|||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||
.map(|(k, v)| {
|
||||
Ok::<_, Error>((
|
||||
RoomAccountDataEventType::from(
|
||||
RoomAccountDataEventType::try_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,8 +1,7 @@
|
|||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use futures_util::{stream::FuturesUnordered, StreamExt};
|
||||
use lru_cache::LruCache;
|
||||
use ruma::{
|
||||
api::federation::discovery::{ServerSigningKeys, VerifyKey},
|
||||
signatures::Ed25519KeyPair,
|
||||
|
@ -12,7 +11,6 @@ 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 {
|
||||
|
@ -28,23 +26,6 @@ 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();
|
||||
|
@ -137,67 +118,8 @@ impl service::globals::Data for KeyValueDatabase {
|
|||
self._db.cleanup()
|
||||
}
|
||||
|
||||
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 memory_usage(&self) -> Result<String> {
|
||||
self._db.memory_usage()
|
||||
}
|
||||
|
||||
fn load_keypair(&self) -> Result<Ed25519KeyPair> {
|
||||
|
@ -256,8 +178,8 @@ lasttimelinecount_cache: {lasttimelinecount_cache}\n"
|
|||
..
|
||||
} = new_keys;
|
||||
|
||||
keys.verify_keys.extend(verify_keys);
|
||||
keys.old_verify_keys.extend(old_verify_keys);
|
||||
keys.verify_keys.extend(verify_keys.into_iter());
|
||||
keys.old_verify_keys.extend(old_verify_keys.into_iter());
|
||||
|
||||
self.server_signingkeys.insert(
|
||||
origin.as_bytes(),
|
||||
|
|
|
@ -12,7 +12,6 @@ mod state;
|
|||
mod state_accessor;
|
||||
mod state_cache;
|
||||
mod state_compressor;
|
||||
mod threads;
|
||||
mod timeline;
|
||||
mod user;
|
||||
|
||||
|
|
|
@ -1,64 +1,10 @@
|
|||
use std::{mem, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
|
||||
use ruma::{EventId, RoomId, UserId};
|
||||
use ruma::{EventId, RoomId};
|
||||
|
||||
use crate::{
|
||||
database::KeyValueDatabase,
|
||||
service::{self, rooms::timeline::PduCount},
|
||||
services, utils, Error, PduEvent, Result,
|
||||
};
|
||||
use crate::{database::KeyValueDatabase, service, 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();
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::mem::size_of;
|
||||
|
||||
use ruma::RoomId;
|
||||
|
||||
use crate::{database::KeyValueDatabase, service, services, utils, Result};
|
||||
|
@ -13,7 +15,7 @@ impl service::rooms::search::Data for KeyValueDatabase {
|
|||
let mut key = shortroomid.to_be_bytes().to_vec();
|
||||
key.extend_from_slice(word.as_bytes());
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(pdu_id); // TODO: currently we save the room id a second time here
|
||||
key.extend_from_slice(pdu_id);
|
||||
(key, Vec::new())
|
||||
});
|
||||
|
||||
|
@ -32,6 +34,7 @@ impl service::rooms::search::Data for KeyValueDatabase {
|
|||
.expect("room exists")
|
||||
.to_be_bytes()
|
||||
.to_vec();
|
||||
let prefix_clone = prefix.clone();
|
||||
|
||||
let words: Vec<_> = search_string
|
||||
.split_terminator(|c: char| !c.is_alphanumeric())
|
||||
|
@ -43,7 +46,6 @@ impl service::rooms::search::Data for KeyValueDatabase {
|
|||
let mut prefix2 = prefix.clone();
|
||||
prefix2.extend_from_slice(word.as_bytes());
|
||||
prefix2.push(0xff);
|
||||
let prefix3 = prefix2.clone();
|
||||
|
||||
let mut last_possible_id = prefix2.clone();
|
||||
last_possible_id.extend_from_slice(&u64::MAX.to_be_bytes());
|
||||
|
@ -51,7 +53,7 @@ impl service::rooms::search::Data for KeyValueDatabase {
|
|||
self.tokenids
|
||||
.iter_from(&last_possible_id, true) // Newest pdus first
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix2))
|
||||
.map(move |(key, _)| key[prefix3.len()..].to_vec())
|
||||
.map(|(key, _)| key[key.len() - size_of::<u64>()..].to_vec())
|
||||
});
|
||||
|
||||
let common_elements = match utils::common_elements(iterators, |a, b| {
|
||||
|
@ -62,6 +64,12 @@ impl service::rooms::search::Data for KeyValueDatabase {
|
|||
None => return Ok(None),
|
||||
};
|
||||
|
||||
Ok(Some((Box::new(common_elements), words)))
|
||||
let mapped = common_elements.map(move |id| {
|
||||
let mut pduid = prefix_clone.clone();
|
||||
pduid.extend_from_slice(&id);
|
||||
pduid
|
||||
});
|
||||
|
||||
Ok(Some((Box::new(mapped), words)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -157,9 +157,10 @@ impl service::rooms::short::Data for KeyValueDatabase {
|
|||
.ok_or_else(|| Error::bad_database("Invalid statekey in shortstatekey_statekey."))?;
|
||||
|
||||
let event_type =
|
||||
StateEventType::from(utils::string_from_bytes(eventtype_bytes).map_err(|_| {
|
||||
StateEventType::try_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.")
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use crate::{database::KeyValueDatabase, service, services, utils, Error, PduEvent, Result};
|
||||
use async_trait::async_trait;
|
||||
|
@ -6,7 +9,7 @@ use ruma::{events::StateEventType, EventId, RoomId};
|
|||
|
||||
#[async_trait]
|
||||
impl service::rooms::state_accessor::Data for KeyValueDatabase {
|
||||
async fn state_full_ids(&self, shortstatehash: u64) -> Result<HashMap<u64, Arc<EventId>>> {
|
||||
async fn state_full_ids(&self, shortstatehash: u64) -> Result<BTreeMap<u64, Arc<EventId>>> {
|
||||
let full_state = services()
|
||||
.rooms
|
||||
.state_compressor
|
||||
|
@ -14,13 +17,13 @@ impl service::rooms::state_accessor::Data for KeyValueDatabase {
|
|||
.pop()
|
||||
.expect("there is always one layer")
|
||||
.1;
|
||||
let mut result = HashMap::new();
|
||||
let mut result = BTreeMap::new();
|
||||
let mut i = 0;
|
||||
for compressed in full_state.iter() {
|
||||
for compressed in full_state.into_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 +48,11 @@ impl service::rooms::state_accessor::Data for KeyValueDatabase {
|
|||
|
||||
let mut result = HashMap::new();
|
||||
let mut i = 0;
|
||||
for compressed in full_state.iter() {
|
||||
for compressed in full_state {
|
||||
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 +98,13 @@ impl service::rooms::state_accessor::Data for KeyValueDatabase {
|
|||
.expect("there is always one layer")
|
||||
.1;
|
||||
Ok(full_state
|
||||
.iter()
|
||||
.into_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)
|
||||
}))
|
||||
|
|
|
@ -471,7 +471,6 @@ 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,
|
||||
|
@ -550,7 +549,6 @@ 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, sync::Arc};
|
||||
use std::{collections::HashSet, mem::size_of};
|
||||
|
||||
use crate::{
|
||||
database::KeyValueDatabase,
|
||||
|
@ -37,20 +37,20 @@ impl service::rooms::state_compressor::Data for KeyValueDatabase {
|
|||
|
||||
Ok(StateDiff {
|
||||
parent,
|
||||
added: Arc::new(added),
|
||||
removed: Arc::new(removed),
|
||||
added,
|
||||
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.iter() {
|
||||
for new in &diff.added {
|
||||
value.extend_from_slice(&new[..]);
|
||||
}
|
||||
|
||||
if !diff.removed.is_empty() {
|
||||
value.extend_from_slice(&0_u64.to_be_bytes());
|
||||
for removed in diff.removed.iter() {
|
||||
for removed in &diff.removed {
|
||||
value.extend_from_slice(&removed[..]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,10 +7,30 @@ use tracing::error;
|
|||
|
||||
use crate::{database::KeyValueDatabase, service, services, utils, Error, PduEvent, Result};
|
||||
|
||||
use service::rooms::timeline::PduCount;
|
||||
|
||||
impl service::rooms::timeline::Data for KeyValueDatabase {
|
||||
fn last_timeline_count(&self, sender_user: &UserId, room_id: &RoomId) -> Result<PduCount> {
|
||||
fn first_pdu_in_room(&self, room_id: &RoomId) -> Result<Option<Arc<PduEvent>>> {
|
||||
let prefix = services()
|
||||
.rooms
|
||||
.short
|
||||
.get_shortroomid(room_id)?
|
||||
.expect("room exists")
|
||||
.to_be_bytes()
|
||||
.to_vec();
|
||||
|
||||
// Look for PDUs in that room.
|
||||
self.pduid_pdu
|
||||
.iter_from(&prefix, false)
|
||||
.filter(|(k, _)| k.starts_with(&prefix))
|
||||
.map(|(_, pdu)| {
|
||||
serde_json::from_slice(&pdu)
|
||||
.map_err(|_| Error::bad_database("Invalid first PDU in db."))
|
||||
.map(Arc::new)
|
||||
})
|
||||
.next()
|
||||
.transpose()
|
||||
}
|
||||
|
||||
fn last_timeline_count(&self, sender_user: &UserId, room_id: &RoomId) -> Result<u64> {
|
||||
match self
|
||||
.lasttimelinecount_cache
|
||||
.lock()
|
||||
|
@ -19,18 +39,20 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
|||
{
|
||||
hash_map::Entry::Vacant(v) => {
|
||||
if let Some(last_count) = self
|
||||
.pdus_until(sender_user, room_id, PduCount::max())?
|
||||
.find_map(|r| {
|
||||
.pdus_until(sender_user, room_id, u64::MAX)?
|
||||
.filter_map(|r| {
|
||||
// Filter out buggy events
|
||||
if r.is_err() {
|
||||
error!("Bad pdu in pdus_since: {:?}", r);
|
||||
}
|
||||
r.ok()
|
||||
})
|
||||
.map(|(pduid, _)| self.pdu_count(&pduid))
|
||||
.next()
|
||||
{
|
||||
Ok(*v.insert(last_count.0))
|
||||
Ok(*v.insert(last_count?))
|
||||
} else {
|
||||
Ok(PduCount::Normal(0))
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
hash_map::Entry::Occupied(o) => Ok(*o.get()),
|
||||
|
@ -38,27 +60,29 @@ 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>> {
|
||||
fn get_pdu_count(&self, event_id: &EventId) -> Result<Option<u64>> {
|
||||
self.eventid_pduid
|
||||
.get(event_id.as_bytes())?
|
||||
.map(|pdu_id| pdu_count(&pdu_id))
|
||||
.map(|pdu_id| self.pdu_count(&pdu_id))
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Returns the json of a pdu.
|
||||
fn get_pdu_json(&self, event_id: &EventId) -> Result<Option<CanonicalJsonObject>> {
|
||||
self.get_non_outlier_pdu_json(event_id)?.map_or_else(
|
||||
|| {
|
||||
self.eventid_outlierpdu
|
||||
.get(event_id.as_bytes())?
|
||||
.map(|pdu| {
|
||||
serde_json::from_slice(&pdu)
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in db."))
|
||||
})
|
||||
.transpose()
|
||||
},
|
||||
|x| Ok(Some(x)),
|
||||
)
|
||||
self.eventid_pduid
|
||||
.get(event_id.as_bytes())?
|
||||
.map_or_else(
|
||||
|| self.eventid_outlierpdu.get(event_id.as_bytes()),
|
||||
|pduid| {
|
||||
Ok(Some(self.pduid_pdu.get(&pduid)?.ok_or_else(|| {
|
||||
Error::bad_database("Invalid pduid in eventid_pduid.")
|
||||
})?))
|
||||
},
|
||||
)?
|
||||
.map(|pdu| {
|
||||
serde_json::from_slice(&pdu).map_err(|_| Error::bad_database("Invalid PDU in db."))
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Returns the json of a pdu.
|
||||
|
@ -83,6 +107,8 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
|||
}
|
||||
|
||||
/// 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())?
|
||||
|
@ -107,20 +133,22 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
|||
}
|
||||
|
||||
if let Some(pdu) = self
|
||||
.get_non_outlier_pdu(event_id)?
|
||||
.eventid_pduid
|
||||
.get(event_id.as_bytes())?
|
||||
.map_or_else(
|
||||
|| {
|
||||
self.eventid_outlierpdu
|
||||
.get(event_id.as_bytes())?
|
||||
.map(|pdu| {
|
||||
serde_json::from_slice(&pdu)
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in db."))
|
||||
})
|
||||
.transpose()
|
||||
|| self.eventid_outlierpdu.get(event_id.as_bytes()),
|
||||
|pduid| {
|
||||
Ok(Some(self.pduid_pdu.get(&pduid)?.ok_or_else(|| {
|
||||
Error::bad_database("Invalid pduid in eventid_pduid.")
|
||||
})?))
|
||||
},
|
||||
|x| Ok(Some(x)),
|
||||
)?
|
||||
.map(Arc::new)
|
||||
.map(|pdu| {
|
||||
serde_json::from_slice(&pdu)
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in db."))
|
||||
.map(Arc::new)
|
||||
})
|
||||
.transpose()?
|
||||
{
|
||||
self.pdu_cache
|
||||
.lock()
|
||||
|
@ -154,6 +182,12 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
|||
})
|
||||
}
|
||||
|
||||
/// Returns the `count` of this pdu's id.
|
||||
fn pdu_count(&self, pdu_id: &[u8]) -> Result<u64> {
|
||||
utils::u64_from_bytes(&pdu_id[pdu_id.len() - size_of::<u64>()..])
|
||||
.map_err(|_| Error::bad_database("PDU has invalid count bytes."))
|
||||
}
|
||||
|
||||
fn append_pdu(
|
||||
&self,
|
||||
pdu_id: &[u8],
|
||||
|
@ -169,7 +203,7 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
|||
self.lasttimelinecount_cache
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(pdu.room_id.clone(), PduCount::Normal(count));
|
||||
.insert(pdu.room_id.clone(), count);
|
||||
|
||||
self.eventid_pduid.insert(pdu.event_id.as_bytes(), pdu_id)?;
|
||||
self.eventid_outlierpdu.remove(pdu.event_id.as_bytes())?;
|
||||
|
@ -177,48 +211,57 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn prepend_backfill_pdu(
|
||||
&self,
|
||||
pdu_id: &[u8],
|
||||
event_id: &EventId,
|
||||
json: &CanonicalJsonObject,
|
||||
) -> Result<()> {
|
||||
self.pduid_pdu.insert(
|
||||
pdu_id,
|
||||
&serde_json::to_vec(json).expect("CanonicalJsonObject is always a valid"),
|
||||
)?;
|
||||
|
||||
self.eventid_pduid.insert(event_id.as_bytes(), pdu_id)?;
|
||||
self.eventid_outlierpdu.remove(event_id.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes a pdu and creates a new one with the same id.
|
||||
fn replace_pdu(
|
||||
&self,
|
||||
pdu_id: &[u8],
|
||||
pdu_json: &CanonicalJsonObject,
|
||||
pdu: &PduEvent,
|
||||
) -> Result<()> {
|
||||
fn replace_pdu(&self, pdu_id: &[u8], pdu: &PduEvent) -> Result<()> {
|
||||
if self.pduid_pdu.get(pdu_id)?.is_some() {
|
||||
self.pduid_pdu.insert(
|
||||
pdu_id,
|
||||
&serde_json::to_vec(pdu_json).expect("CanonicalJsonObject is always a valid"),
|
||||
&serde_json::to_vec(pdu).expect("CanonicalJsonObject is always a valid"),
|
||||
)?;
|
||||
Ok(())
|
||||
} else {
|
||||
return Err(Error::BadRequest(
|
||||
Err(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"PDU does not exist.",
|
||||
));
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
self.pdu_cache
|
||||
.lock()
|
||||
.unwrap()
|
||||
.remove(&(*pdu.event_id).to_owned());
|
||||
/// Returns an iterator over all events in a room that happened after the event with id `since`
|
||||
/// in chronological order.
|
||||
fn pdus_since<'a>(
|
||||
&'a self,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
since: u64,
|
||||
) -> Result<Box<dyn Iterator<Item = Result<(Vec<u8>, PduEvent)>> + 'a>> {
|
||||
let prefix = services()
|
||||
.rooms
|
||||
.short
|
||||
.get_shortroomid(room_id)?
|
||||
.expect("room exists")
|
||||
.to_be_bytes()
|
||||
.to_vec();
|
||||
|
||||
Ok(())
|
||||
// Skip the first pdu if it's exactly at since, because we sent that last time
|
||||
let mut first_pdu_id = prefix.clone();
|
||||
first_pdu_id.extend_from_slice(&(since + 1).to_be_bytes());
|
||||
|
||||
let user_id = user_id.to_owned();
|
||||
|
||||
Ok(Box::new(
|
||||
self.pduid_pdu
|
||||
.iter_from(&first_pdu_id, false)
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||
.map(move |(pdu_id, v)| {
|
||||
let mut pdu = serde_json::from_slice::<PduEvent>(&v)
|
||||
.map_err(|_| Error::bad_database("PDU in db is invalid."))?;
|
||||
if pdu.sender != user_id {
|
||||
pdu.remove_transaction_id()?;
|
||||
}
|
||||
Ok((pdu_id, pdu))
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns an iterator over all events and their tokens in a room that happened before the
|
||||
|
@ -227,15 +270,27 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
|||
&'a self,
|
||||
user_id: &UserId,
|
||||
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)?;
|
||||
until: u64,
|
||||
) -> Result<Box<dyn Iterator<Item = Result<(Vec<u8>, PduEvent)>> + 'a>> {
|
||||
// Create the first part of the full pdu id
|
||||
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.saturating_sub(1)).to_be_bytes()); // -1 because we don't want event at `until`
|
||||
|
||||
let current: &[u8] = ¤t;
|
||||
|
||||
let user_id = user_id.to_owned();
|
||||
|
||||
Ok(Box::new(
|
||||
self.pduid_pdu
|
||||
.iter_from(¤t, true)
|
||||
.iter_from(current, true)
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||
.map(move |(pdu_id, v)| {
|
||||
let mut pdu = serde_json::from_slice::<PduEvent>(&v)
|
||||
|
@ -243,9 +298,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))
|
||||
Ok((pdu_id, pdu))
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
@ -254,15 +307,27 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
|||
&'a self,
|
||||
user_id: &UserId,
|
||||
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)?;
|
||||
from: u64,
|
||||
) -> Result<Box<dyn Iterator<Item = Result<(Vec<u8>, PduEvent)>> + 'a>> {
|
||||
// Create the first part of the full pdu id
|
||||
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(&(from + 1).to_be_bytes()); // +1 so we don't send the base event
|
||||
|
||||
let current: &[u8] = ¤t;
|
||||
|
||||
let user_id = user_id.to_owned();
|
||||
|
||||
Ok(Box::new(
|
||||
self.pduid_pdu
|
||||
.iter_from(¤t, false)
|
||||
.iter_from(current, false)
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||
.map(move |(pdu_id, v)| {
|
||||
let mut pdu = serde_json::from_slice::<PduEvent>(&v)
|
||||
|
@ -270,9 +335,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))
|
||||
Ok((pdu_id, pdu))
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
@ -305,60 +368,3 @@ impl service::rooms::timeline::Data for KeyValueDatabase {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the `count` of this pdu's id.
|
||||
fn pdu_count(pdu_id: &[u8]) -> Result<PduCount> {
|
||||
let last_u64 = utils::u64_from_bytes(&pdu_id[pdu_id.len() - size_of::<u64>()..])
|
||||
.map_err(|_| Error::bad_database("PDU has invalid count bytes."))?;
|
||||
let second_last_u64 = utils::u64_from_bytes(
|
||||
&pdu_id[pdu_id.len() - 2 * size_of::<u64>()..pdu_id.len() - size_of::<u64>()],
|
||||
);
|
||||
|
||||
if matches!(second_last_u64, Ok(0)) {
|
||||
Ok(PduCount::Backfilled(u64::MAX - last_u64))
|
||||
} else {
|
||||
Ok(PduCount::Normal(last_u64))
|
||||
}
|
||||
}
|
||||
|
||||
fn count_to_id(
|
||||
room_id: &RoomId,
|
||||
count: PduCount,
|
||||
offset: u64,
|
||||
subtract: bool,
|
||||
) -> Result<(Vec<u8>, Vec<u8>)> {
|
||||
let prefix = services()
|
||||
.rooms
|
||||
.short
|
||||
.get_shortroomid(room_id)?
|
||||
.ok_or_else(|| Error::bad_database("Looked for bad shortroomid in timeline"))?
|
||||
.to_be_bytes()
|
||||
.to_vec();
|
||||
let mut pdu_id = prefix.clone();
|
||||
// +1 so we don't send the base event
|
||||
let count_raw = match count {
|
||||
PduCount::Normal(x) => {
|
||||
if subtract {
|
||||
x - offset
|
||||
} else {
|
||||
x + offset
|
||||
}
|
||||
}
|
||||
PduCount::Backfilled(x) => {
|
||||
pdu_id.extend_from_slice(&0_u64.to_be_bytes());
|
||||
let num = u64::MAX - x;
|
||||
if subtract {
|
||||
if num > 0 {
|
||||
num - offset
|
||||
} else {
|
||||
num
|
||||
}
|
||||
} else {
|
||||
num + offset
|
||||
}
|
||||
}
|
||||
};
|
||||
pdu_id.extend_from_slice(&count_raw.to_be_bytes());
|
||||
|
||||
Ok((prefix, pdu_id))
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::{collections::BTreeMap, mem::size_of};
|
||||
|
||||
use ruma::{
|
||||
api::client::{device::Device, error::ErrorKind, filter::FilterDefinition},
|
||||
api::client::{device::Device, error::ErrorKind, filter::IncomingFilterDefinition},
|
||||
encryption::{CrossSigningKey, DeviceKeys, OneTimeKey},
|
||||
events::{AnyToDeviceEvent, StateEventType},
|
||||
serde::Raw,
|
||||
|
@ -146,9 +146,10 @@ impl service::users::Data for KeyValueDatabase {
|
|||
self.userid_avatarurl
|
||||
.get(user_id.as_bytes())?
|
||||
.map(|bytes| {
|
||||
utils::string_from_bytes(&bytes)
|
||||
let s = utils::string_from_bytes(&bytes)
|
||||
.map_err(|_| Error::bad_database("Avatar URL in db is invalid."))?;
|
||||
s.try_into()
|
||||
.map_err(|_| Error::bad_database("Avatar URL in db is invalid."))
|
||||
.map(Into::into)
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
@ -448,13 +449,33 @@ 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);
|
||||
|
||||
let (master_key_key, _) = self.parse_master_key(user_id, master_key)?;
|
||||
// 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());
|
||||
|
||||
self.keyid_key
|
||||
.insert(&master_key_key, master_key.json().get().as_bytes())?;
|
||||
|
@ -530,9 +551,7 @@ impl service::users::Data for KeyValueDatabase {
|
|||
.insert(user_id.as_bytes(), &user_signing_key_key)?;
|
||||
}
|
||||
|
||||
if notify {
|
||||
self.mark_device_key_update(user_id)?;
|
||||
}
|
||||
self.mark_device_key_update(user_id)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -573,6 +592,7 @@ 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(())
|
||||
|
@ -671,80 +691,45 @@ 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.get_key(&key, sender_user, user_id, allowed_signatures)
|
||||
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"),
|
||||
)))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
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.get_key(&key, sender_user, user_id, allowed_signatures)
|
||||
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"),
|
||||
)))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -914,7 +899,7 @@ impl service::users::Data for KeyValueDatabase {
|
|||
}
|
||||
|
||||
/// Creates a new sync filter. Returns the filter id.
|
||||
fn create_filter(&self, user_id: &UserId, filter: &FilterDefinition) -> Result<String> {
|
||||
fn create_filter(&self, user_id: &UserId, filter: &IncomingFilterDefinition) -> Result<String> {
|
||||
let filter_id = utils::random_string(4);
|
||||
|
||||
let mut key = user_id.as_bytes().to_vec();
|
||||
|
@ -929,7 +914,11 @@ impl service::users::Data for KeyValueDatabase {
|
|||
Ok(filter_id)
|
||||
}
|
||||
|
||||
fn get_filter(&self, user_id: &UserId, filter_id: &str) -> Result<Option<FilterDefinition>> {
|
||||
fn get_filter(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
filter_id: &str,
|
||||
) -> Result<Option<IncomingFilterDefinition>> {
|
||||
let mut key = user_id.as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(filter_id.as_bytes());
|
||||
|
@ -945,8 +934,6 @@ 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
|
||||
|
|
|
@ -1,24 +1,19 @@
|
|||
pub mod abstraction;
|
||||
pub mod key_value;
|
||||
|
||||
use crate::{
|
||||
service::rooms::timeline::PduCount, services, utils, Config, Error, PduEvent, Result, Services,
|
||||
SERVICES,
|
||||
};
|
||||
use crate::{services, utils, Config, Error, PduEvent, Result, Services, SERVICES};
|
||||
use abstraction::{KeyValueDatabaseEngine, KvTree};
|
||||
use directories::ProjectDirs;
|
||||
use lru_cache::LruCache;
|
||||
use ruma::{
|
||||
events::{
|
||||
push_rules::{PushRulesEvent, PushRulesEventContent},
|
||||
room::message::RoomMessageEventContent,
|
||||
push_rules::PushRulesEventContent, room::message::RoomMessageEventContent,
|
||||
GlobalAccountDataEvent, GlobalAccountDataEventType, StateEventType,
|
||||
},
|
||||
push::Ruleset,
|
||||
CanonicalJsonValue, EventId, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId,
|
||||
UserId,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
fs::{self, remove_dir_all},
|
||||
|
@ -26,9 +21,7 @@ use std::{
|
|||
mem::size_of,
|
||||
path::Path,
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::time::interval;
|
||||
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
|
@ -83,8 +76,6 @@ 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.
|
||||
|
@ -133,8 +124,6 @@ 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>,
|
||||
|
||||
|
@ -171,7 +160,7 @@ pub struct KeyValueDatabase {
|
|||
pub(super) shortstatekey_cache: Mutex<LruCache<u64, (StateEventType, String)>>,
|
||||
pub(super) our_real_users_cache: RwLock<HashMap<OwnedRoomId, Arc<HashSet<OwnedUserId>>>>,
|
||||
pub(super) appservice_in_room_cache: RwLock<HashMap<OwnedRoomId, HashMap<String, bool>>>,
|
||||
pub(super) lasttimelinecount_cache: Mutex<HashMap<OwnedRoomId, PduCount>>,
|
||||
pub(super) lasttimelinecount_cache: Mutex<HashMap<OwnedRoomId, u64>>,
|
||||
}
|
||||
|
||||
impl KeyValueDatabase {
|
||||
|
@ -267,12 +256,8 @@ 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.");
|
||||
eprintln!("ERROR: Max request size is less than 1KB. Please increase it.");
|
||||
}
|
||||
|
||||
let db_raw = Box::new(Self {
|
||||
|
@ -313,8 +298,6 @@ 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")?,
|
||||
|
@ -355,7 +338,6 @@ 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")?,
|
||||
|
@ -425,7 +407,7 @@ impl KeyValueDatabase {
|
|||
}
|
||||
|
||||
// If the database has any data, perform data migrations before starting
|
||||
let latest_database_version = 13;
|
||||
let latest_database_version = 11;
|
||||
|
||||
if services().users.count()? > 0 {
|
||||
// MIGRATIONS
|
||||
|
@ -500,7 +482,7 @@ impl KeyValueDatabase {
|
|||
for user in services().rooms.state_cache.room_members(&room?) {
|
||||
let user = user?;
|
||||
if user.server_name() != services().globals.server_name() {
|
||||
info!(?user, "Migration: creating user");
|
||||
println!("Migration: Creating user {}", user);
|
||||
services().users.create(&user, None)?;
|
||||
}
|
||||
}
|
||||
|
@ -562,6 +544,7 @@ impl KeyValueDatabase {
|
|||
current_state: HashSet<_>,
|
||||
last_roomstates: &mut HashMap<_, _>| {
|
||||
counter += 1;
|
||||
println!("counter: {}", counter);
|
||||
let last_roomsstatehash = last_roomstates.get(current_room);
|
||||
|
||||
let states_parents = last_roomsstatehash.map_or_else(
|
||||
|
@ -594,8 +577,8 @@ impl KeyValueDatabase {
|
|||
|
||||
services().rooms.state_compressor.save_state_from_diff(
|
||||
current_sstatehash,
|
||||
Arc::new(statediffnew),
|
||||
Arc::new(statediffremoved),
|
||||
statediffnew,
|
||||
statediffremoved,
|
||||
2, // every state change is 2 event changes on average
|
||||
states_parents,
|
||||
)?;
|
||||
|
@ -758,13 +741,15 @@ impl KeyValueDatabase {
|
|||
new_key.extend_from_slice(word);
|
||||
new_key.push(0xff);
|
||||
new_key.extend_from_slice(pdu_id_count);
|
||||
println!("old {:?}", key);
|
||||
println!("new {:?}", new_key);
|
||||
Some((new_key, Vec::new()))
|
||||
})
|
||||
.peekable();
|
||||
|
||||
while iter.peek().is_some() {
|
||||
db.tokenids.insert_batch(&mut iter.by_ref().take(1000))?;
|
||||
debug!("Inserted smaller batch");
|
||||
println!("smaller batch done");
|
||||
}
|
||||
|
||||
info!("Deleting starts");
|
||||
|
@ -774,6 +759,7 @@ impl KeyValueDatabase {
|
|||
.iter()
|
||||
.filter_map(|(key, _)| {
|
||||
if key.starts_with(b"!") {
|
||||
println!("del {:?}", key);
|
||||
Some(key)
|
||||
} else {
|
||||
None
|
||||
|
@ -782,6 +768,7 @@ impl KeyValueDatabase {
|
|||
.collect();
|
||||
|
||||
for key in batch2 {
|
||||
println!("del");
|
||||
db.tokenids.remove(&key)?;
|
||||
}
|
||||
|
||||
|
@ -816,136 +803,7 @@ impl KeyValueDatabase {
|
|||
warn!("Migration: 10 -> 11 finished");
|
||||
}
|
||||
|
||||
if services().globals.database_version()? < 12 {
|
||||
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 rules_list = &mut account_data.content.global;
|
||||
|
||||
//content rule
|
||||
{
|
||||
let content_rule_transformation =
|
||||
[".m.rules.contains_user_name", ".m.rule.contains_user_name"];
|
||||
|
||||
let rule = rules_list.content.get(content_rule_transformation[0]);
|
||||
if rule.is_some() {
|
||||
let mut rule = rule.unwrap().clone();
|
||||
rule.rule_id = content_rule_transformation[1].to_owned();
|
||||
rules_list
|
||||
.content
|
||||
.shift_remove(content_rule_transformation[0]);
|
||||
rules_list.content.insert(rule);
|
||||
}
|
||||
}
|
||||
|
||||
//underride rules
|
||||
{
|
||||
let underride_rule_transformation = [
|
||||
[".m.rules.call", ".m.rule.call"],
|
||||
[".m.rules.room_one_to_one", ".m.rule.room_one_to_one"],
|
||||
[
|
||||
".m.rules.encrypted_room_one_to_one",
|
||||
".m.rule.encrypted_room_one_to_one",
|
||||
],
|
||||
[".m.rules.message", ".m.rule.message"],
|
||||
[".m.rules.encrypted", ".m.rule.encrypted"],
|
||||
];
|
||||
|
||||
for transformation in underride_rule_transformation {
|
||||
let rule = rules_list.underride.get(transformation[0]);
|
||||
if let Some(rule) = rule {
|
||||
let mut rule = rule.clone();
|
||||
rule.rule_id = transformation[1].to_owned();
|
||||
rules_list.underride.shift_remove(transformation[0]);
|
||||
rules_list.underride.insert(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
services().account_data.update(
|
||||
None,
|
||||
&user,
|
||||
GlobalAccountDataEventType::PushRules.to_string().into(),
|
||||
&serde_json::to_value(account_data).expect("to json value always works"),
|
||||
)?;
|
||||
}
|
||||
|
||||
services().globals.bump_database_version(12)?;
|
||||
|
||||
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"),
|
||||
)?;
|
||||
}
|
||||
|
||||
services().globals.bump_database_version(13)?;
|
||||
|
||||
warn!("Migration: 12 -> 13 finished");
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
services().globals.database_version().unwrap(),
|
||||
latest_database_version
|
||||
);
|
||||
assert_eq!(11, latest_database_version);
|
||||
|
||||
info!(
|
||||
"Loaded {} database with version {}",
|
||||
|
@ -991,9 +849,6 @@ 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(())
|
||||
}
|
||||
|
@ -1009,64 +864,13 @@ impl KeyValueDatabase {
|
|||
res
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
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() {
|
||||
use tokio::time::interval;
|
||||
|
||||
#[cfg(unix)]
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
use tracing::info;
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
|
@ -1082,23 +886,23 @@ impl KeyValueDatabase {
|
|||
#[cfg(unix)]
|
||||
tokio::select! {
|
||||
_ = i.tick() => {
|
||||
debug!("cleanup: Timer ticked");
|
||||
info!("cleanup: Timer ticked");
|
||||
}
|
||||
_ = s.recv() => {
|
||||
debug!("cleanup: Received SIGHUP");
|
||||
info!("cleanup: Received SIGHUP");
|
||||
}
|
||||
};
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
i.tick().await;
|
||||
debug!("cleanup: Timer ticked")
|
||||
info!("cleanup: Timer ticked")
|
||||
}
|
||||
|
||||
let start = Instant::now();
|
||||
if let Err(e) = services().globals.cleanup() {
|
||||
error!("cleanup: Errored: {}", e);
|
||||
} else {
|
||||
debug!("cleanup: Finished in {:?}", start.elapsed());
|
||||
info!("cleanup: Finished in {:?}", start.elapsed());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
#![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;
|
||||
mod config;
|
||||
mod database;
|
||||
|
|
203
src/main.rs
203
src/main.rs
|
@ -1,7 +1,17 @@
|
|||
use std::{future::Future, io, net::SocketAddr, sync::atomic, time::Duration};
|
||||
#![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, time::Duration};
|
||||
|
||||
use axum::{
|
||||
extract::{DefaultBodyLimit, FromRequestParts, MatchedPath},
|
||||
extract::{DefaultBodyLimit, FromRequest, MatchedPath},
|
||||
handler::Handler,
|
||||
response::IntoResponse,
|
||||
routing::{get, on, MethodFilter},
|
||||
Router,
|
||||
|
@ -16,9 +26,10 @@ use http::{
|
|||
header::{self, HeaderName},
|
||||
Method, StatusCode, Uri,
|
||||
};
|
||||
use opentelemetry::trace::{FutureExt, Tracer};
|
||||
use ruma::api::{
|
||||
client::{
|
||||
error::{Error as RumaError, ErrorBody, ErrorKind},
|
||||
error::{Error as RumaError, ErrorKind},
|
||||
uiaa::UiaaResponse,
|
||||
},
|
||||
IncomingRequest,
|
||||
|
@ -30,7 +41,7 @@ use tower_http::{
|
|||
trace::TraceLayer,
|
||||
ServiceBuilderExt as _,
|
||||
};
|
||||
use tracing::{debug, error, info, warn};
|
||||
use tracing::{info, warn};
|
||||
use tracing_subscriber::{prelude::*, EnvFilter};
|
||||
|
||||
pub use conduit::*; // Re-export everything from the library crate
|
||||
|
@ -44,7 +55,7 @@ static GLOBAL: Jemalloc = Jemalloc;
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Initialize config
|
||||
// Initialize DB
|
||||
let raw_config =
|
||||
Figment::new()
|
||||
.merge(
|
||||
|
@ -58,86 +69,65 @@ async fn main() {
|
|||
let config = match raw_config.extract::<Config>() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("It looks like your config is invalid. The following error occurred: {e}");
|
||||
eprintln!("It looks like your config is invalid. The following error occured while parsing it: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
config.warn_deprecated();
|
||||
|
||||
let log = format!("{},ruma_state_res=error,_=off,sled=off", config.log);
|
||||
if let Err(e) = KeyValueDatabase::load_or_create(config).await {
|
||||
eprintln!(
|
||||
"The database couldn't be loaded or created. The following error occured: {}",
|
||||
e
|
||||
);
|
||||
std::process::exit(1);
|
||||
};
|
||||
|
||||
let config = &services().globals.config;
|
||||
|
||||
let start = async {
|
||||
run_server().await.unwrap();
|
||||
};
|
||||
|
||||
if config.allow_jaeger {
|
||||
opentelemetry::global::set_text_map_propagator(opentelemetry_jaeger::Propagator::new());
|
||||
let tracer = opentelemetry_jaeger::new_agent_pipeline()
|
||||
.with_auto_split_batch(true)
|
||||
.with_service_name("conduit")
|
||||
.install_batch(opentelemetry::runtime::Tokio)
|
||||
.unwrap();
|
||||
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
|
||||
|
||||
let filter_layer = match EnvFilter::try_new(&log) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"It looks like your log config is invalid. The following error occurred: {e}"
|
||||
);
|
||||
EnvFilter::try_new("warn").unwrap()
|
||||
}
|
||||
};
|
||||
let span = tracer.start("conduit");
|
||||
start.with_current_context().await;
|
||||
drop(span);
|
||||
|
||||
let subscriber = tracing_subscriber::Registry::default()
|
||||
.with(filter_layer)
|
||||
.with(telemetry);
|
||||
tracing::subscriber::set_global_default(subscriber).unwrap();
|
||||
} else if config.tracing_flame {
|
||||
let registry = tracing_subscriber::Registry::default();
|
||||
let (flame_layer, _guard) =
|
||||
tracing_flame::FlameLayer::with_file("./tracing.folded").unwrap();
|
||||
let flame_layer = flame_layer.with_empty_samples(false);
|
||||
|
||||
let filter_layer = EnvFilter::new("trace,h2=off");
|
||||
|
||||
let subscriber = registry.with(filter_layer).with(flame_layer);
|
||||
tracing::subscriber::set_global_default(subscriber).unwrap();
|
||||
println!("exporting");
|
||||
opentelemetry::global::shutdown_tracer_provider();
|
||||
} else {
|
||||
let registry = tracing_subscriber::Registry::default();
|
||||
let fmt_layer = tracing_subscriber::fmt::Layer::new();
|
||||
let filter_layer = match EnvFilter::try_new(&log) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("It looks like your config is invalid. The following error occured while parsing it: {e}");
|
||||
EnvFilter::try_new("warn").unwrap()
|
||||
}
|
||||
};
|
||||
if config.tracing_flame {
|
||||
let (flame_layer, _guard) =
|
||||
tracing_flame::FlameLayer::with_file("./tracing.folded").unwrap();
|
||||
let flame_layer = flame_layer.with_empty_samples(false);
|
||||
|
||||
let subscriber = registry.with(filter_layer).with(fmt_layer);
|
||||
tracing::subscriber::set_global_default(subscriber).unwrap();
|
||||
}
|
||||
let filter_layer = EnvFilter::new("trace,h2=off");
|
||||
|
||||
// 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");
|
||||
let subscriber = registry.with(filter_layer).with(flame_layer);
|
||||
tracing::subscriber::set_global_default(subscriber).unwrap();
|
||||
start.await;
|
||||
} else {
|
||||
let fmt_layer = tracing_subscriber::fmt::Layer::new();
|
||||
let filter_layer = match EnvFilter::try_new(&config.log) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("It looks like your log config is invalid. The following error occurred: {}", e);
|
||||
EnvFilter::try_new("warn").unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
info!("Loading database");
|
||||
if let Err(error) = KeyValueDatabase::load_or_create(config).await {
|
||||
error!(?error, "The database couldn't be loaded or created");
|
||||
|
||||
std::process::exit(1);
|
||||
};
|
||||
let config = &services().globals.config;
|
||||
|
||||
info!("Starting server");
|
||||
run_server().await.unwrap();
|
||||
|
||||
if config.allow_jaeger {
|
||||
opentelemetry::global::shutdown_tracer_provider();
|
||||
let subscriber = registry.with(filter_layer).with(fmt_layer);
|
||||
tracing::subscriber::set_global_default(subscriber).unwrap();
|
||||
start.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,7 +139,6 @@ async fn run_server() -> io::Result<()> {
|
|||
|
||||
let middlewares = ServiceBuilder::new()
|
||||
.sensitive_headers([header::AUTHORIZATION])
|
||||
.layer(axum::middleware::from_fn(spawn_task))
|
||||
.layer(
|
||||
TraceLayer::new_for_http().make_span_with(|request: &http::Request<_>| {
|
||||
let path = if let Some(path) = request.extensions().get::<MatchedPath>() {
|
||||
|
@ -161,6 +150,7 @@ async fn run_server() -> io::Result<()> {
|
|||
tracing::info_span!("http_request", %path)
|
||||
}),
|
||||
)
|
||||
.compression()
|
||||
.layer(axum::middleware::from_fn(unrecognized_method))
|
||||
.layer(
|
||||
CorsLayer::new()
|
||||
|
@ -196,39 +186,21 @@ async fn run_server() -> io::Result<()> {
|
|||
match &config.tls {
|
||||
Some(tls) => {
|
||||
let conf = RustlsConfig::from_pem_file(&tls.certs, &tls.key).await?;
|
||||
let server = bind_rustls(addr, conf).handle(handle).serve(app);
|
||||
|
||||
#[cfg(feature = "systemd")]
|
||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]);
|
||||
|
||||
server.await?
|
||||
bind_rustls(addr, conf).handle(handle).serve(app).await?;
|
||||
}
|
||||
None => {
|
||||
let server = bind(addr).handle(handle).serve(app);
|
||||
|
||||
#[cfg(feature = "systemd")]
|
||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]);
|
||||
|
||||
server.await?
|
||||
bind(addr).handle(handle).serve(app).await?;
|
||||
}
|
||||
}
|
||||
|
||||
// On shutdown
|
||||
info!(target: "shutdown-sync", "Received shutdown notification, notifying sync helpers...");
|
||||
services().globals.rotate.fire();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn spawn_task<B: Send + 'static>(
|
||||
req: axum::http::Request<B>,
|
||||
next: axum::middleware::Next<B>,
|
||||
) -> std::result::Result<axum::response::Response, StatusCode> {
|
||||
if services().globals.shutdown.load(atomic::Ordering::Relaxed) {
|
||||
return Err(StatusCode::SERVICE_UNAVAILABLE);
|
||||
}
|
||||
tokio::spawn(next.run(req))
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
|
||||
async fn unrecognized_method<B: Send>(
|
||||
async fn unrecognized_method<B>(
|
||||
req: axum::http::Request<B>,
|
||||
next: axum::middleware::Next<B>,
|
||||
) -> std::result::Result<axum::response::Response, StatusCode> {
|
||||
|
@ -238,10 +210,8 @@ async fn unrecognized_method<B: Send>(
|
|||
if inner.status() == axum::http::StatusCode::METHOD_NOT_ALLOWED {
|
||||
warn!("Method not allowed: {method} {uri}");
|
||||
return Ok(RumaResponse(UiaaResponse::MatrixError(RumaError {
|
||||
body: ErrorBody::Standard {
|
||||
kind: ErrorKind::Unrecognized,
|
||||
message: "M_UNRECOGNIZED: Unrecognized request".to_owned(),
|
||||
},
|
||||
kind: ErrorKind::Unrecognized,
|
||||
message: "M_UNRECOGNIZED: Unrecognized request".to_owned(),
|
||||
status_code: StatusCode::METHOD_NOT_ALLOWED,
|
||||
}))
|
||||
.into_response());
|
||||
|
@ -359,7 +329,6 @@ 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)
|
||||
|
@ -385,11 +354,6 @@ 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",
|
||||
|
@ -403,7 +367,6 @@ fn routes() -> Router {
|
|||
.ruma_route(server_server::get_public_rooms_filtered_route)
|
||||
.ruma_route(server_server::send_transaction_message_route)
|
||||
.ruma_route(server_server::get_event_route)
|
||||
.ruma_route(server_server::get_backfill_route)
|
||||
.ruma_route(server_server::get_missing_events_route)
|
||||
.ruma_route(server_server::get_event_authorization_route)
|
||||
.ruma_route(server_server::get_room_state_route)
|
||||
|
@ -425,8 +388,7 @@ fn routes() -> Router {
|
|||
"/_matrix/client/v3/rooms/:room_id/initialSync",
|
||||
get(initial_sync),
|
||||
)
|
||||
.route("/", get(it_works))
|
||||
.fallback(not_found)
|
||||
.fallback(not_found.into_service())
|
||||
}
|
||||
|
||||
async fn shutdown_signal(handle: ServerHandle) {
|
||||
|
@ -456,11 +418,6 @@ async fn shutdown_signal(handle: ServerHandle) {
|
|||
|
||||
warn!("Received {}, shutting down...", sig);
|
||||
handle.graceful_shutdown(Some(Duration::from_secs(30)));
|
||||
|
||||
services().globals.shutdown();
|
||||
|
||||
#[cfg(feature = "systemd")]
|
||||
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Stopping]);
|
||||
}
|
||||
|
||||
async fn not_found(uri: Uri) -> impl IntoResponse {
|
||||
|
@ -475,10 +432,6 @@ 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
|
||||
|
@ -514,7 +467,7 @@ macro_rules! impl_ruma_handler {
|
|||
Fut: Future<Output = Result<Req::OutgoingResponse, E>>
|
||||
+ Send,
|
||||
E: IntoResponse,
|
||||
$( $ty: FromRequestParts<()> + Send + 'static, )*
|
||||
$( $ty: FromRequest<axum::body::Body> + Send + 'static, )*
|
||||
{
|
||||
fn add_to_router(self, mut router: Router) -> Router {
|
||||
let meta = Req::METADATA;
|
||||
|
@ -554,24 +507,6 @@ fn method_to_filter(method: Method) -> MethodFilter {
|
|||
Method::POST => MethodFilter::POST,
|
||||
Method::PUT => MethodFilter::PUT,
|
||||
Method::TRACE => MethodFilter::TRACE,
|
||||
m => panic!("Unsupported HTTP method: {m:?}"),
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::{
|
||||
collections::BTreeMap,
|
||||
convert::{TryFrom, TryInto},
|
||||
sync::{Arc, RwLock},
|
||||
sync::Arc,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
|
@ -21,7 +21,7 @@ use ruma::{
|
|||
power_levels::RoomPowerLevelsEventContent,
|
||||
topic::RoomTopicEventContent,
|
||||
},
|
||||
TimelineEventType,
|
||||
RoomEventType,
|
||||
},
|
||||
EventId, OwnedRoomAliasId, RoomAliasId, RoomId, RoomVersionId, ServerName, UserId,
|
||||
};
|
||||
|
@ -50,7 +50,7 @@ enum AdminCommand {
|
|||
/// Registering a new bridge using the ID of an existing bridge will replace
|
||||
/// the old one.
|
||||
///
|
||||
/// [commandbody]()
|
||||
/// [commandbody]
|
||||
/// # ```
|
||||
/// # yaml content here
|
||||
/// # ```
|
||||
|
@ -96,7 +96,7 @@ enum AdminCommand {
|
|||
/// Removing a mass amount of users from a room may cause a significant amount of leave events.
|
||||
/// The time to leave rooms may depend significantly on joined rooms and servers.
|
||||
///
|
||||
/// [commandbody]()
|
||||
/// [commandbody]
|
||||
/// # ```
|
||||
/// # User list here
|
||||
/// # ```
|
||||
|
@ -121,7 +121,7 @@ enum AdminCommand {
|
|||
/// The PDU event is only checked for validity and is not added to the
|
||||
/// database.
|
||||
///
|
||||
/// [commandbody]()
|
||||
/// [commandbody]
|
||||
/// # ```
|
||||
/// # PDU json content here
|
||||
/// # ```
|
||||
|
@ -134,13 +134,7 @@ enum AdminCommand {
|
|||
},
|
||||
|
||||
/// Print database memory usage statistics
|
||||
MemoryUsage,
|
||||
|
||||
/// Clears all of Conduit's database caches with index smaller than the amount
|
||||
ClearDatabaseCaches { amount: u32 },
|
||||
|
||||
/// Clears all of Conduit's service caches with index smaller than the amount
|
||||
ClearServiceCaches { amount: u32 },
|
||||
DatabaseMemoryUsage,
|
||||
|
||||
/// Show configuration values
|
||||
ShowConfig,
|
||||
|
@ -163,20 +157,6 @@ enum AdminCommand {
|
|||
DisableRoom { room_id: Box<RoomId> },
|
||||
/// Enables incoming federation handling for a room again.
|
||||
EnableRoom { room_id: Box<RoomId> },
|
||||
|
||||
/// Verify json signatures
|
||||
/// [commandbody]()
|
||||
/// # ```
|
||||
/// # json here
|
||||
/// # ```
|
||||
SignJson,
|
||||
|
||||
/// Verify json signatures
|
||||
/// [commandbody]()
|
||||
/// # ```
|
||||
/// # json here
|
||||
/// # ```
|
||||
VerifyJson,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -232,7 +212,7 @@ impl Service {
|
|||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMessage,
|
||||
event_type: RoomEventType::RoomMessage,
|
||||
content: to_raw_value(&message)
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
|
@ -287,7 +267,7 @@ impl Service {
|
|||
|
||||
// Parse and process a message from the admin room
|
||||
async fn process_admin_message(&self, room_message: String) -> RoomMessageEventContent {
|
||||
let mut lines = room_message.lines().filter(|l| !l.trim().is_empty());
|
||||
let mut lines = room_message.lines();
|
||||
let command_line = lines.next().expect("each string has at least one line");
|
||||
let body: Vec<_> = lines.collect();
|
||||
|
||||
|
@ -307,11 +287,13 @@ impl Service {
|
|||
Err(error) => {
|
||||
let markdown_message = format!(
|
||||
"Encountered an error while handling the command:\n\
|
||||
```\n{error}\n```",
|
||||
```\n{}\n```",
|
||||
error,
|
||||
);
|
||||
let html_message = format!(
|
||||
"Encountered an error while handling the command:\n\
|
||||
<pre>\n{error}\n</pre>",
|
||||
<pre>\n{}\n</pre>",
|
||||
error,
|
||||
);
|
||||
|
||||
RoomMessageEventContent::text_html(markdown_message, html_message)
|
||||
|
@ -356,14 +338,17 @@ impl Service {
|
|||
match parsed_config {
|
||||
Ok(yaml) => match services().appservice.register_appservice(yaml) {
|
||||
Ok(id) => RoomMessageEventContent::text_plain(format!(
|
||||
"Appservice registered with ID: {id}."
|
||||
"Appservice registered with ID: {}.",
|
||||
id
|
||||
)),
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to register appservice: {e}"
|
||||
"Failed to register appservice: {}",
|
||||
e
|
||||
)),
|
||||
},
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||
"Could not parse appservice config: {e}"
|
||||
"Could not parse appservice config: {}",
|
||||
e
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
|
@ -380,7 +365,8 @@ impl Service {
|
|||
{
|
||||
Ok(()) => RoomMessageEventContent::text_plain("Appservice unregistered."),
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to unregister appservice: {e}"
|
||||
"Failed to unregister appservice: {}",
|
||||
e
|
||||
)),
|
||||
},
|
||||
AdminCommand::ListAppservices => {
|
||||
|
@ -473,7 +459,8 @@ impl Service {
|
|||
.count();
|
||||
let elapsed = start.elapsed();
|
||||
RoomMessageEventContent::text_plain(format!(
|
||||
"Loaded auth chain with length {count} in {elapsed:?}"
|
||||
"Loaded auth chain with length {} in {:?}",
|
||||
count, elapsed
|
||||
))
|
||||
} else {
|
||||
RoomMessageEventContent::text_plain("Event not found.")
|
||||
|
@ -487,26 +474,30 @@ impl Service {
|
|||
Ok(value) => {
|
||||
match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
|
||||
Ok(hash) => {
|
||||
let event_id = EventId::parse(format!("${hash}"));
|
||||
let event_id = EventId::parse(format!("${}", hash));
|
||||
|
||||
match serde_json::from_value::<PduEvent>(
|
||||
serde_json::to_value(value).expect("value is json"),
|
||||
) {
|
||||
Ok(pdu) => RoomMessageEventContent::text_plain(format!(
|
||||
"EventId: {event_id:?}\n{pdu:#?}"
|
||||
"EventId: {:?}\n{:#?}",
|
||||
event_id, pdu
|
||||
)),
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||
"EventId: {event_id:?}\nCould not parse event: {e}"
|
||||
"EventId: {:?}\nCould not parse event: {}",
|
||||
event_id, e
|
||||
)),
|
||||
}
|
||||
}
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||
"Could not parse PDU JSON: {e:?}"
|
||||
"Could not parse PDU JSON: {:?}",
|
||||
e
|
||||
)),
|
||||
}
|
||||
}
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||
"Invalid json in command body: {e}"
|
||||
"Invalid json in command body: {}",
|
||||
e
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
|
@ -551,24 +542,13 @@ impl Service {
|
|||
None => RoomMessageEventContent::text_plain("PDU not found."),
|
||||
}
|
||||
}
|
||||
AdminCommand::MemoryUsage => {
|
||||
let response1 = services().memory_usage();
|
||||
let response2 = services().globals.db.memory_usage();
|
||||
|
||||
RoomMessageEventContent::text_plain(format!(
|
||||
"Services:\n{response1}\n\nDatabase:\n{response2}"
|
||||
))
|
||||
}
|
||||
AdminCommand::ClearDatabaseCaches { amount } => {
|
||||
services().globals.db.clear_caches(amount);
|
||||
|
||||
RoomMessageEventContent::text_plain("Done.")
|
||||
}
|
||||
AdminCommand::ClearServiceCaches { amount } => {
|
||||
services().clear_caches(amount);
|
||||
|
||||
RoomMessageEventContent::text_plain("Done.")
|
||||
}
|
||||
AdminCommand::DatabaseMemoryUsage => match services().globals.db.memory_usage() {
|
||||
Ok(response) => RoomMessageEventContent::text_plain(response),
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to get database memory usage: {}",
|
||||
e
|
||||
)),
|
||||
},
|
||||
AdminCommand::ShowConfig => {
|
||||
// Construct and send the response
|
||||
RoomMessageEventContent::text_plain(format!("{}", services().globals.config))
|
||||
|
@ -581,13 +561,15 @@ impl Service {
|
|||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"The supplied username is not a valid username: {e}"
|
||||
"The supplied username is not a valid username: {}",
|
||||
e
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
// Check if the specified user is valid
|
||||
if !services().users.exists(&user_id)?
|
||||
|| services().users.is_deactivated(&user_id)?
|
||||
|| user_id
|
||||
== UserId::parse_with_server_name(
|
||||
"conduit",
|
||||
|
@ -596,7 +578,7 @@ impl Service {
|
|||
.expect("conduit user exists")
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"The specified user does not exist!",
|
||||
"The specified user does not exist or is deactivated!",
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -607,10 +589,12 @@ impl Service {
|
|||
.set_password(&user_id, Some(new_password.as_str()))
|
||||
{
|
||||
Ok(()) => RoomMessageEventContent::text_plain(format!(
|
||||
"Successfully reset the password for user {user_id}: {new_password}"
|
||||
"Successfully reset the password for user {}: {}",
|
||||
user_id, new_password
|
||||
)),
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||
"Couldn't reset the password for user {user_id}: {e}"
|
||||
"Couldn't reset the password for user {}: {}",
|
||||
user_id, e
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
@ -625,18 +609,19 @@ impl Service {
|
|||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"The supplied username is not a valid username: {e}"
|
||||
"The supplied username is not a valid username: {}",
|
||||
e
|
||||
)))
|
||||
}
|
||||
};
|
||||
if user_id.is_historical() {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Userid {user_id} is not allowed due to historical"
|
||||
"userid {user_id} is not allowed due to historical"
|
||||
)));
|
||||
}
|
||||
if services().users.exists(&user_id)? {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Userid {user_id} already exists"
|
||||
"userid {user_id} already exists"
|
||||
)));
|
||||
}
|
||||
// Create user
|
||||
|
@ -691,7 +676,8 @@ impl Service {
|
|||
let user_id = Arc::<UserId>::from(user_id);
|
||||
if services().users.exists(&user_id)? {
|
||||
RoomMessageEventContent::text_plain(format!(
|
||||
"Making {user_id} leave all rooms before deactivation..."
|
||||
"Making {} leave all rooms before deactivation...",
|
||||
user_id
|
||||
));
|
||||
|
||||
services().users.deactivate_account(&user_id)?;
|
||||
|
@ -701,11 +687,13 @@ impl Service {
|
|||
}
|
||||
|
||||
RoomMessageEventContent::text_plain(format!(
|
||||
"User {user_id} has been deactivated"
|
||||
"User {} has been deactivated",
|
||||
user_id
|
||||
))
|
||||
} else {
|
||||
RoomMessageEventContent::text_plain(format!(
|
||||
"User {user_id} doesn't exist on this server"
|
||||
"User {} doesn't exist on this server",
|
||||
user_id
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -721,7 +709,8 @@ impl Service {
|
|||
Ok(user_id) => user_ids.push(user_id),
|
||||
Err(_) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"{username} is not a valid username"
|
||||
"{} is not a valid username",
|
||||
username
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
@ -757,7 +746,8 @@ impl Service {
|
|||
|
||||
if admins.is_empty() {
|
||||
RoomMessageEventContent::text_plain(format!(
|
||||
"Deactivated {deactivation_count} accounts."
|
||||
"Deactivated {} accounts.",
|
||||
deactivation_count
|
||||
))
|
||||
} else {
|
||||
RoomMessageEventContent::text_plain(format!("Deactivated {} accounts.\nSkipped admin accounts: {:?}. Use --force to deactivate admin accounts", deactivation_count, admins.join(", ")))
|
||||
|
@ -768,60 +758,6 @@ impl Service {
|
|||
)
|
||||
}
|
||||
}
|
||||
AdminCommand::SignJson => {
|
||||
if body.len() > 2 && body[0].trim() == "```" && body.last().unwrap().trim() == "```"
|
||||
{
|
||||
let string = body[1..body.len() - 1].join("\n");
|
||||
match serde_json::from_str(&string) {
|
||||
Ok(mut value) => {
|
||||
ruma::signatures::sign_json(
|
||||
services().globals.server_name().as_str(),
|
||||
services().globals.keypair(),
|
||||
&mut value,
|
||||
)
|
||||
.expect("our request json is what ruma expects");
|
||||
let json_text = serde_json::to_string_pretty(&value)
|
||||
.expect("canonical json is valid json");
|
||||
RoomMessageEventContent::text_plain(json_text)
|
||||
}
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!("Invalid json: {e}")),
|
||||
}
|
||||
} else {
|
||||
RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
)
|
||||
}
|
||||
}
|
||||
AdminCommand::VerifyJson => {
|
||||
if body.len() > 2 && body[0].trim() == "```" && body.last().unwrap().trim() == "```"
|
||||
{
|
||||
let string = body[1..body.len() - 1].join("\n");
|
||||
match serde_json::from_str(&string) {
|
||||
Ok(value) => {
|
||||
let pub_key_map = RwLock::new(BTreeMap::new());
|
||||
|
||||
services()
|
||||
.rooms
|
||||
.event_handler
|
||||
.fetch_required_signing_keys(&value, &pub_key_map)
|
||||
.await?;
|
||||
|
||||
let pub_key_map = pub_key_map.read().unwrap();
|
||||
match ruma::signatures::verify_json(&pub_key_map, &value) {
|
||||
Ok(_) => RoomMessageEventContent::text_plain("Signature correct"),
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||
"Signature verification failed: {e}"
|
||||
)),
|
||||
}
|
||||
}
|
||||
Err(e) => RoomMessageEventContent::text_plain(format!("Invalid json: {e}")),
|
||||
}
|
||||
} else {
|
||||
RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(reply_message_content)
|
||||
|
@ -831,8 +767,8 @@ impl Service {
|
|||
fn usage_to_html(&self, text: &str, server_name: &ServerName) -> String {
|
||||
// Replace `@conduit:servername:-subcmdname` with `@conduit:servername: subcmdname`
|
||||
let text = text.replace(
|
||||
&format!("@conduit:{server_name}:-"),
|
||||
&format!("@conduit:{server_name}: "),
|
||||
&format!("@conduit:{}:-", server_name),
|
||||
&format!("@conduit:{}: ", server_name),
|
||||
);
|
||||
|
||||
// For the conduit admin room, subcommands become main commands
|
||||
|
@ -858,15 +794,12 @@ impl Service {
|
|||
.expect("Regex compilation should not fail");
|
||||
let text = re.replace_all(&text, "<code>$1</code>: $4");
|
||||
|
||||
// Look for a `[commandbody]()` tag. If it exists, use all lines below it that
|
||||
// Look for a `[commandbody]` tag. If it exists, use all lines below it that
|
||||
// start with a `#` in the USAGE section.
|
||||
let mut text_lines: Vec<&str> = text.lines().collect();
|
||||
let mut command_body = String::new();
|
||||
|
||||
if let Some(line_index) = text_lines
|
||||
.iter()
|
||||
.position(|line| *line == "[commandbody]()")
|
||||
{
|
||||
if let Some(line_index) = text_lines.iter().position(|line| *line == "[commandbody]") {
|
||||
text_lines.remove(line_index);
|
||||
|
||||
while text_lines
|
||||
|
@ -935,7 +868,7 @@ impl Service {
|
|||
|
||||
services().users.create(&conduit_user, None)?;
|
||||
|
||||
let mut content = RoomCreateEventContent::new_v1(conduit_user.clone());
|
||||
let mut content = RoomCreateEventContent::new(conduit_user.clone());
|
||||
content.federate = true;
|
||||
content.predecessor = None;
|
||||
content.room_version = services().globals.default_room_version();
|
||||
|
@ -943,7 +876,7 @@ impl Service {
|
|||
// 1. The room create event
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomCreate,
|
||||
event_type: RoomEventType::RoomCreate,
|
||||
content: to_raw_value(&content).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
|
@ -957,7 +890,7 @@ impl Service {
|
|||
// 2. Make conduit bot join
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
event_type: RoomEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
membership: MembershipState::Join,
|
||||
displayname: None,
|
||||
|
@ -984,7 +917,7 @@ impl Service {
|
|||
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomPowerLevels,
|
||||
event_type: RoomEventType::RoomPowerLevels,
|
||||
content: to_raw_value(&RoomPowerLevelsEventContent {
|
||||
users,
|
||||
..Default::default()
|
||||
|
@ -1002,7 +935,7 @@ impl Service {
|
|||
// 4.1 Join Rules
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomJoinRules,
|
||||
event_type: RoomEventType::RoomJoinRules,
|
||||
content: to_raw_value(&RoomJoinRulesEventContent::new(JoinRule::Invite))
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
|
@ -1017,7 +950,7 @@ impl Service {
|
|||
// 4.2 History Visibility
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomHistoryVisibility,
|
||||
event_type: RoomEventType::RoomHistoryVisibility,
|
||||
content: to_raw_value(&RoomHistoryVisibilityEventContent::new(
|
||||
HistoryVisibility::Shared,
|
||||
))
|
||||
|
@ -1034,7 +967,7 @@ impl Service {
|
|||
// 4.3 Guest Access
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomGuestAccess,
|
||||
event_type: RoomEventType::RoomGuestAccess,
|
||||
content: to_raw_value(&RoomGuestAccessEventContent::new(GuestAccess::Forbidden))
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
|
@ -1050,8 +983,8 @@ impl Service {
|
|||
let room_name = format!("{} Admin Room", services().globals.server_name());
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomName,
|
||||
content: to_raw_value(&RoomNameEventContent::new(room_name))
|
||||
event_type: RoomEventType::RoomName,
|
||||
content: to_raw_value(&RoomNameEventContent::new(Some(room_name)))
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
|
@ -1064,7 +997,7 @@ impl Service {
|
|||
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomTopic,
|
||||
event_type: RoomEventType::RoomTopic,
|
||||
content: to_raw_value(&RoomTopicEventContent {
|
||||
topic: format!("Manage {}", services().globals.server_name()),
|
||||
})
|
||||
|
@ -1085,7 +1018,7 @@ impl Service {
|
|||
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomCanonicalAlias,
|
||||
event_type: RoomEventType::RoomCanonicalAlias,
|
||||
content: to_raw_value(&RoomCanonicalAliasEventContent {
|
||||
alias: Some(alias.clone()),
|
||||
alt_aliases: Vec::new(),
|
||||
|
@ -1142,7 +1075,7 @@ impl Service {
|
|||
// Invite and join the real user
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
event_type: RoomEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
membership: MembershipState::Invite,
|
||||
displayname: None,
|
||||
|
@ -1164,7 +1097,7 @@ impl Service {
|
|||
)?;
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
event_type: RoomEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
membership: MembershipState::Join,
|
||||
displayname: Some(displayname),
|
||||
|
@ -1192,7 +1125,7 @@ impl Service {
|
|||
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomPowerLevels,
|
||||
event_type: RoomEventType::RoomPowerLevels,
|
||||
content: to_raw_value(&RoomPowerLevelsEventContent {
|
||||
users,
|
||||
..Default::default()
|
||||
|
@ -1210,7 +1143,7 @@ impl Service {
|
|||
// Send welcome message
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMessage,
|
||||
event_type: RoomEventType::RoomMessage,
|
||||
content: to_raw_value(&RoomMessageEventContent::text_html(
|
||||
format!("## Thank you for trying out Conduit!\n\nConduit is currently in Beta. This means you can join and participate in most Matrix rooms, but not all features are supported and you might run into bugs from time to time.\n\nHelpful links:\n> Website: https://conduit.rs\n> Git and Documentation: https://gitlab.com/famedly/conduit\n> Report issues: https://gitlab.com/famedly/conduit/-/issues\n\nFor a list of available commands, send the following message in this room: `@conduit:{}: --help`\n\nHere are some rooms you can join (by typing the command):\n\nConduit room (Ask questions and get notified on updates):\n`/join #conduit:fachschaften.org`\n\nConduit lounge (Off-topic, only Conduit users are allowed to join)\n`/join #conduit-lounge:conduit.rs`", services().globals.server_name()),
|
||||
format!("<h2>Thank you for trying out Conduit!</h2>\n<p>Conduit is currently in Beta. This means you can join and participate in most Matrix rooms, but not all features are supported and you might run into bugs from time to time.</p>\n<p>Helpful links:</p>\n<blockquote>\n<p>Website: https://conduit.rs<br>Git and Documentation: https://gitlab.com/famedly/conduit<br>Report issues: https://gitlab.com/famedly/conduit/-/issues</p>\n</blockquote>\n<p>For a list of available commands, send the following message in this room: <code>@conduit:{}: --help</code></p>\n<p>Here are some rooms you can join (by typing the command):</p>\n<p>Conduit room (Ask questions and get notified on updates):<br><code>/join #conduit:fachschaften.org</code></p>\n<p>Conduit lounge (Off-topic, only Conduit users are allowed to join)<br><code>/join #conduit-lounge:conduit.rs</code></p>\n", services().globals.server_name()),
|
||||
|
|
|
@ -13,12 +13,9 @@ 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) -> String;
|
||||
fn clear_caches(&self, amount: u32);
|
||||
fn memory_usage(&self) -> Result<String>;
|
||||
fn load_keypair(&self) -> Result<Ed25519KeyPair>;
|
||||
fn remove_keypair(&self) -> Result<()>;
|
||||
fn add_signing_key(
|
||||
|
|
|
@ -1,19 +1,12 @@
|
|||
mod data;
|
||||
pub use data::Data;
|
||||
use ruma::{
|
||||
serde::Base64, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedServerName,
|
||||
OwnedServerSigningKeyId, OwnedUserId,
|
||||
OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedServerName, OwnedServerSigningKeyId, OwnedUserId,
|
||||
};
|
||||
|
||||
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 crate::{Config, Error, Result};
|
||||
use ruma::{
|
||||
api::{
|
||||
client::sync::sync_events,
|
||||
|
@ -23,24 +16,17 @@ use ruma::{
|
|||
};
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
error::Error as StdError,
|
||||
fs,
|
||||
future::{self, Future},
|
||||
iter,
|
||||
future::Future,
|
||||
net::{IpAddr, SocketAddr},
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
atomic::{self, AtomicBool},
|
||||
Arc, Mutex, RwLock,
|
||||
},
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tokio::sync::{broadcast, watch::Receiver, Mutex as TokioMutex, Semaphore};
|
||||
use tracing::{error, info};
|
||||
use tracing::error;
|
||||
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
|
||||
|
@ -64,7 +50,6 @@ 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<()>>>>,
|
||||
|
@ -73,8 +58,6 @@ pub struct Service {
|
|||
pub roomid_federationhandletime: RwLock<HashMap<OwnedRoomId, (OwnedEventId, Instant)>>,
|
||||
pub stateres_mutex: Arc<Mutex<()>>,
|
||||
pub rotate: RotationHandler,
|
||||
|
||||
pub shutdown: AtomicBool,
|
||||
}
|
||||
|
||||
/// Handles "rotation" of long-polling requests. "Rotation" in this context is similar to "rotation" of log files and the like.
|
||||
|
@ -107,45 +90,6 @@ impl Default for RotationHandler {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Resolver {
|
||||
inner: GaiResolver,
|
||||
overrides: Arc<RwLock<TlsNameMap>>,
|
||||
}
|
||||
|
||||
impl Resolver {
|
||||
pub fn new(overrides: Arc<RwLock<TlsNameMap>>) -> Self {
|
||||
Resolver {
|
||||
inner: GaiResolver::new(),
|
||||
overrides,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve for Resolver {
|
||||
fn resolve(&self, name: Name) -> Resolving {
|
||||
self.overrides
|
||||
.read()
|
||||
.expect("lock should not be poisoned")
|
||||
.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();
|
||||
|
@ -167,8 +111,14 @@ 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)?
|
||||
.dns_resolver(Arc::new(Resolver::new(tls_name_override.clone())))
|
||||
.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))
|
||||
})
|
||||
.build()?;
|
||||
|
||||
// Supported and stable room versions
|
||||
|
@ -202,7 +152,6 @@ 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()),
|
||||
|
@ -211,7 +160,6 @@ impl Service {
|
|||
stateres_mutex: Arc::new(Mutex::new(())),
|
||||
sync_receivers: RwLock::new(HashMap::new()),
|
||||
rotate: RotationHandler::new(),
|
||||
shutdown: AtomicBool::new(false),
|
||||
};
|
||||
|
||||
fs::create_dir_all(s.get_media_folder())?;
|
||||
|
@ -220,7 +168,7 @@ impl Service {
|
|||
.supported_room_versions()
|
||||
.contains(&s.config.default_room_version)
|
||||
{
|
||||
error!(config=?s.config.default_room_version, fallback=?crate::config::default_default_room_version(), "Room version in config isn't supported, falling back to default version");
|
||||
error!("Room version in config isn't supported, falling back to default version");
|
||||
s.config.default_room_version = crate::config::default_default_room_version();
|
||||
};
|
||||
|
||||
|
@ -254,16 +202,6 @@ 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
|
||||
}
|
||||
|
@ -272,6 +210,10 @@ 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()
|
||||
}
|
||||
|
@ -280,10 +222,6 @@ impl Service {
|
|||
self.config.max_request_size
|
||||
}
|
||||
|
||||
pub fn max_fetch_prev_events(&self) -> u16 {
|
||||
self.config.max_fetch_prev_events
|
||||
}
|
||||
|
||||
pub fn allow_registration(&self) -> bool {
|
||||
self.config.allow_registration
|
||||
}
|
||||
|
@ -312,10 +250,6 @@ 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
|
||||
}
|
||||
|
@ -378,19 +312,7 @@ impl Service {
|
|||
&self,
|
||||
origin: &ServerName,
|
||||
) -> Result<BTreeMap<OwnedServerSigningKeyId, VerifyKey>> {
|
||||
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)
|
||||
self.db.signing_keys_for(origin)
|
||||
}
|
||||
|
||||
pub fn database_version(&self) -> Result<u64> {
|
||||
|
@ -412,25 +334,13 @@ impl Service {
|
|||
let mut r = PathBuf::new();
|
||||
r.push(self.config.database_path.clone());
|
||||
r.push("media");
|
||||
r.push(general_purpose::URL_SAFE_NO_PAD.encode(key));
|
||||
r.push(base64::encode_config(key, base64::URL_SAFE_NO_PAD));
|
||||
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
|
||||
info!(target: "shutdown-sync", "Received shutdown notification, notifying sync helpers...");
|
||||
services().globals.rotate.fire();
|
||||
}
|
||||
}
|
||||
|
||||
fn reqwest_client_builder(config: &Config) -> Result<reqwest::ClientBuilder> {
|
||||
let mut reqwest_client_builder = reqwest::Client::builder()
|
||||
.pool_max_idle_per_host(0)
|
||||
.connect_timeout(Duration::from_secs(30))
|
||||
.timeout(Duration::from_secs(60 * 3));
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ use image::imageops::FilterType;
|
|||
|
||||
use tokio::{
|
||||
fs::File,
|
||||
io::{AsyncReadExt, AsyncWriteExt, BufReader},
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
};
|
||||
|
||||
pub struct FileMeta {
|
||||
|
@ -70,9 +70,7 @@ impl Service {
|
|||
{
|
||||
let path = services().globals.get_media_file(&key);
|
||||
let mut file = Vec::new();
|
||||
BufReader::new(File::open(path).await?)
|
||||
.read_to_end(&mut file)
|
||||
.await?;
|
||||
File::open(path).await?.read_to_end(&mut file).await?;
|
||||
|
||||
Ok(Some(FileMeta {
|
||||
content_disposition,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
|
@ -77,15 +77,7 @@ impl Services {
|
|||
search: rooms::search::Service { db },
|
||||
short: rooms::short::Service { db },
|
||||
state: rooms::state::Service { db },
|
||||
state_accessor: rooms::state_accessor::Service {
|
||||
db,
|
||||
server_visibility_cache: Mutex::new(LruCache::new(
|
||||
(100.0 * config.conduit_cache_capacity_modifier) as usize,
|
||||
)),
|
||||
user_visibility_cache: Mutex::new(LruCache::new(
|
||||
(100.0 * config.conduit_cache_capacity_modifier) as usize,
|
||||
)),
|
||||
},
|
||||
state_accessor: rooms::state_accessor::Service { db },
|
||||
state_cache: rooms::state_cache::Service { db },
|
||||
state_compressor: rooms::state_compressor::Service {
|
||||
db,
|
||||
|
@ -97,18 +89,11 @@ 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,
|
||||
connections: Mutex::new(BTreeMap::new()),
|
||||
},
|
||||
users: users::Service { db },
|
||||
account_data: account_data::Service { db },
|
||||
admin: admin::Service::build(),
|
||||
key_backups: key_backups::Service { db },
|
||||
|
@ -118,109 +103,4 @@ impl Services {
|
|||
globals: globals::Service::load(db, config)?,
|
||||
})
|
||||
}
|
||||
fn memory_usage(&self) -> String {
|
||||
let lazy_load_waiting = self
|
||||
.rooms
|
||||
.lazy_loading
|
||||
.lazy_load_waiting
|
||||
.lock()
|
||||
.unwrap()
|
||||
.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()
|
||||
.unwrap()
|
||||
.len();
|
||||
let roomid_spacechunk_cache = self
|
||||
.rooms
|
||||
.spaces
|
||||
.roomid_spacechunk_cache
|
||||
.lock()
|
||||
.unwrap()
|
||||
.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}\
|
||||
"
|
||||
)
|
||||
}
|
||||
fn clear_caches(&self, amount: u32) {
|
||||
if amount > 0 {
|
||||
self.rooms
|
||||
.lazy_loading
|
||||
.lazy_load_waiting
|
||||
.lock()
|
||||
.unwrap()
|
||||
.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()
|
||||
.unwrap()
|
||||
.clear();
|
||||
}
|
||||
if amount > 5 {
|
||||
self.rooms
|
||||
.spaces
|
||||
.roomid_spacechunk_cache
|
||||
.lock()
|
||||
.unwrap()
|
||||
.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use crate::Error;
|
||||
use crate::{services, Error};
|
||||
use ruma::{
|
||||
events::{
|
||||
room::member::RoomMemberEventContent, space::child::HierarchySpaceChildEvent,
|
||||
AnyEphemeralRoomEvent, AnyMessageLikeEvent, AnyStateEvent, AnyStrippedStateEvent,
|
||||
AnySyncStateEvent, AnySyncTimelineEvent, AnyTimelineEvent, StateEvent, TimelineEventType,
|
||||
room::member::RoomMemberEventContent, AnyEphemeralRoomEvent, AnyStateEvent,
|
||||
AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent, AnyTimelineEvent,
|
||||
RoomEventType, StateEvent,
|
||||
},
|
||||
serde::Raw,
|
||||
state_res, CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch,
|
||||
OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, RoomVersionId, UInt, UserId,
|
||||
OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UInt, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{
|
||||
|
@ -31,7 +31,7 @@ pub struct PduEvent {
|
|||
pub sender: OwnedUserId,
|
||||
pub origin_server_ts: UInt,
|
||||
#[serde(rename = "type")]
|
||||
pub kind: TimelineEventType,
|
||||
pub kind: RoomEventType,
|
||||
pub content: Box<RawJsonValue>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub state_key: Option<String>,
|
||||
|
@ -53,10 +53,10 @@ impl PduEvent {
|
|||
self.unsigned = None;
|
||||
|
||||
let allowed: &[&str] = match self.kind {
|
||||
TimelineEventType::RoomMember => &["join_authorised_via_users_server", "membership"],
|
||||
TimelineEventType::RoomCreate => &["creator"],
|
||||
TimelineEventType::RoomJoinRules => &["join_rule"],
|
||||
TimelineEventType::RoomPowerLevels => &[
|
||||
RoomEventType::RoomMember => &["join_authorised_via_users_server", "membership"],
|
||||
RoomEventType::RoomCreate => &["creator"],
|
||||
RoomEventType::RoomJoinRules => &["join_rule"],
|
||||
RoomEventType::RoomPowerLevels => &[
|
||||
"ban",
|
||||
"events",
|
||||
"events_default",
|
||||
|
@ -66,7 +66,7 @@ impl PduEvent {
|
|||
"users",
|
||||
"users_default",
|
||||
],
|
||||
TimelineEventType::RoomHistoryVisibility => &["history_visibility"],
|
||||
RoomEventType::RoomHistoryVisibility => &["history_visibility"],
|
||||
_ => &[],
|
||||
};
|
||||
|
||||
|
@ -103,19 +103,6 @@ impl PduEvent {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_age(&mut self) -> crate::Result<()> {
|
||||
let mut unsigned: BTreeMap<String, Box<RawJsonValue>> = self
|
||||
.unsigned
|
||||
.as_ref()
|
||||
.map_or_else(|| Ok(BTreeMap::new()), |u| serde_json::from_str(u.get()))
|
||||
.map_err(|_| Error::bad_database("Invalid unsigned in pdu event"))?;
|
||||
|
||||
unsigned.insert("age".to_owned(), to_raw_value(&1).unwrap());
|
||||
self.unsigned = Some(to_raw_value(&unsigned).expect("unsigned is valid"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn to_sync_room_event(&self) -> Raw<AnySyncTimelineEvent> {
|
||||
let mut json = json!({
|
||||
|
@ -124,11 +111,9 @@ impl PduEvent {
|
|||
"event_id": self.event_id,
|
||||
"sender": self.sender,
|
||||
"origin_server_ts": self.origin_server_ts,
|
||||
"unsigned": self.unsigned,
|
||||
});
|
||||
|
||||
if let Some(unsigned) = &self.unsigned {
|
||||
json["unsigned"] = json!(unsigned);
|
||||
}
|
||||
if let Some(state_key) = &self.state_key {
|
||||
json["state_key"] = json!(state_key);
|
||||
}
|
||||
|
@ -148,12 +133,10 @@ impl PduEvent {
|
|||
"event_id": self.event_id,
|
||||
"sender": self.sender,
|
||||
"origin_server_ts": self.origin_server_ts,
|
||||
"unsigned": self.unsigned,
|
||||
"room_id": self.room_id,
|
||||
});
|
||||
|
||||
if let Some(unsigned) = &self.unsigned {
|
||||
json["unsigned"] = json!(unsigned);
|
||||
}
|
||||
if let Some(state_key) = &self.state_key {
|
||||
json["state_key"] = json!(state_key);
|
||||
}
|
||||
|
@ -172,36 +155,10 @@ impl PduEvent {
|
|||
"event_id": self.event_id,
|
||||
"sender": self.sender,
|
||||
"origin_server_ts": self.origin_server_ts,
|
||||
"unsigned": self.unsigned,
|
||||
"room_id": self.room_id,
|
||||
});
|
||||
|
||||
if let Some(unsigned) = &self.unsigned {
|
||||
json["unsigned"] = json!(unsigned);
|
||||
}
|
||||
if let Some(state_key) = &self.state_key {
|
||||
json["state_key"] = json!(state_key);
|
||||
}
|
||||
if let Some(redacts) = &self.redacts {
|
||||
json["redacts"] = json!(redacts);
|
||||
}
|
||||
|
||||
serde_json::from_value(json).expect("Raw::from_value always works")
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn to_message_like_event(&self) -> Raw<AnyMessageLikeEvent> {
|
||||
let mut json = json!({
|
||||
"content": self.content,
|
||||
"type": self.kind,
|
||||
"event_id": self.event_id,
|
||||
"sender": self.sender,
|
||||
"origin_server_ts": self.origin_server_ts,
|
||||
"room_id": self.room_id,
|
||||
});
|
||||
|
||||
if let Some(unsigned) = &self.unsigned {
|
||||
json["unsigned"] = json!(unsigned);
|
||||
}
|
||||
if let Some(state_key) = &self.state_key {
|
||||
json["state_key"] = json!(state_key);
|
||||
}
|
||||
|
@ -214,38 +171,32 @@ impl PduEvent {
|
|||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn to_state_event(&self) -> Raw<AnyStateEvent> {
|
||||
let mut json = json!({
|
||||
let json = json!({
|
||||
"content": self.content,
|
||||
"type": self.kind,
|
||||
"event_id": self.event_id,
|
||||
"sender": self.sender,
|
||||
"origin_server_ts": self.origin_server_ts,
|
||||
"unsigned": self.unsigned,
|
||||
"room_id": self.room_id,
|
||||
"state_key": self.state_key,
|
||||
});
|
||||
|
||||
if let Some(unsigned) = &self.unsigned {
|
||||
json["unsigned"] = json!(unsigned);
|
||||
}
|
||||
|
||||
serde_json::from_value(json).expect("Raw::from_value always works")
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn to_sync_state_event(&self) -> Raw<AnySyncStateEvent> {
|
||||
let mut json = json!({
|
||||
let json = json!({
|
||||
"content": self.content,
|
||||
"type": self.kind,
|
||||
"event_id": self.event_id,
|
||||
"sender": self.sender,
|
||||
"origin_server_ts": self.origin_server_ts,
|
||||
"unsigned": self.unsigned,
|
||||
"state_key": self.state_key,
|
||||
});
|
||||
|
||||
if let Some(unsigned) = &self.unsigned {
|
||||
json["unsigned"] = json!(unsigned);
|
||||
}
|
||||
|
||||
serde_json::from_value(json).expect("Raw::from_value always works")
|
||||
}
|
||||
|
||||
|
@ -261,36 +212,20 @@ impl PduEvent {
|
|||
serde_json::from_value(json).expect("Raw::from_value always works")
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn to_stripped_spacechild_state_event(&self) -> Raw<HierarchySpaceChildEvent> {
|
||||
let json = json!({
|
||||
"content": self.content,
|
||||
"type": self.kind,
|
||||
"sender": self.sender,
|
||||
"state_key": self.state_key,
|
||||
"origin_server_ts": self.origin_server_ts,
|
||||
});
|
||||
|
||||
serde_json::from_value(json).expect("Raw::from_value always works")
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn to_member_event(&self) -> Raw<StateEvent<RoomMemberEventContent>> {
|
||||
let mut json = json!({
|
||||
let json = json!({
|
||||
"content": self.content,
|
||||
"type": self.kind,
|
||||
"event_id": self.event_id,
|
||||
"sender": self.sender,
|
||||
"origin_server_ts": self.origin_server_ts,
|
||||
"redacts": self.redacts,
|
||||
"unsigned": self.unsigned,
|
||||
"room_id": self.room_id,
|
||||
"state_key": self.state_key,
|
||||
});
|
||||
|
||||
if let Some(unsigned) = &self.unsigned {
|
||||
json["unsigned"] = json!(unsigned);
|
||||
}
|
||||
|
||||
serde_json::from_value(json).expect("Raw::from_value always works")
|
||||
}
|
||||
|
||||
|
@ -346,7 +281,7 @@ impl state_res::Event for PduEvent {
|
|||
&self.sender
|
||||
}
|
||||
|
||||
fn event_type(&self) -> &TimelineEventType {
|
||||
fn event_type(&self) -> &RoomEventType {
|
||||
&self.kind
|
||||
}
|
||||
|
||||
|
@ -385,7 +320,7 @@ impl PartialEq for PduEvent {
|
|||
}
|
||||
impl PartialOrd for PduEvent {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
self.event_id.partial_cmp(&other.event_id)
|
||||
}
|
||||
}
|
||||
impl Ord for PduEvent {
|
||||
|
@ -399,17 +334,23 @@ impl Ord for PduEvent {
|
|||
/// Returns a tuple of the new `EventId` and the PDU as a `BTreeMap<String, CanonicalJsonValue>`.
|
||||
pub(crate) fn gen_event_id_canonical_json(
|
||||
pdu: &RawJsonValue,
|
||||
room_version_id: &RoomVersionId,
|
||||
) -> crate::Result<(OwnedEventId, CanonicalJsonObject)> {
|
||||
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 = value
|
||||
.get("room_id")
|
||||
.and_then(|id| RoomId::parse(id.as_str()?).ok())
|
||||
.ok_or_else(|| Error::bad_database("PDU in db has invalid room_id."))?;
|
||||
|
||||
let room_version_id = services().rooms.state.get_room_version(&room_id);
|
||||
|
||||
let event_id = format!(
|
||||
"${}",
|
||||
// Anything higher than version3 behaves the same
|
||||
ruma::signatures::reference_hash(&value, room_version_id)
|
||||
ruma::signatures::reference_hash(&value, &room_version_id?)
|
||||
.expect("ruma can calculate reference hashes")
|
||||
)
|
||||
.try_into()
|
||||
|
@ -422,7 +363,7 @@ pub(crate) fn gen_event_id_canonical_json(
|
|||
#[derive(Debug, Deserialize)]
|
||||
pub struct PduBuilder {
|
||||
#[serde(rename = "type")]
|
||||
pub event_type: TimelineEventType,
|
||||
pub event_type: RoomEventType,
|
||||
pub content: Box<RawJsonValue>,
|
||||
pub unsigned: Option<BTreeMap<String, serde_json::Value>>,
|
||||
pub state_key: Option<String>,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
mod data;
|
||||
pub use data::Data;
|
||||
use ruma::{events::AnySyncTimelineEvent, push::PushConditionPowerLevelsCtx};
|
||||
use ruma::events::AnySyncTimelineEvent;
|
||||
|
||||
use crate::{services, Error, PduEvent, Result};
|
||||
use bytes::BytesMut;
|
||||
|
@ -13,7 +13,10 @@ use ruma::{
|
|||
},
|
||||
IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken,
|
||||
},
|
||||
events::{room::power_levels::RoomPowerLevelsEventContent, StateEventType, TimelineEventType},
|
||||
events::{
|
||||
room::{name::RoomNameEventContent, power_levels::RoomPowerLevelsEventContent},
|
||||
RoomEventType, StateEventType,
|
||||
},
|
||||
push::{Action, PushConditionRoomCtx, PushFormat, Ruleset, Tweak},
|
||||
serde::Raw,
|
||||
uint, RoomId, UInt, UserId,
|
||||
|
@ -159,12 +162,13 @@ impl Service {
|
|||
&pdu.room_id,
|
||||
)? {
|
||||
let n = match action {
|
||||
Action::Notify => true,
|
||||
Action::DontNotify => false,
|
||||
// TODO: Implement proper support for coalesce
|
||||
Action::Notify | Action::Coalesce => true,
|
||||
Action::SetTweak(tweak) => {
|
||||
tweaks.push(tweak.clone());
|
||||
continue;
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if notify.is_some() {
|
||||
|
@ -193,12 +197,6 @@ impl Service {
|
|||
pdu: &Raw<AnySyncTimelineEvent>,
|
||||
room_id: &RoomId,
|
||||
) -> Result<&'a [Action]> {
|
||||
let power_levels = PushConditionPowerLevelsCtx {
|
||||
users: power_levels.users.clone(),
|
||||
users_default: power_levels.users_default,
|
||||
notifications: power_levels.notifications.clone(),
|
||||
};
|
||||
|
||||
let ctx = PushConditionRoomCtx {
|
||||
room_id: room_id.to_owned(),
|
||||
member_count: 10_u32.into(), // TODO: get member count efficiently
|
||||
|
@ -207,7 +205,9 @@ impl Service {
|
|||
.users
|
||||
.displayname(user)?
|
||||
.unwrap_or_else(|| user.localpart().to_owned()),
|
||||
power_levels: Some(power_levels),
|
||||
users_power_levels: power_levels.users.clone(),
|
||||
default_power_level: power_levels.users_default,
|
||||
notification_power_levels: power_levels.notifications.clone(),
|
||||
};
|
||||
|
||||
Ok(ruleset.get_actions(pdu, &ctx))
|
||||
|
@ -239,16 +239,16 @@ impl Service {
|
|||
device.tweaks = tweaks.clone();
|
||||
}
|
||||
|
||||
let d = vec![device];
|
||||
let d = &[device];
|
||||
let mut notifi = Notification::new(d);
|
||||
|
||||
notifi.prio = NotificationPriority::Low;
|
||||
notifi.event_id = Some((*event.event_id).to_owned());
|
||||
notifi.room_id = Some((*event.room_id).to_owned());
|
||||
notifi.event_id = Some(&event.event_id);
|
||||
notifi.room_id = Some(&event.room_id);
|
||||
// TODO: missed calls
|
||||
notifi.counts = NotificationCounts::new(unread, uint!(0));
|
||||
|
||||
if event.kind == TimelineEventType::RoomEncrypted
|
||||
if event.kind == RoomEventType::RoomEncrypted
|
||||
|| tweaks
|
||||
.iter()
|
||||
.any(|t| matches!(t, Tweak::Highlight(true) | Tweak::Sound(_)))
|
||||
|
@ -260,18 +260,34 @@ impl Service {
|
|||
self.send_request(&http.url, send_event_notification::v1::Request::new(notifi))
|
||||
.await?;
|
||||
} else {
|
||||
notifi.sender = Some(event.sender.clone());
|
||||
notifi.event_type = Some(event.kind.clone());
|
||||
notifi.content = serde_json::value::to_raw_value(&event.content).ok();
|
||||
notifi.sender = Some(&event.sender);
|
||||
notifi.event_type = Some(&event.kind);
|
||||
let content = serde_json::value::to_raw_value(&event.content).ok();
|
||||
notifi.content = content.as_deref();
|
||||
|
||||
if event.kind == TimelineEventType::RoomMember {
|
||||
if event.kind == RoomEventType::RoomMember {
|
||||
notifi.user_is_target =
|
||||
event.state_key.as_deref() == Some(event.sender.as_str());
|
||||
}
|
||||
|
||||
notifi.sender_display_name = services().users.displayname(&event.sender)?;
|
||||
let user_name = services().users.displayname(&event.sender)?;
|
||||
notifi.sender_display_name = user_name.as_deref();
|
||||
|
||||
notifi.room_name = services().rooms.state_accessor.get_name(&event.room_id)?;
|
||||
let room_name = if let Some(room_name_pdu) = services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(&event.room_id, &StateEventType::RoomName, "")?
|
||||
{
|
||||
serde_json::from_str::<RoomNameEventContent>(room_name_pdu.content.get())
|
||||
.map_err(|_| {
|
||||
Error::bad_database("Invalid room name event in database.")
|
||||
})?
|
||||
.name
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
notifi.room_name = room_name.as_deref();
|
||||
|
||||
self.send_request(&http.url, send_event_notification::v1::Request::new(notifi))
|
||||
.await?;
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::{
|
|||
|
||||
pub use data::Data;
|
||||
use ruma::{api::client::error::ErrorKind, EventId, RoomId};
|
||||
use tracing::{debug, error, warn};
|
||||
use tracing::log::warn;
|
||||
|
||||
use crate::{services, Error, Result};
|
||||
|
||||
|
@ -15,7 +15,11 @@ pub struct Service {
|
|||
}
|
||||
|
||||
impl Service {
|
||||
pub fn get_cached_eventid_authchain(&self, key: &[u64]) -> Result<Option<Arc<HashSet<u64>>>> {
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn get_cached_eventid_authchain<'a>(
|
||||
&'a self,
|
||||
key: &[u64],
|
||||
) -> Result<Option<Arc<HashSet<u64>>>> {
|
||||
self.db.get_cached_eventid_authchain(key)
|
||||
}
|
||||
|
||||
|
@ -85,10 +89,10 @@ impl Service {
|
|||
.rooms
|
||||
.auth_chain
|
||||
.cache_auth_chain(vec![sevent_id], Arc::clone(&auth_chain))?;
|
||||
debug!(
|
||||
event_id = ?event_id,
|
||||
chain_length = ?auth_chain.len(),
|
||||
"Cache missed event"
|
||||
println!(
|
||||
"cache missed event {} with auth chain len {}",
|
||||
event_id,
|
||||
auth_chain.len()
|
||||
);
|
||||
chunk_cache.extend(auth_chain.iter());
|
||||
|
||||
|
@ -98,11 +102,11 @@ impl Service {
|
|||
}
|
||||
};
|
||||
}
|
||||
debug!(
|
||||
chunk_cache_length = ?chunk_cache.len(),
|
||||
hits = ?hits2,
|
||||
misses = ?misses2,
|
||||
"Chunk missed",
|
||||
println!(
|
||||
"chunk missed with len {}, event hits2: {}, misses2: {}",
|
||||
chunk_cache.len(),
|
||||
hits2,
|
||||
misses2
|
||||
);
|
||||
let chunk_cache = Arc::new(chunk_cache);
|
||||
services()
|
||||
|
@ -112,11 +116,11 @@ impl Service {
|
|||
full_auth_chain.extend(chunk_cache.iter());
|
||||
}
|
||||
|
||||
debug!(
|
||||
chain_length = ?full_auth_chain.len(),
|
||||
hits = ?hits,
|
||||
misses = ?misses,
|
||||
"Auth chain stats",
|
||||
println!(
|
||||
"total: {}, chunk hits: {}, misses: {}",
|
||||
full_auth_chain.len(),
|
||||
hits,
|
||||
misses
|
||||
);
|
||||
|
||||
Ok(full_auth_chain
|
||||
|
@ -148,10 +152,10 @@ impl Service {
|
|||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
warn!(?event_id, "Could not find pdu mentioned in auth events");
|
||||
warn!("Could not find pdu mentioned in auth events: {}", event_id);
|
||||
}
|
||||
Err(error) => {
|
||||
error!(?event_id, ?error, "Could not load event in auth chain");
|
||||
Err(e) => {
|
||||
warn!("Could not load event in auth chain: {} {}", event_id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ pub trait Data: Send + Sync {
|
|||
) -> Result<()>;
|
||||
|
||||
/// Returns an iterator over the most recent read_receipts in a room that happened after the event with id `since`.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn readreceipts_since<'a>(
|
||||
&'a self,
|
||||
room_id: &RoomId,
|
||||
|
|
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue