Merge branch 'main' of github.com:matrix-org/dendrite into s7evink/consent-tracking

This commit is contained in:
Till Faelligen 2022-03-28 11:30:16 +02:00
commit 019f0922ea
109 changed files with 1996 additions and 1019 deletions

View file

@ -2,6 +2,7 @@
<!-- Please read docs/CONTRIBUTING.md before submitting your pull request --> <!-- Please read docs/CONTRIBUTING.md before submitting your pull request -->
* [ ] I have added added tests for PR _or_ I have justified why this PR doesn't need tests.
* [ ] Pull request includes a [sign off](https://github.com/matrix-org/dendrite/blob/main/docs/CONTRIBUTING.md#sign-off) * [ ] Pull request includes a [sign off](https://github.com/matrix-org/dendrite/blob/main/docs/CONTRIBUTING.md#sign-off)
Signed-off-by: `Your Name <your@email.example.org>` Signed-off-by: `Your Name <your@email.example.org>`

View file

@ -1,34 +0,0 @@
name: "CodeQL"
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: ["go"]
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
fetch-depth: 2
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

346
.github/workflows/dendrite.yml vendored Normal file
View file

@ -0,0 +1,346 @@
name: Dendrite
on:
push:
branches:
- main
pull_request:
release:
types: [published]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
wasm:
name: WASM build test
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: 1.16
- uses: actions/cache@v2
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-wasm-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-wasm
- name: Install Node
uses: actions/setup-node@v2
with:
node-version: 14
- uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Reconfigure Git to use HTTPS auth for repo packages
run: >
git config --global url."https://github.com/".insteadOf
ssh://git@github.com/
- name: Install test dependencies
working-directory: ./test/wasm
run: npm ci
- name: Test
run: ./test-dendritejs.sh
# Run golangci-lint
lint:
timeout-minutes: 5
name: Linting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
# run go test with different go versions
test:
timeout-minutes: 5
name: Unit tests (Go ${{ matrix.go }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
go: ["1.16", "1.17", "1.18"]
steps:
- uses: actions/checkout@v3
- name: Setup go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go }}
- uses: actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go${{ matrix.go }}-test-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go${{ matrix.go }}-test-
- run: go test ./...
# build Dendrite for linux with different architectures and go versions
build:
name: Build for Linux
timeout-minutes: 10
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
go: ["1.16", "1.17", "1.18"]
goos: ["linux"]
goarch: ["amd64", "386"]
steps:
- uses: actions/checkout@v3
- name: Setup go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go }}
- name: Install dependencies x86
if: ${{ matrix.goarch == '386' }}
run: sudo apt update && sudo apt-get install -y gcc-multilib
- uses: actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go${{ matrix.go }}-${{ matrix.goarch }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go${{ matrix.go }}-${{ matrix.goarch }}-
- env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: 1
run: go build -trimpath -v -o "bin/" ./cmd/...
# build for Windows 64-bit
build_windows:
name: Build for Windows
timeout-minutes: 10
runs-on: ubuntu-latest
strategy:
matrix:
go: ["1.16", "1.17", "1.18"]
goos: ["windows"]
goarch: ["amd64"]
steps:
- uses: actions/checkout@v3
- name: Setup Go ${{ matrix.go }}
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go }}
- name: Install dependencies
run: sudo apt update && sudo apt install -y gcc-mingw-w64-x86-64 # install required gcc
- uses: actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go${{ matrix.go }}-${{ matrix.goos }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go${{ matrix.go }}-${{ matrix.goos }}
- env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: 1
CC: "/usr/bin/x86_64-w64-mingw32-gcc"
run: go build -trimpath -v -o "bin/" ./cmd/...
# Dummy step to gate other tests on without repeating the whole list
initial-tests-done:
name: Initial tests passed
needs: [lint, test, build, build_windows]
runs-on: ubuntu-latest
if: ${{ !cancelled() }} # Run this even if prior jobs were skipped
steps:
- name: Check initial tests passed
uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}
# run database upgrade tests
upgrade_test:
name: Upgrade tests
timeout-minutes: 20
needs: initial-tests-done
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup go
uses: actions/setup-go@v2
with:
go-version: "1.16"
- uses: actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-upgrade-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-upgrade
- name: Build upgrade-tests
run: go build ./cmd/dendrite-upgrade-tests
- name: Test upgrade
run: ./dendrite-upgrade-tests --head .
# run Sytest in different variations
sytest:
timeout-minutes: 20
needs: initial-tests-done
name: "Sytest (${{ matrix.label }})"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- label: SQLite
- label: SQLite, full HTTP APIs
api: full-http
- label: PostgreSQL
postgres: postgres
- label: PostgreSQL, full HTTP APIs
postgres: postgres
api: full-http
container:
image: matrixdotorg/sytest-dendrite:latest
volumes:
- ${{ github.workspace }}:/src
env:
POSTGRES: ${{ matrix.postgres && 1}}
API: ${{ matrix.api && 1 }}
steps:
- uses: actions/checkout@v2
- name: Run Sytest
run: /bootstrap.sh dendrite
working-directory: /src
- name: Summarise results.tap
if: ${{ always() }}
run: /sytest/scripts/tap_to_gha.pl /logs/results.tap
- name: Upload Sytest logs
uses: actions/upload-artifact@v2
if: ${{ always() }}
with:
name: Sytest Logs - ${{ job.status }} - (Dendrite, ${{ join(matrix.*, ', ') }})
path: |
/logs/results.tap
/logs/**/*.log*
# run Complement
complement:
name: "Complement (${{ matrix.label }})"
timeout-minutes: 20
needs: initial-tests-done
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- label: SQLite
- label: SQLite, full HTTP APIs
api: full-http
- label: PostgreSQL
postgres: Postgres
- label: PostgreSQL, full HTTP APIs
postgres: Postgres
api: full-http
steps:
# Env vars are set file a file given by $GITHUB_PATH. We need both Go 1.17 and GOPATH on env to run Complement.
# See https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#adding-a-system-path
- name: "Set Go Version"
run: |
echo "$GOROOT_1_17_X64/bin" >> $GITHUB_PATH
echo "~/go/bin" >> $GITHUB_PATH
- name: "Install Complement Dependencies"
# We don't need to install Go because it is included on the Ubuntu 20.04 image:
# See https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu2004-Readme.md specifically GOROOT_1_17_X64
run: |
sudo apt-get update && sudo apt-get install -y libolm3 libolm-dev
go get -v github.com/haveyoudebuggedit/gotestfmt/v2/cmd/gotestfmt@latest
- name: Run actions/checkout@v2 for dendrite
uses: actions/checkout@v2
with:
path: dendrite
# Attempt to check out the same branch of Complement as the PR. If it
# doesn't exist, fallback to main.
- name: Checkout complement
shell: bash
run: |
mkdir -p complement
# Attempt to use the version of complement which best matches the current
# build. Depending on whether this is a PR or release, etc. we need to
# use different fallbacks.
#
# 1. First check if there's a similarly named branch (GITHUB_HEAD_REF
# for pull requests, otherwise GITHUB_REF).
# 2. Attempt to use the base branch, e.g. when merging into release-vX.Y
# (GITHUB_BASE_REF for pull requests).
# 3. Use the default complement branch ("master").
for BRANCH_NAME in "$GITHUB_HEAD_REF" "$GITHUB_BASE_REF" "${GITHUB_REF#refs/heads/}" "master"; do
# Skip empty branch names and merge commits.
if [[ -z "$BRANCH_NAME" || $BRANCH_NAME =~ ^refs/pull/.* ]]; then
continue
fi
(wget -O - "https://github.com/matrix-org/complement/archive/$BRANCH_NAME.tar.gz" | tar -xz --strip-components=1 -C complement) && break
done
# Build initial Dendrite image
- run: docker build -t complement-dendrite -f build/scripts/Complement${{ matrix.postgres }}.Dockerfile .
working-directory: dendrite
# Run Complement
- run: |
set -o pipefail &&
go test -v -json -tags dendrite_blacklist ./tests/... 2>&1 | gotestfmt
shell: bash
name: Run Complement Tests
env:
COMPLEMENT_BASE_IMAGE: complement-dendrite:latest
API: ${{ matrix.api && 1 }}
working-directory: complement
integration-tests-done:
name: Integration tests passed
needs: [initial-tests-done, upgrade_test, sytest, complement]
runs-on: ubuntu-latest
if: ${{ !cancelled() }} # Run this even if prior jobs were skipped
steps:
- name: Check integration tests passed
uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}
update-docker-images:
name: Update Docker images
permissions:
packages: write
contents: read
if: github.repository == 'matrix-org/dendrite' && github.ref_name == 'main'
needs: [integration-tests-done]
uses: matrix-org/dendrite/.github/workflows/docker.yml@main
secrets:
DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}

View file

@ -1,71 +0,0 @@
# Based on https://github.com/docker/build-push-action
name: "Docker Hub"
on:
release:
types: [published]
env:
DOCKER_NAMESPACE: matrixdotorg
DOCKER_HUB_USER: dendritegithub
PLATFORMS: linux/amd64,linux/arm64,linux/arm/v7
jobs:
Monolith:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Get release tag
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ env.DOCKER_HUB_USER }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build monolith image
id: docker_build_monolith
uses: docker/build-push-action@v2
with:
context: .
file: ./build/docker/Dockerfile.monolith
platforms: ${{ env.PLATFORMS }}
push: true
tags: |
${{ env.DOCKER_NAMESPACE }}/dendrite-monolith:latest
${{ env.DOCKER_NAMESPACE }}/dendrite-monolith:${{ env.RELEASE_VERSION }}
Polylith:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Get release tag
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ env.DOCKER_HUB_USER }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build polylith image
id: docker_build_polylith
uses: docker/build-push-action@v2
with:
context: .
file: ./build/docker/Dockerfile.polylith
platforms: ${{ env.PLATFORMS }}
push: true
tags: |
${{ env.DOCKER_NAMESPACE }}/dendrite-polylith:latest
${{ env.DOCKER_NAMESPACE }}/dendrite-polylith:${{ env.RELEASE_VERSION }}

139
.github/workflows/docker.yml vendored Normal file
View file

@ -0,0 +1,139 @@
# Based on https://github.com/docker/build-push-action
name: "Docker"
on:
release: # A GitHub release was published
types: [published]
workflow_dispatch: # A build was manually requested
workflow_call: # Another pipeline called us
secrets:
DOCKER_TOKEN:
required: true
env:
DOCKER_NAMESPACE: matrixdotorg
DOCKER_HUB_USER: dendritegithub
GHCR_NAMESPACE: matrix-org
PLATFORMS: linux/amd64,linux/arm64,linux/arm/v7
jobs:
monolith:
name: Monolith image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Get release tag
if: github.event_name == 'release' # Only for GitHub releases
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ env.DOCKER_HUB_USER }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Login to GitHub Containers
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build main monolith image
if: github.ref_name == 'main'
id: docker_build_monolith
uses: docker/build-push-action@v2
with:
cache-from: type=gha
cache-to: type=gha,mode=max
context: .
file: ./build/docker/Dockerfile.monolith
platforms: ${{ env.PLATFORMS }}
push: true
tags: |
${{ env.DOCKER_NAMESPACE }}/dendrite-monolith:${{ github.ref_name }}
ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-monolith:${{ github.ref_name }}
- name: Build release monolith image
if: github.event_name == 'release' # Only for GitHub releases
id: docker_build_monolith_release
uses: docker/build-push-action@v2
with:
cache-from: type=gha
cache-to: type=gha,mode=max
context: .
file: ./build/docker/Dockerfile.monolith
platforms: ${{ env.PLATFORMS }}
push: true
tags: |
${{ env.DOCKER_NAMESPACE }}/dendrite-monolith:latest
${{ env.DOCKER_NAMESPACE }}/dendrite-monolith:${{ env.RELEASE_VERSION }}
ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-monolith:latest
ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-monolith:${{ env.RELEASE_VERSION }}
polylith:
name: Polylith image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Get release tag
if: github.event_name == 'release' # Only for GitHub releases
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ env.DOCKER_HUB_USER }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Login to GitHub Containers
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build main polylith image
if: github.ref_name == 'main'
id: docker_build_polylith
uses: docker/build-push-action@v2
with:
cache-from: type=gha
cache-to: type=gha,mode=max
context: .
file: ./build/docker/Dockerfile.polylith
platforms: ${{ env.PLATFORMS }}
push: true
tags: |
${{ env.DOCKER_NAMESPACE }}/dendrite-polylith:${{ github.ref_name }}
ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-polylith:${{ github.ref_name }}
- name: Build release polylith image
if: github.event_name == 'release' # Only for GitHub releases
id: docker_build_polylith_release
uses: docker/build-push-action@v2
with:
cache-from: type=gha
cache-to: type=gha,mode=max
context: .
file: ./build/docker/Dockerfile.polylith
platforms: ${{ env.PLATFORMS }}
push: true
tags: |
${{ env.DOCKER_NAMESPACE }}/dendrite-polylith:latest
${{ env.DOCKER_NAMESPACE }}/dendrite-polylith:${{ env.RELEASE_VERSION }}
ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-polylith:latest
ghcr.io/${{ env.GHCR_NAMESPACE }}/dendrite-polylith:${{ env.RELEASE_VERSION }}

View file

@ -1,71 +0,0 @@
name: Tests
on:
push:
branches: ["main"]
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
complement:
runs-on: ubuntu-latest
steps:
# Env vars are set file a file given by $GITHUB_PATH. We need both Go 1.17 and GOPATH on env to run Complement.
# See https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#adding-a-system-path
- name: "Set Go Version"
run: |
echo "$GOROOT_1_17_X64/bin" >> $GITHUB_PATH
echo "~/go/bin" >> $GITHUB_PATH
- name: "Install Complement Dependencies"
# We don't need to install Go because it is included on the Ubuntu 20.04 image:
# See https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu2004-Readme.md specifically GOROOT_1_17_X64
run: |
sudo apt-get update && sudo apt-get install -y libolm3 libolm-dev
go get -v github.com/haveyoudebuggedit/gotestfmt/v2/cmd/gotestfmt@latest
- name: Run actions/checkout@v2 for dendrite
uses: actions/checkout@v2
with:
path: dendrite
# Attempt to check out the same branch of Complement as the PR. If it
# doesn't exist, fallback to main.
- name: Checkout complement
shell: bash
run: |
mkdir -p complement
# Attempt to use the version of complement which best matches the current
# build. Depending on whether this is a PR or release, etc. we need to
# use different fallbacks.
#
# 1. First check if there's a similarly named branch (GITHUB_HEAD_REF
# for pull requests, otherwise GITHUB_REF).
# 2. Attempt to use the base branch, e.g. when merging into release-vX.Y
# (GITHUB_BASE_REF for pull requests).
# 3. Use the default complement branch ("master").
for BRANCH_NAME in "$GITHUB_HEAD_REF" "$GITHUB_BASE_REF" "${GITHUB_REF#refs/heads/}" "master"; do
# Skip empty branch names and merge commits.
if [[ -z "$BRANCH_NAME" || $BRANCH_NAME =~ ^refs/pull/.* ]]; then
continue
fi
(wget -O - "https://github.com/matrix-org/complement/archive/$BRANCH_NAME.tar.gz" | tar -xz --strip-components=1 -C complement) && break
done
# Build initial Dendrite image
- run: docker build -t complement-dendrite -f build/scripts/Complement.Dockerfile .
working-directory: dendrite
# Run Complement
- run: |
set -o pipefail &&
go test -v -json -tags dendrite_blacklist ./tests/... 2>&1 | gotestfmt
shell: bash
name: Run Complement Tests
env:
COMPLEMENT_BASE_IMAGE: complement-dendrite:latest
working-directory: complement

View file

@ -1,49 +0,0 @@
name: WebAssembly
on:
push:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: 1.16.5
- uses: actions/cache@v2
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Install Node
uses: actions/setup-node@v2
with:
node-version: 14
- uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Reconfigure Git to use HTTPS auth for repo packages
run: >
git config --global url."https://github.com/".insteadOf
ssh://git@github.com/
- name: Install test dependencies
working-directory: ./test/wasm
run: npm ci
- name: Test
run: ./test-dendritejs.sh

View file

@ -1,5 +1,43 @@
# Changelog # Changelog
## Dendrite 0.7.0 (2022-03-25)
### Features
* The roomserver input API will now queue all events into NATS, which provides better crash resilience
* The roomserver input API now configures per-room consumers, which should use less memory
* Canonical aliases can now be added and removed
* MSC2946 Spaces Summary now works correctly, both locally and over federation
* Healthcheck endpoints are now available at:
* `/_dendrite/monitor/up`, which will return 200 when Dendrite is ready to accept requests
* `/_dendrite/monitor/health`, which will return 200 if healthy and 503 if degraded for some reason
* The `X-Matrix` federation authorisation header now includes a `destination` field, as per MSC3383
* The `/sync` endpoint now uses less memory by only ranging state for rooms that the user has participated in
* The `/messages` endpoint now accepts stream positions in both the `from` and `to` parameters
* Dendrite will now log a warning at startup if the file descriptor limit is set too low
* The federation client will now attempt to use HTTP/2 if available
* The federation client will now attempt to resume TLS sessions if possible, to reduce handshake overheads
* The built-in NATS Server has been updated to version 2.7.4
* NATS streams that don't match the desired configuration will now be recreated automatically
* When performing a graceful shutdown, Dendrite will now wait for NATS Server to shutdown completely, which should avoid some corruption of data on-disk
* The `create-account` tool has seen a number of improvements, will now ask for passwords automatically
### Fixes
* The `/sync` endpoint will no longer lose state events when truncating the timeline for history visibility
* The `/context` endpoint now works correctly with `lazy_load_members`
* The `/directory/list/room/{roomID}` endpoint now correctly reports whether a room is published in the server room directory or not
* Some bugs around appservice username validation have been fixed
* Roomserver output messages are no longer unnecessarily inflated by state events, which should reduce the number of NATS message size errors
* Stream IDs for device list updates are now always 64-bit, which should fix some problems when running Dendrite on a 32-bit system
* Purging room state in the sync API has been fixed after a faulty database query was corrected
* The federation client will now release host records for remote destinations after 5 minutes instead of holding them in memory forever
* Remote media requests will now correctly return an error if the file cannot be found or downloaded
* A panic in the media API that could happen when the remote file doesn't exist has been fixed
* Various bugs around membership state and invites have been fixed
* The memberships table will now be correctly updated when rejecting a federated invite
* The client API and appservice API will now access the user database using the user API rather than accessing the database directly
## Dendrite 0.6.5 (2022-03-04) ## Dendrite 0.6.5 (2022-03-04)
### Features ### Features

View file

@ -1,4 +1,5 @@
# Dendrite [![Build Status](https://badge.buildkite.com/4be40938ab19f2bbc4a6c6724517353ee3ec1422e279faf374.svg?branch=master)](https://buildkite.com/matrix-dot-org/dendrite) [![Dendrite](https://img.shields.io/matrix/dendrite:matrix.org.svg?label=%23dendrite%3Amatrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#dendrite:matrix.org) [![Dendrite Dev](https://img.shields.io/matrix/dendrite-dev:matrix.org.svg?label=%23dendrite-dev%3Amatrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#dendrite-dev:matrix.org) # Dendrite
[![Build status](https://github.com/matrix-org/dendrite/actions/workflows/dendrite.yml/badge.svg?event=push)](https://github.com/matrix-org/dendrite/actions/workflows/dendrite.yml) [![Dendrite](https://img.shields.io/matrix/dendrite:matrix.org.svg?label=%23dendrite%3Amatrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#dendrite:matrix.org) [![Dendrite Dev](https://img.shields.io/matrix/dendrite-dev:matrix.org.svg?label=%23dendrite-dev%3Amatrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#dendrite-dev:matrix.org)
Dendrite is a second-generation Matrix homeserver written in Go. Dendrite is a second-generation Matrix homeserver written in Go.
It intends to provide an **efficient**, **reliable** and **scalable** alternative to [Synapse](https://github.com/matrix-org/synapse): It intends to provide an **efficient**, **reliable** and **scalable** alternative to [Synapse](https://github.com/matrix-org/synapse):

View file

@ -19,11 +19,10 @@ package api
import ( import (
"context" "context"
"database/sql"
"errors" "errors"
"github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/clientapi/auth/authtypes"
userdb "github.com/matrix-org/dendrite/userapi/storage" userapi "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
) )
@ -85,7 +84,7 @@ func RetrieveUserProfile(
ctx context.Context, ctx context.Context,
userID string, userID string,
asAPI AppServiceQueryAPI, asAPI AppServiceQueryAPI,
accountDB userdb.Database, profileAPI userapi.UserProfileAPI,
) (*authtypes.Profile, error) { ) (*authtypes.Profile, error) {
localpart, _, err := gomatrixserverlib.SplitID('@', userID) localpart, _, err := gomatrixserverlib.SplitID('@', userID)
if err != nil { if err != nil {
@ -93,10 +92,17 @@ func RetrieveUserProfile(
} }
// Try to query the user from the local database // Try to query the user from the local database
profile, err := accountDB.GetProfileByLocalpart(ctx, localpart) res := &userapi.QueryProfileResponse{}
if err != nil && err != sql.ErrNoRows { err = profileAPI.QueryProfile(ctx, &userapi.QueryProfileRequest{UserID: userID}, res)
if err != nil {
return nil, err return nil, err
} else if profile != nil { }
profile := &authtypes.Profile{
Localpart: localpart,
DisplayName: res.DisplayName,
AvatarURL: res.AvatarURL,
}
if res.UserExists {
return profile, nil return profile, nil
} }
@ -113,11 +119,15 @@ func RetrieveUserProfile(
} }
// Try to query the user from the local database again // Try to query the user from the local database again
profile, err = accountDB.GetProfileByLocalpart(ctx, localpart) err = profileAPI.QueryProfile(ctx, &userapi.QueryProfileRequest{UserID: userID}, res)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// profile should not be nil at this point // profile should not be nil at this point
return profile, nil return &authtypes.Profile{
Localpart: localpart,
DisplayName: res.DisplayName,
AvatarURL: res.AvatarURL,
}, nil
} }

View file

@ -59,7 +59,7 @@ func NewInternalAPI(
}, },
}, },
} }
js, _ := jetstream.Prepare(&base.Cfg.Global.JetStream) js, _ := jetstream.Prepare(base.ProcessContext, &base.Cfg.Global.JetStream)
// Create a connection to the appservice postgres DB // Create a connection to the appservice postgres DB
appserviceDB, err := storage.NewDatabase(&base.Cfg.AppServiceAPI.Database) appserviceDB, err := storage.NewDatabase(&base.Cfg.AppServiceAPI.Database)

View file

@ -56,7 +56,7 @@ func NewOutputRoomEventConsumer(
ctx: process.Context(), ctx: process.Context(),
jetstream: js, jetstream: js,
durable: cfg.Global.JetStream.Durable("AppserviceRoomserverConsumer"), durable: cfg.Global.JetStream.Durable("AppserviceRoomserverConsumer"),
topic: cfg.Global.JetStream.TopicFor(jetstream.OutputRoomEvent), topic: cfg.Global.JetStream.Prefixed(jetstream.OutputRoomEvent),
asDB: appserviceDB, asDB: appserviceDB,
rsAPI: rsAPI, rsAPI: rsAPI,
serverName: string(cfg.Global.ServerName), serverName: string(cfg.Global.ServerName),

View file

@ -28,7 +28,6 @@ import (
// Database stores events intended to be later sent to application services // Database stores events intended to be later sent to application services
type Database struct { type Database struct {
sqlutil.PartitionOffsetStatements
events eventsStatements events eventsStatements
txnID txnStatements txnID txnStatements
db *sql.DB db *sql.DB
@ -46,9 +45,6 @@ func NewDatabase(dbProperties *config.DatabaseOptions) (*Database, error) {
if err = result.prepare(); err != nil { if err = result.prepare(); err != nil {
return nil, err return nil, err
} }
if err = result.PartitionOffsetStatements.Prepare(result.db, result.writer, "appservice"); err != nil {
return nil, err
}
return &result, nil return &result, nil
} }

View file

@ -27,7 +27,6 @@ import (
// Database stores events intended to be later sent to application services // Database stores events intended to be later sent to application services
type Database struct { type Database struct {
sqlutil.PartitionOffsetStatements
events eventsStatements events eventsStatements
txnID txnStatements txnID txnStatements
db *sql.DB db *sql.DB
@ -45,9 +44,6 @@ func NewDatabase(dbProperties *config.DatabaseOptions) (*Database, error) {
if err = result.prepare(); err != nil { if err = result.prepare(); err != nil {
return nil, err return nil, err
} }
if err = result.PartitionOffsetStatements.Prepare(result.db, result.writer, "appservice"); err != nil {
return nil, err
}
return &result, nil return &result, nil
} }

View file

@ -13,6 +13,10 @@ RUN go build -trimpath -o bin/ ./cmd/create-account
RUN go build -trimpath -o bin/ ./cmd/generate-keys RUN go build -trimpath -o bin/ ./cmd/generate-keys
FROM alpine:latest FROM alpine:latest
LABEL org.opencontainers.image.title="Dendrite (Monolith)"
LABEL org.opencontainers.image.description="Next-generation Matrix homeserver written in Go"
LABEL org.opencontainers.image.source="https://github.com/matrix-org/dendrite"
LABEL org.opencontainers.image.licenses="Apache-2.0"
COPY --from=base /build/bin/* /usr/bin/ COPY --from=base /build/bin/* /usr/bin/

View file

@ -13,6 +13,10 @@ RUN go build -trimpath -o bin/ ./cmd/create-account
RUN go build -trimpath -o bin/ ./cmd/generate-keys RUN go build -trimpath -o bin/ ./cmd/generate-keys
FROM alpine:latest FROM alpine:latest
LABEL org.opencontainers.image.title="Dendrite (Polylith)"
LABEL org.opencontainers.image.description="Next-generation Matrix homeserver written in Go"
LABEL org.opencontainers.image.source="https://github.com/matrix-org/dendrite"
LABEL org.opencontainers.image.licenses="Apache-2.0"
COPY --from=base /build/bin/* /usr/bin/ COPY --from=base /build/bin/* /usr/bin/

View file

@ -408,6 +408,8 @@ func (m *DendriteMonolith) Stop() {
_ = m.PineconeRouter.Close() _ = m.PineconeRouter.Close()
} }
const MaxFrameSize = types.MaxFrameSize
type Conduit struct { type Conduit struct {
conn net.Conn conn net.Conn
port types.SwitchPortID port types.SwitchPortID

View file

@ -21,6 +21,7 @@ WORKDIR /dendrite
RUN ./generate-keys --private-key matrix_key.pem RUN ./generate-keys --private-key matrix_key.pem
ENV SERVER_NAME=localhost ENV SERVER_NAME=localhost
ENV API=0
EXPOSE 8008 8448 EXPOSE 8008 8448
# At runtime, generate TLS cert based on the CA now mounted at /ca # At runtime, generate TLS cert based on the CA now mounted at /ca
@ -28,4 +29,4 @@ EXPOSE 8008 8448
CMD ./generate-keys --server $SERVER_NAME --tls-cert server.crt --tls-key server.key --tls-authority-cert /complement/ca/ca.crt --tls-authority-key /complement/ca/ca.key && \ CMD ./generate-keys --server $SERVER_NAME --tls-cert server.crt --tls-key server.key --tls-authority-cert /complement/ca/ca.crt --tls-authority-key /complement/ca/ca.key && \
./generate-config -server $SERVER_NAME --ci > dendrite.yaml && \ ./generate-config -server $SERVER_NAME --ci > dendrite.yaml && \
cp /complement/ca/ca.crt /usr/local/share/ca-certificates/ && update-ca-certificates && \ cp /complement/ca/ca.crt /usr/local/share/ca-certificates/ && update-ca-certificates && \
./dendrite-monolith-server --tls-cert server.crt --tls-key server.key --config dendrite.yaml ./dendrite-monolith-server --tls-cert server.crt --tls-key server.key --config dendrite.yaml -api=${API:-0}

View file

@ -39,6 +39,7 @@ WORKDIR /dendrite
RUN ./generate-keys --private-key matrix_key.pem RUN ./generate-keys --private-key matrix_key.pem
ENV SERVER_NAME=localhost ENV SERVER_NAME=localhost
ENV API=0
EXPOSE 8008 8448 EXPOSE 8008 8448
@ -50,4 +51,4 @@ CMD /build/run_postgres.sh && ./generate-keys --server $SERVER_NAME --tls-cert s
sed -i "s%connection_string:.*$%connection_string: postgresql://postgres@localhost/postgres?sslmode=disable%g" dendrite.yaml && \ sed -i "s%connection_string:.*$%connection_string: postgresql://postgres@localhost/postgres?sslmode=disable%g" dendrite.yaml && \
sed -i 's/max_open_conns:.*$/max_open_conns: 100/g' dendrite.yaml && \ sed -i 's/max_open_conns:.*$/max_open_conns: 100/g' dendrite.yaml && \
cp /complement/ca/ca.crt /usr/local/share/ca-certificates/ && update-ca-certificates && \ cp /complement/ca/ca.crt /usr/local/share/ca-certificates/ && update-ca-certificates && \
./dendrite-monolith-server --tls-cert server.crt --tls-key server.key --config dendrite.yaml ./dendrite-monolith-server --tls-cert server.crt --tls-key server.key --config dendrite.yaml -api=${API:-0}

View file

@ -33,7 +33,7 @@ import (
// called after authorization has completed, with the result of the authorization. // called after authorization has completed, with the result of the authorization.
// If the final return value is non-nil, an error occurred and the cleanup function // If the final return value is non-nil, an error occurred and the cleanup function
// is nil. // is nil.
func LoginFromJSONReader(ctx context.Context, r io.Reader, accountDB AccountDatabase, userAPI UserInternalAPIForLogin, cfg *config.ClientAPI) (*Login, LoginCleanupFunc, *util.JSONResponse) { func LoginFromJSONReader(ctx context.Context, r io.Reader, useraccountAPI uapi.UserAccountAPI, userAPI UserInternalAPIForLogin, cfg *config.ClientAPI) (*Login, LoginCleanupFunc, *util.JSONResponse) {
reqBytes, err := ioutil.ReadAll(r) reqBytes, err := ioutil.ReadAll(r)
if err != nil { if err != nil {
err := &util.JSONResponse{ err := &util.JSONResponse{
@ -58,7 +58,7 @@ func LoginFromJSONReader(ctx context.Context, r io.Reader, accountDB AccountData
switch header.Type { switch header.Type {
case authtypes.LoginTypePassword: case authtypes.LoginTypePassword:
typ = &LoginTypePassword{ typ = &LoginTypePassword{
GetAccountByPassword: accountDB.GetAccountByPassword, GetAccountByPassword: useraccountAPI.QueryAccountByPassword,
Config: cfg, Config: cfg,
} }
case authtypes.LoginTypeToken: case authtypes.LoginTypeToken:

View file

@ -16,7 +16,6 @@ package auth
import ( import (
"context" "context"
"database/sql"
"net/http" "net/http"
"reflect" "reflect"
"strings" "strings"
@ -64,14 +63,13 @@ func TestLoginFromJSONReader(t *testing.T) {
} }
for _, tst := range tsts { for _, tst := range tsts {
t.Run(tst.Name, func(t *testing.T) { t.Run(tst.Name, func(t *testing.T) {
var accountDB fakeAccountDB
var userAPI fakeUserInternalAPI var userAPI fakeUserInternalAPI
cfg := &config.ClientAPI{ cfg := &config.ClientAPI{
Matrix: &config.Global{ Matrix: &config.Global{
ServerName: serverName, ServerName: serverName,
}, },
} }
login, cleanup, err := LoginFromJSONReader(ctx, strings.NewReader(tst.Body), &accountDB, &userAPI, cfg) login, cleanup, err := LoginFromJSONReader(ctx, strings.NewReader(tst.Body), &userAPI, &userAPI, cfg)
if err != nil { if err != nil {
t.Fatalf("LoginFromJSONReader failed: %+v", err) t.Fatalf("LoginFromJSONReader failed: %+v", err)
} }
@ -143,14 +141,13 @@ func TestBadLoginFromJSONReader(t *testing.T) {
} }
for _, tst := range tsts { for _, tst := range tsts {
t.Run(tst.Name, func(t *testing.T) { t.Run(tst.Name, func(t *testing.T) {
var accountDB fakeAccountDB
var userAPI fakeUserInternalAPI var userAPI fakeUserInternalAPI
cfg := &config.ClientAPI{ cfg := &config.ClientAPI{
Matrix: &config.Global{ Matrix: &config.Global{
ServerName: serverName, ServerName: serverName,
}, },
} }
_, cleanup, errRes := LoginFromJSONReader(ctx, strings.NewReader(tst.Body), &accountDB, &userAPI, cfg) _, cleanup, errRes := LoginFromJSONReader(ctx, strings.NewReader(tst.Body), &userAPI, &userAPI, cfg)
if errRes == nil { if errRes == nil {
cleanup(ctx, nil) cleanup(ctx, nil)
t.Fatalf("LoginFromJSONReader err: got %+v, want code %q", errRes, tst.WantErrCode) t.Fatalf("LoginFromJSONReader err: got %+v, want code %q", errRes, tst.WantErrCode)
@ -161,24 +158,22 @@ func TestBadLoginFromJSONReader(t *testing.T) {
} }
} }
type fakeAccountDB struct {
AccountDatabase
}
func (*fakeAccountDB) GetAccountByPassword(ctx context.Context, localpart, password string) (*uapi.Account, error) {
if password == "invalidpassword" {
return nil, sql.ErrNoRows
}
return &uapi.Account{}, nil
}
type fakeUserInternalAPI struct { type fakeUserInternalAPI struct {
UserInternalAPIForLogin UserInternalAPIForLogin
uapi.UserAccountAPI
DeletedTokens []string DeletedTokens []string
} }
func (ua *fakeUserInternalAPI) QueryAccountByPassword(ctx context.Context, req *uapi.QueryAccountByPasswordRequest, res *uapi.QueryAccountByPasswordResponse) error {
if req.PlaintextPassword == "invalidpassword" {
res.Account = nil
return nil
}
res.Exists = true
res.Account = &uapi.Account{}
return nil
}
func (ua *fakeUserInternalAPI) PerformLoginTokenDeletion(ctx context.Context, req *uapi.PerformLoginTokenDeletionRequest, res *uapi.PerformLoginTokenDeletionResponse) error { func (ua *fakeUserInternalAPI) PerformLoginTokenDeletion(ctx context.Context, req *uapi.PerformLoginTokenDeletionRequest, res *uapi.PerformLoginTokenDeletionResponse) error {
ua.DeletedTokens = append(ua.DeletedTokens, req.Token) ua.DeletedTokens = append(ua.DeletedTokens, req.Token)
return nil return nil

View file

@ -16,7 +16,6 @@ package auth
import ( import (
"context" "context"
"database/sql"
"net/http" "net/http"
"strings" "strings"
@ -29,7 +28,7 @@ import (
"github.com/matrix-org/util" "github.com/matrix-org/util"
) )
type GetAccountByPassword func(ctx context.Context, localpart, password string) (*api.Account, error) type GetAccountByPassword func(ctx context.Context, req *api.QueryAccountByPasswordRequest, res *api.QueryAccountByPasswordResponse) error
type PasswordRequest struct { type PasswordRequest struct {
Login Login
@ -62,7 +61,7 @@ func (t *LoginTypePassword) LoginFromJSON(ctx context.Context, reqBytes []byte)
func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login, *util.JSONResponse) { func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login, *util.JSONResponse) {
r := req.(*PasswordRequest) r := req.(*PasswordRequest)
username := strings.ToLower(r.Username()) username := strings.ToLower(r.Username())
if username == "" { if username == "" {
return nil, &util.JSONResponse{ return nil, &util.JSONResponse{
Code: http.StatusUnauthorized, Code: http.StatusUnauthorized,
@ -77,19 +76,33 @@ func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login,
} }
} }
// Squash username to all lowercase letters // Squash username to all lowercase letters
_, err = t.GetAccountByPassword(ctx, strings.ToLower(localpart), r.Password) res := &api.QueryAccountByPasswordResponse{}
err = t.GetAccountByPassword(ctx, &api.QueryAccountByPasswordRequest{Localpart: strings.ToLower(localpart), PlaintextPassword: r.Password}, res)
if err != nil { if err != nil {
if err == sql.ErrNoRows { return nil, &util.JSONResponse{
_, err = t.GetAccountByPassword(ctx, localpart, r.Password) Code: http.StatusInternalServerError,
if err == nil { JSON: jsonerror.Unknown("unable to fetch account by password"),
return &r.Login, nil }
}
if !res.Exists {
err = t.GetAccountByPassword(ctx, &api.QueryAccountByPasswordRequest{
Localpart: localpart,
PlaintextPassword: r.Password,
}, res)
if err != nil {
return nil, &util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: jsonerror.Unknown("unable to fetch account by password"),
} }
} }
// Technically we could tell them if the user does not exist by checking if err == sql.ErrNoRows // Technically we could tell them if the user does not exist by checking if err == sql.ErrNoRows
// but that would leak the existence of the user. // but that would leak the existence of the user.
return nil, &util.JSONResponse{ if !res.Exists {
Code: http.StatusForbidden, return nil, &util.JSONResponse{
JSON: jsonerror.Forbidden("The username or password was incorrect or the account does not exist."), Code: http.StatusForbidden,
JSON: jsonerror.Forbidden("The username or password was incorrect or the account does not exist."),
}
} }
} }
return &r.Login, nil return &r.Login, nil

View file

@ -110,9 +110,9 @@ type UserInteractive struct {
Sessions map[string][]string Sessions map[string][]string
} }
func NewUserInteractive(accountDB AccountDatabase, cfg *config.ClientAPI) *UserInteractive { func NewUserInteractive(userAccountAPI api.UserAccountAPI, cfg *config.ClientAPI) *UserInteractive {
typePassword := &LoginTypePassword{ typePassword := &LoginTypePassword{
GetAccountByPassword: accountDB.GetAccountByPassword, GetAccountByPassword: userAccountAPI.QueryAccountByPassword,
Config: cfg, Config: cfg,
} }
return &UserInteractive{ return &UserInteractive{

View file

@ -25,15 +25,25 @@ var (
) )
type fakeAccountDatabase struct { type fakeAccountDatabase struct {
AccountDatabase api.UserAccountAPI
} }
func (*fakeAccountDatabase) GetAccountByPassword(ctx context.Context, localpart, plaintextPassword string) (*api.Account, error) { func (d *fakeAccountDatabase) PerformPasswordUpdate(ctx context.Context, req *api.PerformPasswordUpdateRequest, res *api.PerformPasswordUpdateResponse) error {
acc, ok := lookup[localpart+" "+plaintextPassword] return nil
}
func (d *fakeAccountDatabase) PerformAccountDeactivation(ctx context.Context, req *api.PerformAccountDeactivationRequest, res *api.PerformAccountDeactivationResponse) error {
return nil
}
func (d *fakeAccountDatabase) QueryAccountByPassword(ctx context.Context, req *api.QueryAccountByPasswordRequest, res *api.QueryAccountByPasswordResponse) error {
acc, ok := lookup[req.Localpart+" "+req.PlaintextPassword]
if !ok { if !ok {
return nil, fmt.Errorf("unknown user/password") return fmt.Errorf("unknown user/password")
} }
return acc, nil res.Account = acc
res.Exists = true
return nil
} }
func setup() *UserInteractive { func setup() *UserInteractive {

View file

@ -27,17 +27,17 @@ import (
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/setup/config"
"github.com/matrix-org/dendrite/setup/jetstream" "github.com/matrix-org/dendrite/setup/jetstream"
"github.com/matrix-org/dendrite/setup/process"
userapi "github.com/matrix-org/dendrite/userapi/api" userapi "github.com/matrix-org/dendrite/userapi/api"
userdb "github.com/matrix-org/dendrite/userapi/storage"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
) )
// AddPublicRoutes sets up and registers HTTP handlers for the ClientAPI component. // AddPublicRoutes sets up and registers HTTP handlers for the ClientAPI component.
func AddPublicRoutes( func AddPublicRoutes(
process *process.ProcessContext,
router *mux.Router, router *mux.Router,
synapseAdminRouter *mux.Router, synapseAdminRouter *mux.Router,
cfg *config.ClientAPI, cfg *config.ClientAPI,
accountsDB userdb.Database,
federation *gomatrixserverlib.FederationClient, federation *gomatrixserverlib.FederationClient,
rsAPI roomserverAPI.RoomserverInternalAPI, rsAPI roomserverAPI.RoomserverInternalAPI,
eduInputAPI eduServerAPI.EDUServerInputAPI, eduInputAPI eduServerAPI.EDUServerInputAPI,
@ -49,16 +49,16 @@ func AddPublicRoutes(
extRoomsProvider api.ExtraPublicRoomsProvider, extRoomsProvider api.ExtraPublicRoomsProvider,
mscCfg *config.MSCs, mscCfg *config.MSCs,
) { ) {
js, _ := jetstream.Prepare(&cfg.Matrix.JetStream) js, _ := jetstream.Prepare(process, &cfg.Matrix.JetStream)
syncProducer := &producers.SyncAPIProducer{ syncProducer := &producers.SyncAPIProducer{
JetStream: js, JetStream: js,
Topic: cfg.Matrix.JetStream.TopicFor(jetstream.OutputClientData), Topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputClientData),
} }
routing.Setup( routing.Setup(
router, synapseAdminRouter, cfg, eduInputAPI, rsAPI, asAPI, router, synapseAdminRouter, cfg, eduInputAPI, rsAPI, asAPI,
accountsDB, userAPI, federation, userAPI, federation,
syncProducer, transactionsCache, fsAPI, keyAPI, syncProducer, transactionsCache, fsAPI, keyAPI,
extRoomsProvider, mscCfg, extRoomsProvider, mscCfg,
) )

View file

@ -31,7 +31,6 @@ import (
"github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/clientapi/jsonerror"
"github.com/matrix-org/dendrite/internal/eventutil" "github.com/matrix-org/dendrite/internal/eventutil"
"github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/setup/config"
userdb "github.com/matrix-org/dendrite/userapi/storage"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util" "github.com/matrix-org/util"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -138,7 +137,7 @@ type fledglingEvent struct {
func CreateRoom( func CreateRoom(
req *http.Request, device *api.Device, req *http.Request, device *api.Device,
cfg *config.ClientAPI, cfg *config.ClientAPI,
accountDB userdb.Database, rsAPI roomserverAPI.RoomserverInternalAPI, profileAPI api.UserProfileAPI, rsAPI roomserverAPI.RoomserverInternalAPI,
asAPI appserviceAPI.AppServiceQueryAPI, asAPI appserviceAPI.AppServiceQueryAPI,
) util.JSONResponse { ) util.JSONResponse {
var r createRoomRequest var r createRoomRequest
@ -156,7 +155,7 @@ func CreateRoom(
JSON: jsonerror.InvalidArgumentValue(err.Error()), JSON: jsonerror.InvalidArgumentValue(err.Error()),
} }
} }
return createRoom(req.Context(), r, device, cfg, accountDB, rsAPI, asAPI, evTime) return createRoom(req.Context(), r, device, cfg, profileAPI, rsAPI, asAPI, evTime)
} }
// createRoom implements /createRoom // createRoom implements /createRoom
@ -165,7 +164,7 @@ func createRoom(
ctx context.Context, ctx context.Context,
r createRoomRequest, device *api.Device, r createRoomRequest, device *api.Device,
cfg *config.ClientAPI, cfg *config.ClientAPI,
accountDB userdb.Database, rsAPI roomserverAPI.RoomserverInternalAPI, profileAPI api.UserProfileAPI, rsAPI roomserverAPI.RoomserverInternalAPI,
asAPI appserviceAPI.AppServiceQueryAPI, asAPI appserviceAPI.AppServiceQueryAPI,
evTime time.Time, evTime time.Time,
) util.JSONResponse { ) util.JSONResponse {
@ -201,7 +200,7 @@ func createRoom(
"roomVersion": roomVersion, "roomVersion": roomVersion,
}).Info("Creating new room") }).Info("Creating new room")
profile, err := appserviceAPI.RetrieveUserProfile(ctx, userID, asAPI, accountDB) profile, err := appserviceAPI.RetrieveUserProfile(ctx, userID, asAPI, profileAPI)
if err != nil { if err != nil {
util.GetLogger(ctx).WithError(err).Error("appserviceAPI.RetrieveUserProfile failed") util.GetLogger(ctx).WithError(err).Error("appserviceAPI.RetrieveUserProfile failed")
return jsonerror.InternalServerError() return jsonerror.InternalServerError()
@ -520,7 +519,7 @@ func createRoom(
for _, invitee := range r.Invite { for _, invitee := range r.Invite {
// Build the invite event. // Build the invite event.
inviteEvent, err := buildMembershipEvent( inviteEvent, err := buildMembershipEvent(
ctx, invitee, "", accountDB, device, gomatrixserverlib.Invite, ctx, invitee, "", profileAPI, device, gomatrixserverlib.Invite,
roomID, true, cfg, evTime, rsAPI, asAPI, roomID, true, cfg, evTime, rsAPI, asAPI,
) )
if err != nil { if err != nil {

View file

@ -15,7 +15,7 @@ import (
func Deactivate( func Deactivate(
req *http.Request, req *http.Request,
userInteractiveAuth *auth.UserInteractive, userInteractiveAuth *auth.UserInteractive,
userAPI api.UserInternalAPI, accountAPI api.UserAccountAPI,
deviceAPI *api.Device, deviceAPI *api.Device,
) util.JSONResponse { ) util.JSONResponse {
ctx := req.Context() ctx := req.Context()
@ -40,7 +40,7 @@ func Deactivate(
} }
var res api.PerformAccountDeactivationResponse var res api.PerformAccountDeactivationResponse
err = userAPI.PerformAccountDeactivation(ctx, &api.PerformAccountDeactivationRequest{ err = accountAPI.PerformAccountDeactivation(ctx, &api.PerformAccountDeactivationRequest{
Localpart: localpart, Localpart: localpart,
}, &res) }, &res)
if err != nil { if err != nil {

View file

@ -18,12 +18,10 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
"github.com/matrix-org/dendrite/clientapi/httputil" "github.com/matrix-org/dendrite/clientapi/httputil"
"github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/clientapi/jsonerror"
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/dendrite/userapi/api"
userdb "github.com/matrix-org/dendrite/userapi/storage"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util" "github.com/matrix-org/util"
) )
@ -32,7 +30,7 @@ func JoinRoomByIDOrAlias(
req *http.Request, req *http.Request,
device *api.Device, device *api.Device,
rsAPI roomserverAPI.RoomserverInternalAPI, rsAPI roomserverAPI.RoomserverInternalAPI,
accountDB userdb.Database, profileAPI api.UserProfileAPI,
roomIDOrAlias string, roomIDOrAlias string,
) util.JSONResponse { ) util.JSONResponse {
// Prepare to ask the roomserver to perform the room join. // Prepare to ask the roomserver to perform the room join.
@ -60,19 +58,23 @@ func JoinRoomByIDOrAlias(
_ = httputil.UnmarshalJSONRequest(req, &joinReq.Content) _ = httputil.UnmarshalJSONRequest(req, &joinReq.Content)
// Work out our localpart for the client profile request. // Work out our localpart for the client profile request.
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
if err != nil { // Request our profile content to populate the request content with.
util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed") res := &api.QueryProfileResponse{}
} else { err := profileAPI.QueryProfile(req.Context(), &api.QueryProfileRequest{UserID: device.UserID}, res)
// Request our profile content to populate the request content with. if err != nil || !res.UserExists {
var profile *authtypes.Profile if !res.UserExists {
profile, err = accountDB.GetProfileByLocalpart(req.Context(), localpart) util.GetLogger(req.Context()).Error("Unable to query user profile, no profile found.")
if err != nil { return util.JSONResponse{
util.GetLogger(req.Context()).WithError(err).Error("accountDB.GetProfileByLocalpart failed") Code: http.StatusInternalServerError,
} else { JSON: jsonerror.Unknown("Unable to query user profile, no profile found."),
joinReq.Content["displayname"] = profile.DisplayName }
joinReq.Content["avatar_url"] = profile.AvatarURL
} }
util.GetLogger(req.Context()).WithError(err).Error("UserProfileAPI.QueryProfile failed")
} else {
joinReq.Content["displayname"] = res.DisplayName
joinReq.Content["avatar_url"] = res.AvatarURL
} }
// Ask the roomserver to perform the join. // Ask the roomserver to perform the join.

View file

@ -24,7 +24,6 @@ import (
"github.com/matrix-org/dendrite/keyserver/api" "github.com/matrix-org/dendrite/keyserver/api"
"github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/setup/config"
userapi "github.com/matrix-org/dendrite/userapi/api" userapi "github.com/matrix-org/dendrite/userapi/api"
userdb "github.com/matrix-org/dendrite/userapi/storage"
"github.com/matrix-org/util" "github.com/matrix-org/util"
) )
@ -36,7 +35,7 @@ type crossSigningRequest struct {
func UploadCrossSigningDeviceKeys( func UploadCrossSigningDeviceKeys(
req *http.Request, userInteractiveAuth *auth.UserInteractive, req *http.Request, userInteractiveAuth *auth.UserInteractive,
keyserverAPI api.KeyInternalAPI, device *userapi.Device, keyserverAPI api.KeyInternalAPI, device *userapi.Device,
accountDB userdb.Database, cfg *config.ClientAPI, accountAPI userapi.UserAccountAPI, cfg *config.ClientAPI,
) util.JSONResponse { ) util.JSONResponse {
uploadReq := &crossSigningRequest{} uploadReq := &crossSigningRequest{}
uploadRes := &api.PerformUploadDeviceKeysResponse{} uploadRes := &api.PerformUploadDeviceKeysResponse{}
@ -64,7 +63,7 @@ func UploadCrossSigningDeviceKeys(
} }
} }
typePassword := auth.LoginTypePassword{ typePassword := auth.LoginTypePassword{
GetAccountByPassword: accountDB.GetAccountByPassword, GetAccountByPassword: accountAPI.QueryAccountByPassword,
Config: cfg, Config: cfg,
} }
if _, authErr := typePassword.Login(req.Context(), &uploadReq.Auth.PasswordRequest); authErr != nil { if _, authErr := typePassword.Login(req.Context(), &uploadReq.Auth.PasswordRequest); authErr != nil {

View file

@ -23,7 +23,6 @@ import (
"github.com/matrix-org/dendrite/clientapi/userutil" "github.com/matrix-org/dendrite/clientapi/userutil"
"github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/setup/config"
userapi "github.com/matrix-org/dendrite/userapi/api" userapi "github.com/matrix-org/dendrite/userapi/api"
userdb "github.com/matrix-org/dendrite/userapi/storage"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util" "github.com/matrix-org/util"
) )
@ -54,7 +53,7 @@ func passwordLogin() flows {
// Login implements GET and POST /login // Login implements GET and POST /login
func Login( func Login(
req *http.Request, accountDB userdb.Database, userAPI userapi.UserInternalAPI, req *http.Request, userAPI userapi.UserInternalAPI,
cfg *config.ClientAPI, cfg *config.ClientAPI,
) util.JSONResponse { ) util.JSONResponse {
if req.Method == http.MethodGet { if req.Method == http.MethodGet {
@ -64,7 +63,7 @@ func Login(
JSON: passwordLogin(), JSON: passwordLogin(),
} }
} else if req.Method == http.MethodPost { } else if req.Method == http.MethodPost {
login, cleanup, authErr := auth.LoginFromJSONReader(req.Context(), req.Body, accountDB, userAPI, cfg) login, cleanup, authErr := auth.LoginFromJSONReader(req.Context(), req.Body, userAPI, userAPI, cfg)
if authErr != nil { if authErr != nil {
return *authErr return *authErr
} }

View file

@ -30,7 +30,6 @@ import (
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/setup/config"
userapi "github.com/matrix-org/dendrite/userapi/api" userapi "github.com/matrix-org/dendrite/userapi/api"
userdb "github.com/matrix-org/dendrite/userapi/storage"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util" "github.com/matrix-org/util"
@ -39,7 +38,7 @@ import (
var errMissingUserID = errors.New("'user_id' must be supplied") var errMissingUserID = errors.New("'user_id' must be supplied")
func SendBan( func SendBan(
req *http.Request, accountDB userdb.Database, device *userapi.Device, req *http.Request, profileAPI userapi.UserProfileAPI, device *userapi.Device,
roomID string, cfg *config.ClientAPI, roomID string, cfg *config.ClientAPI,
rsAPI roomserverAPI.RoomserverInternalAPI, asAPI appserviceAPI.AppServiceQueryAPI, rsAPI roomserverAPI.RoomserverInternalAPI, asAPI appserviceAPI.AppServiceQueryAPI,
) util.JSONResponse { ) util.JSONResponse {
@ -78,16 +77,16 @@ func SendBan(
} }
} }
return sendMembership(req.Context(), accountDB, device, roomID, "ban", body.Reason, cfg, body.UserID, evTime, roomVer, rsAPI, asAPI) return sendMembership(req.Context(), profileAPI, device, roomID, "ban", body.Reason, cfg, body.UserID, evTime, roomVer, rsAPI, asAPI)
} }
func sendMembership(ctx context.Context, accountDB userdb.Database, device *userapi.Device, func sendMembership(ctx context.Context, profileAPI userapi.UserProfileAPI, device *userapi.Device,
roomID, membership, reason string, cfg *config.ClientAPI, targetUserID string, evTime time.Time, roomID, membership, reason string, cfg *config.ClientAPI, targetUserID string, evTime time.Time,
roomVer gomatrixserverlib.RoomVersion, roomVer gomatrixserverlib.RoomVersion,
rsAPI roomserverAPI.RoomserverInternalAPI, asAPI appserviceAPI.AppServiceQueryAPI) util.JSONResponse { rsAPI roomserverAPI.RoomserverInternalAPI, asAPI appserviceAPI.AppServiceQueryAPI) util.JSONResponse {
event, err := buildMembershipEvent( event, err := buildMembershipEvent(
ctx, targetUserID, reason, accountDB, device, membership, ctx, targetUserID, reason, profileAPI, device, membership,
roomID, false, cfg, evTime, rsAPI, asAPI, roomID, false, cfg, evTime, rsAPI, asAPI,
) )
if err == errMissingUserID { if err == errMissingUserID {
@ -125,7 +124,7 @@ func sendMembership(ctx context.Context, accountDB userdb.Database, device *user
} }
func SendKick( func SendKick(
req *http.Request, accountDB userdb.Database, device *userapi.Device, req *http.Request, profileAPI userapi.UserProfileAPI, device *userapi.Device,
roomID string, cfg *config.ClientAPI, roomID string, cfg *config.ClientAPI,
rsAPI roomserverAPI.RoomserverInternalAPI, asAPI appserviceAPI.AppServiceQueryAPI, rsAPI roomserverAPI.RoomserverInternalAPI, asAPI appserviceAPI.AppServiceQueryAPI,
) util.JSONResponse { ) util.JSONResponse {
@ -161,11 +160,11 @@ func SendKick(
} }
} }
// TODO: should we be using SendLeave instead? // TODO: should we be using SendLeave instead?
return sendMembership(req.Context(), accountDB, device, roomID, "leave", body.Reason, cfg, body.UserID, evTime, roomVer, rsAPI, asAPI) return sendMembership(req.Context(), profileAPI, device, roomID, "leave", body.Reason, cfg, body.UserID, evTime, roomVer, rsAPI, asAPI)
} }
func SendUnban( func SendUnban(
req *http.Request, accountDB userdb.Database, device *userapi.Device, req *http.Request, profileAPI userapi.UserProfileAPI, device *userapi.Device,
roomID string, cfg *config.ClientAPI, roomID string, cfg *config.ClientAPI,
rsAPI roomserverAPI.RoomserverInternalAPI, asAPI appserviceAPI.AppServiceQueryAPI, rsAPI roomserverAPI.RoomserverInternalAPI, asAPI appserviceAPI.AppServiceQueryAPI,
) util.JSONResponse { ) util.JSONResponse {
@ -196,11 +195,11 @@ func SendUnban(
} }
} }
// TODO: should we be using SendLeave instead? // TODO: should we be using SendLeave instead?
return sendMembership(req.Context(), accountDB, device, roomID, "leave", body.Reason, cfg, body.UserID, evTime, roomVer, rsAPI, asAPI) return sendMembership(req.Context(), profileAPI, device, roomID, "leave", body.Reason, cfg, body.UserID, evTime, roomVer, rsAPI, asAPI)
} }
func SendInvite( func SendInvite(
req *http.Request, accountDB userdb.Database, device *userapi.Device, req *http.Request, profileAPI userapi.UserProfileAPI, device *userapi.Device,
roomID string, cfg *config.ClientAPI, roomID string, cfg *config.ClientAPI,
rsAPI roomserverAPI.RoomserverInternalAPI, asAPI appserviceAPI.AppServiceQueryAPI, rsAPI roomserverAPI.RoomserverInternalAPI, asAPI appserviceAPI.AppServiceQueryAPI,
) util.JSONResponse { ) util.JSONResponse {
@ -210,7 +209,7 @@ func SendInvite(
} }
inviteStored, jsonErrResp := checkAndProcessThreepid( inviteStored, jsonErrResp := checkAndProcessThreepid(
req, device, body, cfg, rsAPI, accountDB, roomID, evTime, req, device, body, cfg, rsAPI, profileAPI, roomID, evTime,
) )
if jsonErrResp != nil { if jsonErrResp != nil {
return *jsonErrResp return *jsonErrResp
@ -227,14 +226,14 @@ func SendInvite(
} }
// We already received the return value, so no need to check for an error here. // We already received the return value, so no need to check for an error here.
response, _ := sendInvite(req.Context(), accountDB, device, roomID, body.UserID, body.Reason, cfg, rsAPI, asAPI, evTime) response, _ := sendInvite(req.Context(), profileAPI, device, roomID, body.UserID, body.Reason, cfg, rsAPI, asAPI, evTime)
return response return response
} }
// sendInvite sends an invitation to a user. Returns a JSONResponse and an error // sendInvite sends an invitation to a user. Returns a JSONResponse and an error
func sendInvite( func sendInvite(
ctx context.Context, ctx context.Context,
accountDB userdb.Database, profileAPI userapi.UserProfileAPI,
device *userapi.Device, device *userapi.Device,
roomID, userID, reason string, roomID, userID, reason string,
cfg *config.ClientAPI, cfg *config.ClientAPI,
@ -242,7 +241,7 @@ func sendInvite(
asAPI appserviceAPI.AppServiceQueryAPI, evTime time.Time, asAPI appserviceAPI.AppServiceQueryAPI, evTime time.Time,
) (util.JSONResponse, error) { ) (util.JSONResponse, error) {
event, err := buildMembershipEvent( event, err := buildMembershipEvent(
ctx, userID, reason, accountDB, device, "invite", ctx, userID, reason, profileAPI, device, "invite",
roomID, false, cfg, evTime, rsAPI, asAPI, roomID, false, cfg, evTime, rsAPI, asAPI,
) )
if err == errMissingUserID { if err == errMissingUserID {
@ -286,13 +285,13 @@ func sendInvite(
func buildMembershipEvent( func buildMembershipEvent(
ctx context.Context, ctx context.Context,
targetUserID, reason string, accountDB userdb.Database, targetUserID, reason string, profileAPI userapi.UserProfileAPI,
device *userapi.Device, device *userapi.Device,
membership, roomID string, isDirect bool, membership, roomID string, isDirect bool,
cfg *config.ClientAPI, evTime time.Time, cfg *config.ClientAPI, evTime time.Time,
rsAPI roomserverAPI.RoomserverInternalAPI, asAPI appserviceAPI.AppServiceQueryAPI, rsAPI roomserverAPI.RoomserverInternalAPI, asAPI appserviceAPI.AppServiceQueryAPI,
) (*gomatrixserverlib.HeaderedEvent, error) { ) (*gomatrixserverlib.HeaderedEvent, error) {
profile, err := loadProfile(ctx, targetUserID, cfg, accountDB, asAPI) profile, err := loadProfile(ctx, targetUserID, cfg, profileAPI, asAPI)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -327,7 +326,7 @@ func loadProfile(
ctx context.Context, ctx context.Context,
userID string, userID string,
cfg *config.ClientAPI, cfg *config.ClientAPI,
accountDB userdb.Database, profileAPI userapi.UserProfileAPI,
asAPI appserviceAPI.AppServiceQueryAPI, asAPI appserviceAPI.AppServiceQueryAPI,
) (*authtypes.Profile, error) { ) (*authtypes.Profile, error) {
_, serverName, err := gomatrixserverlib.SplitID('@', userID) _, serverName, err := gomatrixserverlib.SplitID('@', userID)
@ -337,7 +336,7 @@ func loadProfile(
var profile *authtypes.Profile var profile *authtypes.Profile
if serverName == cfg.Matrix.ServerName { if serverName == cfg.Matrix.ServerName {
profile, err = appserviceAPI.RetrieveUserProfile(ctx, userID, asAPI, accountDB) profile, err = appserviceAPI.RetrieveUserProfile(ctx, userID, asAPI, profileAPI)
} else { } else {
profile = &authtypes.Profile{} profile = &authtypes.Profile{}
} }
@ -381,13 +380,13 @@ func checkAndProcessThreepid(
body *threepid.MembershipRequest, body *threepid.MembershipRequest,
cfg *config.ClientAPI, cfg *config.ClientAPI,
rsAPI roomserverAPI.RoomserverInternalAPI, rsAPI roomserverAPI.RoomserverInternalAPI,
accountDB userdb.Database, profileAPI userapi.UserProfileAPI,
roomID string, roomID string,
evTime time.Time, evTime time.Time,
) (inviteStored bool, errRes *util.JSONResponse) { ) (inviteStored bool, errRes *util.JSONResponse) {
inviteStored, err := threepid.CheckAndProcessInvite( inviteStored, err := threepid.CheckAndProcessInvite(
req.Context(), device, body, cfg, rsAPI, accountDB, req.Context(), device, body, cfg, rsAPI, profileAPI,
roomID, evTime, roomID, evTime,
) )
if err == threepid.ErrMissingParameter { if err == threepid.ErrMissingParameter {

View file

@ -9,7 +9,6 @@ import (
"github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/clientapi/jsonerror"
"github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/setup/config"
"github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/dendrite/userapi/api"
userdb "github.com/matrix-org/dendrite/userapi/storage"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util" "github.com/matrix-org/util"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -30,7 +29,6 @@ type newPasswordAuth struct {
func Password( func Password(
req *http.Request, req *http.Request,
userAPI api.UserInternalAPI, userAPI api.UserInternalAPI,
accountDB userdb.Database,
device *api.Device, device *api.Device,
cfg *config.ClientAPI, cfg *config.ClientAPI,
) util.JSONResponse { ) util.JSONResponse {
@ -74,7 +72,7 @@ func Password(
// Check if the existing password is correct. // Check if the existing password is correct.
typePassword := auth.LoginTypePassword{ typePassword := auth.LoginTypePassword{
GetAccountByPassword: accountDB.GetAccountByPassword, GetAccountByPassword: userAPI.QueryAccountByPassword,
Config: cfg, Config: cfg,
} }
if _, authErr := typePassword.Login(req.Context(), &r.Auth.PasswordRequest); authErr != nil { if _, authErr := typePassword.Login(req.Context(), &r.Auth.PasswordRequest); authErr != nil {

View file

@ -19,7 +19,6 @@ import (
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/dendrite/userapi/api"
userdb "github.com/matrix-org/dendrite/userapi/storage"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util" "github.com/matrix-org/util"
) )
@ -28,7 +27,6 @@ func PeekRoomByIDOrAlias(
req *http.Request, req *http.Request,
device *api.Device, device *api.Device,
rsAPI roomserverAPI.RoomserverInternalAPI, rsAPI roomserverAPI.RoomserverInternalAPI,
accountDB userdb.Database,
roomIDOrAlias string, roomIDOrAlias string,
) util.JSONResponse { ) util.JSONResponse {
// if this is a remote roomIDOrAlias, we have to ask the roomserver (or federation sender?) to // if this is a remote roomIDOrAlias, we have to ask the roomserver (or federation sender?) to
@ -82,7 +80,6 @@ func UnpeekRoomByID(
req *http.Request, req *http.Request,
device *api.Device, device *api.Device,
rsAPI roomserverAPI.RoomserverInternalAPI, rsAPI roomserverAPI.RoomserverInternalAPI,
accountDB userdb.Database,
roomID string, roomID string,
) util.JSONResponse { ) util.JSONResponse {
unpeekReq := roomserverAPI.PerformUnpeekRequest{ unpeekReq := roomserverAPI.PerformUnpeekRequest{

View file

@ -27,7 +27,6 @@ import (
"github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/setup/config"
userapi "github.com/matrix-org/dendrite/userapi/api" userapi "github.com/matrix-org/dendrite/userapi/api"
userdb "github.com/matrix-org/dendrite/userapi/storage"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrix" "github.com/matrix-org/gomatrix"
@ -36,12 +35,12 @@ import (
// GetProfile implements GET /profile/{userID} // GetProfile implements GET /profile/{userID}
func GetProfile( func GetProfile(
req *http.Request, accountDB userdb.Database, cfg *config.ClientAPI, req *http.Request, profileAPI userapi.UserProfileAPI, cfg *config.ClientAPI,
userID string, userID string,
asAPI appserviceAPI.AppServiceQueryAPI, asAPI appserviceAPI.AppServiceQueryAPI,
federation *gomatrixserverlib.FederationClient, federation *gomatrixserverlib.FederationClient,
) util.JSONResponse { ) util.JSONResponse {
profile, err := getProfile(req.Context(), accountDB, cfg, userID, asAPI, federation) profile, err := getProfile(req.Context(), profileAPI, cfg, userID, asAPI, federation)
if err != nil { if err != nil {
if err == eventutil.ErrProfileNoExists { if err == eventutil.ErrProfileNoExists {
return util.JSONResponse{ return util.JSONResponse{
@ -65,11 +64,11 @@ func GetProfile(
// GetAvatarURL implements GET /profile/{userID}/avatar_url // GetAvatarURL implements GET /profile/{userID}/avatar_url
func GetAvatarURL( func GetAvatarURL(
req *http.Request, accountDB userdb.Database, cfg *config.ClientAPI, req *http.Request, profileAPI userapi.UserProfileAPI, cfg *config.ClientAPI,
userID string, asAPI appserviceAPI.AppServiceQueryAPI, userID string, asAPI appserviceAPI.AppServiceQueryAPI,
federation *gomatrixserverlib.FederationClient, federation *gomatrixserverlib.FederationClient,
) util.JSONResponse { ) util.JSONResponse {
profile, err := getProfile(req.Context(), accountDB, cfg, userID, asAPI, federation) profile, err := getProfile(req.Context(), profileAPI, cfg, userID, asAPI, federation)
if err != nil { if err != nil {
if err == eventutil.ErrProfileNoExists { if err == eventutil.ErrProfileNoExists {
return util.JSONResponse{ return util.JSONResponse{
@ -92,7 +91,7 @@ func GetAvatarURL(
// SetAvatarURL implements PUT /profile/{userID}/avatar_url // SetAvatarURL implements PUT /profile/{userID}/avatar_url
func SetAvatarURL( func SetAvatarURL(
req *http.Request, accountDB userdb.Database, req *http.Request, profileAPI userapi.UserProfileAPI,
device *userapi.Device, userID string, cfg *config.ClientAPI, rsAPI api.RoomserverInternalAPI, device *userapi.Device, userID string, cfg *config.ClientAPI, rsAPI api.RoomserverInternalAPI,
) util.JSONResponse { ) util.JSONResponse {
if userID != device.UserID { if userID != device.UserID {
@ -127,22 +126,34 @@ func SetAvatarURL(
} }
} }
oldProfile, err := accountDB.GetProfileByLocalpart(req.Context(), localpart) res := &userapi.QueryProfileResponse{}
err = profileAPI.QueryProfile(req.Context(), &userapi.QueryProfileRequest{
UserID: userID,
}, res)
if err != nil { if err != nil {
util.GetLogger(req.Context()).WithError(err).Error("accountDB.GetProfileByLocalpart failed") util.GetLogger(req.Context()).WithError(err).Error("profileAPI.QueryProfile failed")
return jsonerror.InternalServerError()
}
oldProfile := &authtypes.Profile{
Localpart: localpart,
DisplayName: res.DisplayName,
AvatarURL: res.AvatarURL,
}
setRes := &userapi.PerformSetAvatarURLResponse{}
if err = profileAPI.SetAvatarURL(req.Context(), &userapi.PerformSetAvatarURLRequest{
Localpart: localpart,
AvatarURL: r.AvatarURL,
}, setRes); err != nil {
util.GetLogger(req.Context()).WithError(err).Error("profileAPI.SetAvatarURL failed")
return jsonerror.InternalServerError() return jsonerror.InternalServerError()
} }
if err = accountDB.SetAvatarURL(req.Context(), localpart, r.AvatarURL); err != nil { var roomsRes api.QueryRoomsForUserResponse
util.GetLogger(req.Context()).WithError(err).Error("accountDB.SetAvatarURL failed")
return jsonerror.InternalServerError()
}
var res api.QueryRoomsForUserResponse
err = rsAPI.QueryRoomsForUser(req.Context(), &api.QueryRoomsForUserRequest{ err = rsAPI.QueryRoomsForUser(req.Context(), &api.QueryRoomsForUserRequest{
UserID: device.UserID, UserID: device.UserID,
WantMembership: "join", WantMembership: "join",
}, &res) }, &roomsRes)
if err != nil { if err != nil {
util.GetLogger(req.Context()).WithError(err).Error("QueryRoomsForUser failed") util.GetLogger(req.Context()).WithError(err).Error("QueryRoomsForUser failed")
return jsonerror.InternalServerError() return jsonerror.InternalServerError()
@ -155,7 +166,7 @@ func SetAvatarURL(
} }
events, err := buildMembershipEvents( events, err := buildMembershipEvents(
req.Context(), res.RoomIDs, newProfile, userID, cfg, evTime, rsAPI, req.Context(), roomsRes.RoomIDs, newProfile, userID, cfg, evTime, rsAPI,
) )
switch e := err.(type) { switch e := err.(type) {
case nil: case nil:
@ -182,11 +193,11 @@ func SetAvatarURL(
// GetDisplayName implements GET /profile/{userID}/displayname // GetDisplayName implements GET /profile/{userID}/displayname
func GetDisplayName( func GetDisplayName(
req *http.Request, accountDB userdb.Database, cfg *config.ClientAPI, req *http.Request, profileAPI userapi.UserProfileAPI, cfg *config.ClientAPI,
userID string, asAPI appserviceAPI.AppServiceQueryAPI, userID string, asAPI appserviceAPI.AppServiceQueryAPI,
federation *gomatrixserverlib.FederationClient, federation *gomatrixserverlib.FederationClient,
) util.JSONResponse { ) util.JSONResponse {
profile, err := getProfile(req.Context(), accountDB, cfg, userID, asAPI, federation) profile, err := getProfile(req.Context(), profileAPI, cfg, userID, asAPI, federation)
if err != nil { if err != nil {
if err == eventutil.ErrProfileNoExists { if err == eventutil.ErrProfileNoExists {
return util.JSONResponse{ return util.JSONResponse{
@ -209,7 +220,7 @@ func GetDisplayName(
// SetDisplayName implements PUT /profile/{userID}/displayname // SetDisplayName implements PUT /profile/{userID}/displayname
func SetDisplayName( func SetDisplayName(
req *http.Request, accountDB userdb.Database, req *http.Request, profileAPI userapi.UserProfileAPI,
device *userapi.Device, userID string, cfg *config.ClientAPI, rsAPI api.RoomserverInternalAPI, device *userapi.Device, userID string, cfg *config.ClientAPI, rsAPI api.RoomserverInternalAPI,
) util.JSONResponse { ) util.JSONResponse {
if userID != device.UserID { if userID != device.UserID {
@ -244,14 +255,26 @@ func SetDisplayName(
} }
} }
oldProfile, err := accountDB.GetProfileByLocalpart(req.Context(), localpart) pRes := &userapi.QueryProfileResponse{}
err = profileAPI.QueryProfile(req.Context(), &userapi.QueryProfileRequest{
UserID: userID,
}, pRes)
if err != nil { if err != nil {
util.GetLogger(req.Context()).WithError(err).Error("accountDB.GetProfileByLocalpart failed") util.GetLogger(req.Context()).WithError(err).Error("profileAPI.QueryProfile failed")
return jsonerror.InternalServerError() return jsonerror.InternalServerError()
} }
oldProfile := &authtypes.Profile{
Localpart: localpart,
DisplayName: pRes.DisplayName,
AvatarURL: pRes.AvatarURL,
}
if err = accountDB.SetDisplayName(req.Context(), localpart, r.DisplayName); err != nil { err = profileAPI.SetDisplayName(req.Context(), &userapi.PerformUpdateDisplayNameRequest{
util.GetLogger(req.Context()).WithError(err).Error("accountDB.SetDisplayName failed") Localpart: localpart,
DisplayName: r.DisplayName,
}, &struct{}{})
if err != nil {
util.GetLogger(req.Context()).WithError(err).Error("profileAPI.SetDisplayName failed")
return jsonerror.InternalServerError() return jsonerror.InternalServerError()
} }
@ -302,7 +325,7 @@ func SetDisplayName(
// Returns an error when something goes wrong or specifically // Returns an error when something goes wrong or specifically
// eventutil.ErrProfileNoExists when the profile doesn't exist. // eventutil.ErrProfileNoExists when the profile doesn't exist.
func getProfile( func getProfile(
ctx context.Context, accountDB userdb.Database, cfg *config.ClientAPI, ctx context.Context, profileAPI userapi.UserProfileAPI, cfg *config.ClientAPI,
userID string, userID string,
asAPI appserviceAPI.AppServiceQueryAPI, asAPI appserviceAPI.AppServiceQueryAPI,
federation *gomatrixserverlib.FederationClient, federation *gomatrixserverlib.FederationClient,
@ -331,7 +354,7 @@ func getProfile(
}, nil }, nil
} }
profile, err := appserviceAPI.RetrieveUserProfile(ctx, userID, asAPI, accountDB) profile, err := appserviceAPI.RetrieveUserProfile(ctx, userID, asAPI, profileAPI)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -44,7 +44,6 @@ import (
"github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/clientapi/jsonerror"
"github.com/matrix-org/dendrite/clientapi/userutil" "github.com/matrix-org/dendrite/clientapi/userutil"
userapi "github.com/matrix-org/dendrite/userapi/api" userapi "github.com/matrix-org/dendrite/userapi/api"
userdb "github.com/matrix-org/dendrite/userapi/storage"
) )
var ( var (
@ -523,8 +522,7 @@ func validateApplicationService(
// http://matrix.org/speculator/spec/HEAD/client_server/unstable.html#post-matrix-client-unstable-register // http://matrix.org/speculator/spec/HEAD/client_server/unstable.html#post-matrix-client-unstable-register
func Register( func Register(
req *http.Request, req *http.Request,
userAPI userapi.UserInternalAPI, userAPI userapi.UserRegisterAPI,
accountDB userdb.Database,
cfg *config.ClientAPI, cfg *config.ClientAPI,
) util.JSONResponse { ) util.JSONResponse {
var r registerRequest var r registerRequest
@ -552,13 +550,12 @@ func Register(
} }
// Auto generate a numeric username if r.Username is empty // Auto generate a numeric username if r.Username is empty
if r.Username == "" { if r.Username == "" {
id, err := accountDB.GetNewNumericLocalpart(req.Context()) res := &userapi.QueryNumericLocalpartResponse{}
if err != nil { if err := userAPI.QueryNumericLocalpart(req.Context(), res); err != nil {
util.GetLogger(req.Context()).WithError(err).Error("accountDB.GetNewNumericLocalpart failed") util.GetLogger(req.Context()).WithError(err).Error("userAPI.QueryNumericLocalpart failed")
return jsonerror.InternalServerError() return jsonerror.InternalServerError()
} }
r.Username = strconv.FormatInt(res.ID, 10)
r.Username = strconv.FormatInt(id, 10)
} }
// Is this an appservice registration? It will be if the access // Is this an appservice registration? It will be if the access
@ -606,7 +603,7 @@ func handleGuestRegistration(
req *http.Request, req *http.Request,
r registerRequest, r registerRequest,
cfg *config.ClientAPI, cfg *config.ClientAPI,
userAPI userapi.UserInternalAPI, userAPI userapi.UserRegisterAPI,
) util.JSONResponse { ) util.JSONResponse {
if cfg.RegistrationDisabled || cfg.GuestsDisabled { if cfg.RegistrationDisabled || cfg.GuestsDisabled {
return util.JSONResponse{ return util.JSONResponse{
@ -671,7 +668,7 @@ func handleRegistrationFlow(
r registerRequest, r registerRequest,
sessionID string, sessionID string,
cfg *config.ClientAPI, cfg *config.ClientAPI,
userAPI userapi.UserInternalAPI, userAPI userapi.UserRegisterAPI,
accessToken string, accessToken string,
accessTokenErr error, accessTokenErr error,
) util.JSONResponse { ) util.JSONResponse {
@ -762,7 +759,7 @@ func handleApplicationServiceRegistration(
req *http.Request, req *http.Request,
r registerRequest, r registerRequest,
cfg *config.ClientAPI, cfg *config.ClientAPI,
userAPI userapi.UserInternalAPI, userAPI userapi.UserRegisterAPI,
) util.JSONResponse { ) util.JSONResponse {
// Check if we previously had issues extracting the access token from the // Check if we previously had issues extracting the access token from the
// request. // request.
@ -805,7 +802,7 @@ func checkAndCompleteFlow(
r registerRequest, r registerRequest,
sessionID string, sessionID string,
cfg *config.ClientAPI, cfg *config.ClientAPI,
userAPI userapi.UserInternalAPI, userAPI userapi.UserRegisterAPI,
) util.JSONResponse { ) util.JSONResponse {
if checkFlowCompleted(flow, cfg.Derived.Registration.Flows) { if checkFlowCompleted(flow, cfg.Derived.Registration.Flows) {
policyVersion := "" policyVersion := ""
@ -836,7 +833,7 @@ func checkAndCompleteFlow(
// not all // not all
func completeRegistration( func completeRegistration(
ctx context.Context, ctx context.Context,
userAPI userapi.UserInternalAPI, userAPI userapi.UserRegisterAPI,
username, password, appserviceID, ipAddr, userAgent, sessionID, policyVersion string, username, password, appserviceID, ipAddr, userAgent, sessionID, policyVersion string,
inhibitLogin eventutil.WeakBoolean, inhibitLogin eventutil.WeakBoolean,
displayName, deviceID *string, displayName, deviceID *string,
@ -1003,7 +1000,7 @@ type availableResponse struct {
func RegisterAvailable( func RegisterAvailable(
req *http.Request, req *http.Request,
cfg *config.ClientAPI, cfg *config.ClientAPI,
accountDB userdb.Database, registerAPI userapi.UserRegisterAPI,
) util.JSONResponse { ) util.JSONResponse {
username := req.URL.Query().Get("username") username := req.URL.Query().Get("username")
@ -1025,14 +1022,18 @@ func RegisterAvailable(
} }
} }
availability, availabilityErr := accountDB.CheckAccountAvailability(req.Context(), username) res := &userapi.QueryAccountAvailabilityResponse{}
if availabilityErr != nil { err := registerAPI.QueryAccountAvailability(req.Context(), &userapi.QueryAccountAvailabilityRequest{
Localpart: username,
}, res)
if err != nil {
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusInternalServerError, Code: http.StatusInternalServerError,
JSON: jsonerror.Unknown("failed to check availability: " + availabilityErr.Error()), JSON: jsonerror.Unknown("failed to check availability:" + err.Error()),
} }
} }
if !availability {
if !res.Available {
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
JSON: jsonerror.UserInUse("Desired User ID is already taken."), JSON: jsonerror.UserInUse("Desired User ID is already taken."),

View file

@ -34,7 +34,6 @@ import (
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/setup/config"
userapi "github.com/matrix-org/dendrite/userapi/api" userapi "github.com/matrix-org/dendrite/userapi/api"
userdb "github.com/matrix-org/dendrite/userapi/storage"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util" "github.com/matrix-org/util"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -51,7 +50,6 @@ func Setup(
eduAPI eduServerAPI.EDUServerInputAPI, eduAPI eduServerAPI.EDUServerInputAPI,
rsAPI roomserverAPI.RoomserverInternalAPI, rsAPI roomserverAPI.RoomserverInternalAPI,
asAPI appserviceAPI.AppServiceQueryAPI, asAPI appserviceAPI.AppServiceQueryAPI,
accountDB userdb.Database,
userAPI userapi.UserInternalAPI, userAPI userapi.UserInternalAPI,
federation *gomatrixserverlib.FederationClient, federation *gomatrixserverlib.FederationClient,
syncProducer *producers.SyncAPIProducer, syncProducer *producers.SyncAPIProducer,
@ -62,7 +60,7 @@ func Setup(
mscCfg *config.MSCs, mscCfg *config.MSCs,
) { ) {
rateLimits := httputil.NewRateLimits(&cfg.RateLimiting) rateLimits := httputil.NewRateLimits(&cfg.RateLimiting)
userInteractiveAuth := auth.NewUserInteractive(accountDB, cfg) userInteractiveAuth := auth.NewUserInteractive(userAPI, cfg)
unstableFeatures := map[string]bool{ unstableFeatures := map[string]bool{
"org.matrix.e2e_cross_signing": true, "org.matrix.e2e_cross_signing": true,
@ -124,7 +122,7 @@ func Setup(
) )
if cfg.Matrix.ServerNotices.Enabled { if cfg.Matrix.ServerNotices.Enabled {
logrus.Info("Enabling server notices at /_synapse/admin/v1/send_server_notice") logrus.Info("Enabling server notices at /_synapse/admin/v1/send_server_notice")
serverNotificationSender, err = getSenderDevice(context.Background(), userAPI, accountDB, cfg) serverNotificationSender, err = getSenderDevice(context.Background(), userAPI, cfg)
if err != nil { if err != nil {
logrus.WithError(err).Fatal("unable to get account for sending sending server notices") logrus.WithError(err).Fatal("unable to get account for sending sending server notices")
} }
@ -142,7 +140,7 @@ func Setup(
txnID := vars["txnID"] txnID := vars["txnID"]
return SendServerNotice( return SendServerNotice(
req, &cfg.Matrix.ServerNotices, req, &cfg.Matrix.ServerNotices,
cfg, userAPI, rsAPI, accountDB, asAPI, cfg, userAPI, rsAPI, asAPI,
device, serverNotificationSender, device, serverNotificationSender,
&txnID, transactionsCache, &txnID, transactionsCache,
) )
@ -157,7 +155,7 @@ func Setup(
} }
return SendServerNotice( return SendServerNotice(
req, &cfg.Matrix.ServerNotices, req, &cfg.Matrix.ServerNotices,
cfg, userAPI, rsAPI, accountDB, asAPI, cfg, userAPI, rsAPI, asAPI,
device, serverNotificationSender, device, serverNotificationSender,
nil, transactionsCache, nil, transactionsCache,
) )
@ -192,9 +190,8 @@ func Setup(
consentRequiredCheck := httputil.WithConsentCheck(cfg.Matrix.UserConsentOptions, userAPI) consentRequiredCheck := httputil.WithConsentCheck(cfg.Matrix.UserConsentOptions, userAPI)
v3mux.Handle("/createRoom", v3mux.Handle("/createRoom",
httputil.MakeAuthAPI("createRoom", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { httputil.MakeAuthAPI("createRoom", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return CreateRoom(req, device, cfg, accountDB, rsAPI, asAPI) return CreateRoom(req, device, cfg, userAPI, rsAPI, asAPI)
}, consentRequiredCheck), }, consentRequiredCheck),
).Methods(http.MethodPost, http.MethodOptions) ).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/join/{roomIDOrAlias}", v3mux.Handle("/join/{roomIDOrAlias}",
@ -207,7 +204,7 @@ func Setup(
return util.ErrorResponse(err) return util.ErrorResponse(err)
} }
return JoinRoomByIDOrAlias( return JoinRoomByIDOrAlias(
req, device, rsAPI, accountDB, vars["roomIDOrAlias"], req, device, rsAPI, userAPI, vars["roomIDOrAlias"],
) )
}), }),
).Methods(http.MethodPost, http.MethodOptions) ).Methods(http.MethodPost, http.MethodOptions)
@ -223,7 +220,7 @@ func Setup(
return util.ErrorResponse(err) return util.ErrorResponse(err)
} }
return PeekRoomByIDOrAlias( return PeekRoomByIDOrAlias(
req, device, rsAPI, accountDB, vars["roomIDOrAlias"], req, device, rsAPI, vars["roomIDOrAlias"],
) )
}, consentRequiredCheck), }, consentRequiredCheck),
).Methods(http.MethodPost, http.MethodOptions) ).Methods(http.MethodPost, http.MethodOptions)
@ -243,7 +240,7 @@ func Setup(
return util.ErrorResponse(err) return util.ErrorResponse(err)
} }
return JoinRoomByIDOrAlias( return JoinRoomByIDOrAlias(
req, device, rsAPI, accountDB, vars["roomID"], req, device, rsAPI, userAPI, vars["roomID"],
) )
}), }),
).Methods(http.MethodPost, http.MethodOptions) ).Methods(http.MethodPost, http.MethodOptions)
@ -268,7 +265,7 @@ func Setup(
return util.ErrorResponse(err) return util.ErrorResponse(err)
} }
return UnpeekRoomByID( return UnpeekRoomByID(
req, device, rsAPI, accountDB, vars["roomID"], req, device, rsAPI, vars["roomID"],
) )
}, consentRequiredCheck), }, consentRequiredCheck),
).Methods(http.MethodPost, http.MethodOptions) ).Methods(http.MethodPost, http.MethodOptions)
@ -278,7 +275,7 @@ func Setup(
if err != nil { if err != nil {
return util.ErrorResponse(err) return util.ErrorResponse(err)
} }
return SendBan(req, accountDB, device, vars["roomID"], cfg, rsAPI, asAPI) return SendBan(req, userAPI, device, vars["roomID"], cfg, rsAPI, asAPI)
}, consentRequiredCheck), }, consentRequiredCheck),
).Methods(http.MethodPost, http.MethodOptions) ).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/invite", v3mux.Handle("/rooms/{roomID}/invite",
@ -290,7 +287,7 @@ func Setup(
if err != nil { if err != nil {
return util.ErrorResponse(err) return util.ErrorResponse(err)
} }
return SendInvite(req, accountDB, device, vars["roomID"], cfg, rsAPI, asAPI) return SendInvite(req, userAPI, device, vars["roomID"], cfg, rsAPI, asAPI)
}, consentRequiredCheck), }, consentRequiredCheck),
).Methods(http.MethodPost, http.MethodOptions) ).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/kick", v3mux.Handle("/rooms/{roomID}/kick",
@ -299,7 +296,7 @@ func Setup(
if err != nil { if err != nil {
return util.ErrorResponse(err) return util.ErrorResponse(err)
} }
return SendKick(req, accountDB, device, vars["roomID"], cfg, rsAPI, asAPI) return SendKick(req, userAPI, device, vars["roomID"], cfg, rsAPI, asAPI)
}, consentRequiredCheck), }, consentRequiredCheck),
).Methods(http.MethodPost, http.MethodOptions) ).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/unban", v3mux.Handle("/rooms/{roomID}/unban",
@ -308,7 +305,7 @@ func Setup(
if err != nil { if err != nil {
return util.ErrorResponse(err) return util.ErrorResponse(err)
} }
return SendUnban(req, accountDB, device, vars["roomID"], cfg, rsAPI, asAPI) return SendUnban(req, userAPI, device, vars["roomID"], cfg, rsAPI, asAPI)
}, consentRequiredCheck), }, consentRequiredCheck),
).Methods(http.MethodPost, http.MethodOptions) ).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/send/{eventType}", v3mux.Handle("/rooms/{roomID}/send/{eventType}",
@ -404,14 +401,14 @@ func Setup(
if r := rateLimits.Limit(req); r != nil { if r := rateLimits.Limit(req); r != nil {
return *r return *r
} }
return Register(req, userAPI, accountDB, cfg) return Register(req, userAPI, cfg)
})).Methods(http.MethodPost, http.MethodOptions) })).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/register/available", httputil.MakeExternalAPI("registerAvailable", func(req *http.Request) util.JSONResponse { v3mux.Handle("/register/available", httputil.MakeExternalAPI("registerAvailable", func(req *http.Request) util.JSONResponse {
if r := rateLimits.Limit(req); r != nil { if r := rateLimits.Limit(req); r != nil {
return *r return *r
} }
return RegisterAvailable(req, cfg, accountDB) return RegisterAvailable(req, cfg, userAPI)
})).Methods(http.MethodGet, http.MethodOptions) })).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/directory/room/{roomAlias}", v3mux.Handle("/directory/room/{roomAlias}",
@ -489,7 +486,7 @@ func Setup(
if err != nil { if err != nil {
return util.ErrorResponse(err) return util.ErrorResponse(err)
} }
return SendTyping(req, device, vars["roomID"], vars["userID"], accountDB, eduAPI, rsAPI) return SendTyping(req, device, vars["roomID"], vars["userID"], eduAPI, rsAPI)
}), }),
).Methods(http.MethodPut, http.MethodOptions) ).Methods(http.MethodPut, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/redact/{eventID}", v3mux.Handle("/rooms/{roomID}/redact/{eventID}",
@ -550,7 +547,7 @@ func Setup(
if r := rateLimits.Limit(req); r != nil { if r := rateLimits.Limit(req); r != nil {
return *r return *r
} }
return Password(req, userAPI, accountDB, device, cfg) return Password(req, userAPI, device, cfg)
}), }),
).Methods(http.MethodPost, http.MethodOptions) ).Methods(http.MethodPost, http.MethodOptions)
@ -570,7 +567,7 @@ func Setup(
if r := rateLimits.Limit(req); r != nil { if r := rateLimits.Limit(req); r != nil {
return *r return *r
} }
return Login(req, accountDB, userAPI, cfg) return Login(req, userAPI, cfg)
}), }),
).Methods(http.MethodGet, http.MethodPost, http.MethodOptions) ).Methods(http.MethodGet, http.MethodPost, http.MethodOptions)
@ -725,7 +722,7 @@ func Setup(
if err != nil { if err != nil {
return util.ErrorResponse(err) return util.ErrorResponse(err)
} }
return GetProfile(req, accountDB, cfg, vars["userID"], asAPI, federation) return GetProfile(req, userAPI, cfg, vars["userID"], asAPI, federation)
}), }),
).Methods(http.MethodGet, http.MethodOptions) ).Methods(http.MethodGet, http.MethodOptions)
@ -735,7 +732,7 @@ func Setup(
if err != nil { if err != nil {
return util.ErrorResponse(err) return util.ErrorResponse(err)
} }
return GetAvatarURL(req, accountDB, cfg, vars["userID"], asAPI, federation) return GetAvatarURL(req, userAPI, cfg, vars["userID"], asAPI, federation)
}), }),
).Methods(http.MethodGet, http.MethodOptions) ).Methods(http.MethodGet, http.MethodOptions)
@ -748,7 +745,7 @@ func Setup(
if err != nil { if err != nil {
return util.ErrorResponse(err) return util.ErrorResponse(err)
} }
return SetAvatarURL(req, accountDB, device, vars["userID"], cfg, rsAPI) return SetAvatarURL(req, userAPI, device, vars["userID"], cfg, rsAPI)
}, consentRequiredCheck), }, consentRequiredCheck),
).Methods(http.MethodPut, http.MethodOptions) ).Methods(http.MethodPut, http.MethodOptions)
// Browsers use the OPTIONS HTTP method to check if the CORS policy allows // Browsers use the OPTIONS HTTP method to check if the CORS policy allows
@ -760,7 +757,7 @@ func Setup(
if err != nil { if err != nil {
return util.ErrorResponse(err) return util.ErrorResponse(err)
} }
return GetDisplayName(req, accountDB, cfg, vars["userID"], asAPI, federation) return GetDisplayName(req, userAPI, cfg, vars["userID"], asAPI, federation)
}), }),
).Methods(http.MethodGet, http.MethodOptions) ).Methods(http.MethodGet, http.MethodOptions)
@ -773,7 +770,7 @@ func Setup(
if err != nil { if err != nil {
return util.ErrorResponse(err) return util.ErrorResponse(err)
} }
return SetDisplayName(req, accountDB, device, vars["userID"], cfg, rsAPI) return SetDisplayName(req, userAPI, device, vars["userID"], cfg, rsAPI)
}, consentRequiredCheck), }, consentRequiredCheck),
).Methods(http.MethodPut, http.MethodOptions) ).Methods(http.MethodPut, http.MethodOptions)
// Browsers use the OPTIONS HTTP method to check if the CORS policy allows // Browsers use the OPTIONS HTTP method to check if the CORS policy allows
@ -781,25 +778,25 @@ func Setup(
v3mux.Handle("/account/3pid", v3mux.Handle("/account/3pid",
httputil.MakeAuthAPI("account_3pid", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { httputil.MakeAuthAPI("account_3pid", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return GetAssociated3PIDs(req, accountDB, device) return GetAssociated3PIDs(req, userAPI, device)
}, consentRequiredCheck), }, consentRequiredCheck),
).Methods(http.MethodGet, http.MethodOptions) ).Methods(http.MethodGet, http.MethodOptions)
v3mux.Handle("/account/3pid", v3mux.Handle("/account/3pid",
httputil.MakeAuthAPI("account_3pid", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { httputil.MakeAuthAPI("account_3pid", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return CheckAndSave3PIDAssociation(req, accountDB, device, cfg) return CheckAndSave3PIDAssociation(req, userAPI, device, cfg)
}, consentRequiredCheck), }, consentRequiredCheck),
).Methods(http.MethodPost, http.MethodOptions) ).Methods(http.MethodPost, http.MethodOptions)
unstableMux.Handle("/account/3pid/delete", unstableMux.Handle("/account/3pid/delete",
httputil.MakeAuthAPI("account_3pid", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { httputil.MakeAuthAPI("account_3pid", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return Forget3PID(req, accountDB) return Forget3PID(req, userAPI)
}, consentRequiredCheck), }, consentRequiredCheck),
).Methods(http.MethodPost, http.MethodOptions) ).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/{path:(?:account/3pid|register)}/email/requestToken", v3mux.Handle("/{path:(?:account/3pid|register)}/email/requestToken",
httputil.MakeExternalAPI("account_3pid_request_token", func(req *http.Request) util.JSONResponse { httputil.MakeExternalAPI("account_3pid_request_token", func(req *http.Request) util.JSONResponse {
return RequestEmailToken(req, accountDB, cfg) return RequestEmailToken(req, userAPI, cfg)
}), }),
).Methods(http.MethodPost, http.MethodOptions) ).Methods(http.MethodPost, http.MethodOptions)
@ -1274,7 +1271,7 @@ func Setup(
// Cross-signing device keys // Cross-signing device keys
postDeviceSigningKeys := httputil.MakeAuthAPI("post_device_signing_keys", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { postDeviceSigningKeys := httputil.MakeAuthAPI("post_device_signing_keys", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return UploadCrossSigningDeviceKeys(req, userInteractiveAuth, keyAPI, device, accountDB, cfg) return UploadCrossSigningDeviceKeys(req, userInteractiveAuth, keyAPI, device, userAPI, cfg)
}, consentRequiredCheck) }, consentRequiredCheck)
postDeviceSigningSignatures := httputil.MakeAuthAPI("post_device_signing_signatures", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { postDeviceSigningSignatures := httputil.MakeAuthAPI("post_device_signing_signatures", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {

View file

@ -20,7 +20,6 @@ import (
"github.com/matrix-org/dendrite/eduserver/api" "github.com/matrix-org/dendrite/eduserver/api"
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
userapi "github.com/matrix-org/dendrite/userapi/api" userapi "github.com/matrix-org/dendrite/userapi/api"
userdb "github.com/matrix-org/dendrite/userapi/storage"
"github.com/matrix-org/util" "github.com/matrix-org/util"
) )
@ -33,7 +32,7 @@ type typingContentJSON struct {
// sends the typing events to client API typingProducer // sends the typing events to client API typingProducer
func SendTyping( func SendTyping(
req *http.Request, device *userapi.Device, roomID string, req *http.Request, device *userapi.Device, roomID string,
userID string, accountDB userdb.Database, userID string,
eduAPI api.EDUServerInputAPI, eduAPI api.EDUServerInputAPI,
rsAPI roomserverAPI.RoomserverInternalAPI, rsAPI roomserverAPI.RoomserverInternalAPI,
) util.JSONResponse { ) util.JSONResponse {

View file

@ -22,7 +22,6 @@ import (
"time" "time"
"github.com/matrix-org/dendrite/roomserver/version" "github.com/matrix-org/dendrite/roomserver/version"
userdb "github.com/matrix-org/dendrite/userapi/storage"
"github.com/matrix-org/gomatrix" "github.com/matrix-org/gomatrix"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/tokens" "github.com/matrix-org/gomatrixserverlib/tokens"
@ -59,7 +58,6 @@ func SendServerNotice(
cfgClient *config.ClientAPI, cfgClient *config.ClientAPI,
userAPI userapi.UserInternalAPI, userAPI userapi.UserInternalAPI,
rsAPI api.RoomserverInternalAPI, rsAPI api.RoomserverInternalAPI,
accountsDB userdb.Database,
asAPI appserviceAPI.AppServiceQueryAPI, asAPI appserviceAPI.AppServiceQueryAPI,
device *userapi.Device, device *userapi.Device,
senderDevice *userapi.Device, senderDevice *userapi.Device,
@ -86,7 +84,7 @@ func SendServerNotice(
if resErr != nil { if resErr != nil {
return *resErr return *resErr
} }
res, _ := sendServerNotice(ctx, r, rsAPI, cfgNotices, cfgClient, senderDevice, accountsDB, asAPI, userAPI, txnID, device, txnCache) res, _ := sendServerNotice(ctx, r, rsAPI, cfgNotices, cfgClient, senderDevice, asAPI, userAPI, txnID, device, txnCache)
return res return res
} }
@ -97,7 +95,6 @@ func sendServerNotice(
cfgNotices *config.ServerNotices, cfgNotices *config.ServerNotices,
cfgClient *config.ClientAPI, cfgClient *config.ClientAPI,
senderDevice *userapi.Device, senderDevice *userapi.Device,
accountsDB userdb.Database,
asAPI appserviceAPI.AppServiceQueryAPI, asAPI appserviceAPI.AppServiceQueryAPI,
userAPI userapi.UserInternalAPI, userAPI userapi.UserInternalAPI,
txnID *string, txnID *string,
@ -155,7 +152,7 @@ func sendServerNotice(
PowerLevelContentOverride: pl, PowerLevelContentOverride: pl,
} }
roomRes := createRoom(ctx, crReq, senderDevice, cfgClient, accountsDB, rsAPI, asAPI, time.Now()) roomRes := createRoom(ctx, crReq, senderDevice, cfgClient, userAPI, rsAPI, asAPI, time.Now())
switch data := roomRes.JSON.(type) { switch data := roomRes.JSON.(type) {
case createRoomResponse: case createRoomResponse:
@ -190,7 +187,7 @@ func sendServerNotice(
} }
// re-invite the user // re-invite the user
if res.Membership != gomatrixserverlib.Join { if res.Membership != gomatrixserverlib.Join {
res, err := sendInvite(ctx, accountsDB, senderDevice, roomID, serverNoticeRequest.UserID, "Server notice room", cfgClient, rsAPI, asAPI, time.Now()) res, err := sendInvite(ctx, userAPI, senderDevice, roomID, serverNoticeRequest.UserID, "Server notice room", cfgClient, rsAPI, asAPI, time.Now())
if err != nil { if err != nil {
return res, nil return res, nil
} }
@ -274,7 +271,6 @@ func (r sendServerNoticeRequest) valid() (ok bool) {
func getSenderDevice( func getSenderDevice(
ctx context.Context, ctx context.Context,
userAPI userapi.UserInternalAPI, userAPI userapi.UserInternalAPI,
accountDB userdb.Database,
cfg *config.ClientAPI, cfg *config.ClientAPI,
) (*userapi.Device, error) { ) (*userapi.Device, error) {
var accRes userapi.PerformAccountCreationResponse var accRes userapi.PerformAccountCreationResponse
@ -289,8 +285,12 @@ func getSenderDevice(
} }
// set the avatarurl for the user // set the avatarurl for the user
if err = accountDB.SetAvatarURL(ctx, cfg.Matrix.ServerNotices.LocalPart, cfg.Matrix.ServerNotices.AvatarURL); err != nil { res := &userapi.PerformSetAvatarURLResponse{}
util.GetLogger(ctx).WithError(err).Error("accountDB.SetAvatarURL failed") if err = userAPI.SetAvatarURL(ctx, &userapi.PerformSetAvatarURLRequest{
Localpart: cfg.Matrix.ServerNotices.LocalPart,
AvatarURL: cfg.Matrix.ServerNotices.AvatarURL,
}, res); err != nil {
util.GetLogger(ctx).WithError(err).Error("userAPI.SetAvatarURL failed")
return nil, err return nil, err
} }

View file

@ -40,7 +40,7 @@ type threePIDsResponse struct {
// RequestEmailToken implements: // RequestEmailToken implements:
// POST /account/3pid/email/requestToken // POST /account/3pid/email/requestToken
// POST /register/email/requestToken // POST /register/email/requestToken
func RequestEmailToken(req *http.Request, accountDB userdb.Database, cfg *config.ClientAPI) util.JSONResponse { func RequestEmailToken(req *http.Request, threePIDAPI api.UserThreePIDAPI, cfg *config.ClientAPI) util.JSONResponse {
var body threepid.EmailAssociationRequest var body threepid.EmailAssociationRequest
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil { if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
return *reqErr return *reqErr
@ -50,13 +50,18 @@ func RequestEmailToken(req *http.Request, accountDB userdb.Database, cfg *config
var err error var err error
// Check if the 3PID is already in use locally // Check if the 3PID is already in use locally
localpart, err := accountDB.GetLocalpartForThreePID(req.Context(), body.Email, "email") res := &api.QueryLocalpartForThreePIDResponse{}
err = threePIDAPI.QueryLocalpartForThreePID(req.Context(), &api.QueryLocalpartForThreePIDRequest{
ThreePID: body.Email,
Medium: "email",
}, res)
if err != nil { if err != nil {
util.GetLogger(req.Context()).WithError(err).Error("accountDB.GetLocalpartForThreePID failed") util.GetLogger(req.Context()).WithError(err).Error("threePIDAPI.QueryLocalpartForThreePID failed")
return jsonerror.InternalServerError() return jsonerror.InternalServerError()
} }
if len(localpart) > 0 { if len(res.Localpart) > 0 {
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
JSON: jsonerror.MatrixError{ JSON: jsonerror.MatrixError{
@ -85,7 +90,7 @@ func RequestEmailToken(req *http.Request, accountDB userdb.Database, cfg *config
// CheckAndSave3PIDAssociation implements POST /account/3pid // CheckAndSave3PIDAssociation implements POST /account/3pid
func CheckAndSave3PIDAssociation( func CheckAndSave3PIDAssociation(
req *http.Request, accountDB userdb.Database, device *api.Device, req *http.Request, threePIDAPI api.UserThreePIDAPI, device *api.Device,
cfg *config.ClientAPI, cfg *config.ClientAPI,
) util.JSONResponse { ) util.JSONResponse {
var body threepid.EmailAssociationCheckRequest var body threepid.EmailAssociationCheckRequest
@ -136,8 +141,12 @@ func CheckAndSave3PIDAssociation(
return jsonerror.InternalServerError() return jsonerror.InternalServerError()
} }
if err = accountDB.SaveThreePIDAssociation(req.Context(), address, localpart, medium); err != nil { if err = threePIDAPI.PerformSaveThreePIDAssociation(req.Context(), &api.PerformSaveThreePIDAssociationRequest{
util.GetLogger(req.Context()).WithError(err).Error("accountsDB.SaveThreePIDAssociation failed") ThreePID: address,
Localpart: localpart,
Medium: medium,
}, &struct{}{}); err != nil {
util.GetLogger(req.Context()).WithError(err).Error("threePIDAPI.PerformSaveThreePIDAssociation failed")
return jsonerror.InternalServerError() return jsonerror.InternalServerError()
} }
@ -149,7 +158,7 @@ func CheckAndSave3PIDAssociation(
// GetAssociated3PIDs implements GET /account/3pid // GetAssociated3PIDs implements GET /account/3pid
func GetAssociated3PIDs( func GetAssociated3PIDs(
req *http.Request, accountDB userdb.Database, device *api.Device, req *http.Request, threepidAPI api.UserThreePIDAPI, device *api.Device,
) util.JSONResponse { ) util.JSONResponse {
localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID) localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID)
if err != nil { if err != nil {
@ -157,27 +166,30 @@ func GetAssociated3PIDs(
return jsonerror.InternalServerError() return jsonerror.InternalServerError()
} }
threepids, err := accountDB.GetThreePIDsForLocalpart(req.Context(), localpart) res := &api.QueryThreePIDsForLocalpartResponse{}
err = threepidAPI.QueryThreePIDsForLocalpart(req.Context(), &api.QueryThreePIDsForLocalpartRequest{
Localpart: localpart,
}, res)
if err != nil { if err != nil {
util.GetLogger(req.Context()).WithError(err).Error("accountDB.GetThreePIDsForLocalpart failed") util.GetLogger(req.Context()).WithError(err).Error("threepidAPI.QueryThreePIDsForLocalpart failed")
return jsonerror.InternalServerError() return jsonerror.InternalServerError()
} }
return util.JSONResponse{ return util.JSONResponse{
Code: http.StatusOK, Code: http.StatusOK,
JSON: threePIDsResponse{threepids}, JSON: threePIDsResponse{res.ThreePIDs},
} }
} }
// Forget3PID implements POST /account/3pid/delete // Forget3PID implements POST /account/3pid/delete
func Forget3PID(req *http.Request, accountDB userdb.Database) util.JSONResponse { func Forget3PID(req *http.Request, threepidAPI api.UserThreePIDAPI) util.JSONResponse {
var body authtypes.ThreePID var body authtypes.ThreePID
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil { if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
return *reqErr return *reqErr
} }
if err := accountDB.RemoveThreePIDAssociation(req.Context(), body.Address, body.Medium); err != nil { if err := threepidAPI.PerformForgetThreePID(req.Context(), &api.PerformForgetThreePIDRequest{}, &struct{}{}); err != nil {
util.GetLogger(req.Context()).WithError(err).Error("accountDB.RemoveThreePIDAssociation failed") util.GetLogger(req.Context()).WithError(err).Error("threepidAPI.PerformForgetThreePID failed")
return jsonerror.InternalServerError() return jsonerror.InternalServerError()
} }

View file

@ -29,7 +29,6 @@ import (
"github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/setup/config"
userapi "github.com/matrix-org/dendrite/userapi/api" userapi "github.com/matrix-org/dendrite/userapi/api"
userdb "github.com/matrix-org/dendrite/userapi/storage"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
) )
@ -87,7 +86,7 @@ var (
func CheckAndProcessInvite( func CheckAndProcessInvite(
ctx context.Context, ctx context.Context,
device *userapi.Device, body *MembershipRequest, cfg *config.ClientAPI, device *userapi.Device, body *MembershipRequest, cfg *config.ClientAPI,
rsAPI api.RoomserverInternalAPI, db userdb.Database, rsAPI api.RoomserverInternalAPI, db userapi.UserProfileAPI,
roomID string, roomID string,
evTime time.Time, evTime time.Time,
) (inviteStoredOnIDServer bool, err error) { ) (inviteStoredOnIDServer bool, err error) {
@ -137,7 +136,7 @@ func CheckAndProcessInvite(
// Returns an error if a check or a request failed. // Returns an error if a check or a request failed.
func queryIDServer( func queryIDServer(
ctx context.Context, ctx context.Context,
db userdb.Database, cfg *config.ClientAPI, device *userapi.Device, db userapi.UserProfileAPI, cfg *config.ClientAPI, device *userapi.Device,
body *MembershipRequest, roomID string, body *MembershipRequest, roomID string,
) (lookupRes *idServerLookupResponse, storeInviteRes *idServerStoreInviteResponse, err error) { ) (lookupRes *idServerLookupResponse, storeInviteRes *idServerStoreInviteResponse, err error) {
if err = isTrusted(body.IDServer, cfg); err != nil { if err = isTrusted(body.IDServer, cfg); err != nil {
@ -206,7 +205,7 @@ func queryIDServerLookup(ctx context.Context, body *MembershipRequest) (*idServe
// Returns an error if the request failed to send or if the response couldn't be parsed. // Returns an error if the request failed to send or if the response couldn't be parsed.
func queryIDServerStoreInvite( func queryIDServerStoreInvite(
ctx context.Context, ctx context.Context,
db userdb.Database, cfg *config.ClientAPI, device *userapi.Device, db userapi.UserProfileAPI, cfg *config.ClientAPI, device *userapi.Device,
body *MembershipRequest, roomID string, body *MembershipRequest, roomID string,
) (*idServerStoreInviteResponse, error) { ) (*idServerStoreInviteResponse, error) {
// Retrieve the sender's profile to get their display name // Retrieve the sender's profile to get their display name
@ -217,10 +216,17 @@ func queryIDServerStoreInvite(
var profile *authtypes.Profile var profile *authtypes.Profile
if serverName == cfg.Matrix.ServerName { if serverName == cfg.Matrix.ServerName {
profile, err = db.GetProfileByLocalpart(ctx, localpart) res := &userapi.QueryProfileResponse{}
err = db.QueryProfile(ctx, &userapi.QueryProfileRequest{UserID: device.UserID}, res)
if err != nil { if err != nil {
return nil, err return nil, err
} }
profile = &authtypes.Profile{
Localpart: localpart,
DisplayName: res.DisplayName,
AvatarURL: res.AvatarURL,
}
} else { } else {
profile = &authtypes.Profile{} profile = &authtypes.Profile{}
} }

View file

@ -24,12 +24,11 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/matrix-org/dendrite/setup"
"github.com/matrix-org/dendrite/setup/base" "github.com/matrix-org/dendrite/setup/base"
"github.com/matrix-org/dendrite/userapi/api"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/term" "golang.org/x/term"
"github.com/matrix-org/dendrite/setup"
"github.com/matrix-org/dendrite/userapi/api"
) )
const usage = `Usage: %s const usage = `Usage: %s
@ -43,7 +42,7 @@ Example:
# use password from file # use password from file
%s --config dendrite.yaml -username alice -passwordfile my.pass %s --config dendrite.yaml -username alice -passwordfile my.pass
# ask user to provide password # ask user to provide password
%s --config dendrite.yaml -username alice -ask-pass %s --config dendrite.yaml -username alice
# read password from stdin # read password from stdin
%s --config dendrite.yaml -username alice -passwordstdin < my.pass %s --config dendrite.yaml -username alice -passwordstdin < my.pass
cat my.pass | %s --config dendrite.yaml -username alice -passwordstdin cat my.pass | %s --config dendrite.yaml -username alice -passwordstdin
@ -56,10 +55,10 @@ Arguments:
var ( var (
username = flag.String("username", "", "The username of the account to register (specify the localpart only, e.g. 'alice' for '@alice:domain.com')") username = flag.String("username", "", "The username of the account to register (specify the localpart only, e.g. 'alice' for '@alice:domain.com')")
password = flag.String("password", "", "The password to associate with the account (optional, account will be password-less if not specified)") password = flag.String("password", "", "The password to associate with the account")
pwdFile = flag.String("passwordfile", "", "The file to use for the password (e.g. for automated account creation)") pwdFile = flag.String("passwordfile", "", "The file to use for the password (e.g. for automated account creation)")
pwdStdin = flag.Bool("passwordstdin", false, "Reads the password from stdin") pwdStdin = flag.Bool("passwordstdin", false, "Reads the password from stdin")
askPass = flag.Bool("ask-pass", false, "Ask for the password to use") pwdLess = flag.Bool("passwordless", false, "Create a passwordless account, e.g. if only an accesstoken is required")
isAdmin = flag.Bool("admin", false, "Create an admin account") isAdmin = flag.Bool("admin", false, "Create an admin account")
resetPassword = flag.Bool("reset-password", false, "Resets the password for the given username") resetPassword = flag.Bool("reset-password", false, "Resets the password for the given username")
validUsernameRegex = regexp.MustCompile(`^[0-9a-z_\-=./]+$`) validUsernameRegex = regexp.MustCompile(`^[0-9a-z_\-=./]+$`)
@ -78,22 +77,44 @@ func main() {
os.Exit(1) os.Exit(1)
} }
if *pwdLess && *resetPassword {
logrus.Fatalf("Can not reset to an empty password, unable to login afterwards.")
}
if !validUsernameRegex.MatchString(*username) { if !validUsernameRegex.MatchString(*username) {
logrus.Warn("Username can only contain characters a-z, 0-9, or '_-./='") logrus.Warn("Username can only contain characters a-z, 0-9, or '_-./='")
os.Exit(1) os.Exit(1)
} }
pass := getPassword(password, pwdFile, pwdStdin, askPass, os.Stdin) if len(fmt.Sprintf("@%s:%s", *username, cfg.Global.ServerName)) > 255 {
logrus.Fatalf("Username can not be longer than 255 characters: %s", fmt.Sprintf("@%s:%s", *username, cfg.Global.ServerName))
}
b := base.NewBaseDendrite(cfg, "create-account") var pass string
var err error
if !*pwdLess {
pass, err = getPassword(*password, *pwdFile, *pwdStdin, os.Stdin)
if err != nil {
logrus.Fatalln(err)
}
}
b := base.NewBaseDendrite(cfg, "Monolith")
accountDB := b.CreateAccountsDB() accountDB := b.CreateAccountsDB()
accType := api.AccountTypeUser accType := api.AccountTypeUser
if *isAdmin { if *isAdmin {
accType = api.AccountTypeAdmin accType = api.AccountTypeAdmin
} }
var err error
available, err := accountDB.CheckAccountAvailability(context.Background(), *username)
if err != nil {
logrus.Fatalln("Unable check username existence.")
}
if *resetPassword { if *resetPassword {
if available {
logrus.Fatalln("Username could not be found.")
}
err = accountDB.SetPassword(context.Background(), *username, pass) err = accountDB.SetPassword(context.Background(), *username, pass)
if err != nil { if err != nil {
logrus.Fatalf("Failed to update password for user %s: %s", *username, err.Error()) logrus.Fatalf("Failed to update password for user %s: %s", *username, err.Error())
@ -104,6 +125,9 @@ func main() {
logrus.Infof("Updated password for user %s and invalidated all logins\n", *username) logrus.Infof("Updated password for user %s and invalidated all logins\n", *username)
return return
} }
if !available {
logrus.Fatalln("Username is already in use.")
}
policyVersion := "" policyVersion := ""
if cfg.Global.UserConsentOptions.Enabled { if cfg.Global.UserConsentOptions.Enabled {
@ -118,53 +142,44 @@ func main() {
logrus.Infoln("Created account", *username) logrus.Infoln("Created account", *username)
} }
func getPassword(password, pwdFile *string, pwdStdin, askPass *bool, r io.Reader) string { func getPassword(password, pwdFile string, pwdStdin bool, r io.Reader) (string, error) {
// no password option set, use empty password
if password == nil && pwdFile == nil && pwdStdin == nil && askPass == nil {
return ""
}
// password defined as parameter
if password != nil && *password != "" {
return *password
}
// read password from file // read password from file
if pwdFile != nil && *pwdFile != "" { if pwdFile != "" {
pw, err := ioutil.ReadFile(*pwdFile) pw, err := ioutil.ReadFile(pwdFile)
if err != nil { if err != nil {
logrus.Fatalln("Unable to read password from file:", err) return "", fmt.Errorf("Unable to read password from file: %v", err)
} }
return strings.TrimSpace(string(pw)) return strings.TrimSpace(string(pw)), nil
} }
// read password from stdin // read password from stdin
if pwdStdin != nil && *pwdStdin { if pwdStdin {
data, err := ioutil.ReadAll(r) data, err := ioutil.ReadAll(r)
if err != nil { if err != nil {
logrus.Fatalln("Unable to read password from stdin:", err) return "", fmt.Errorf("Unable to read password from stdin: %v", err)
} }
return strings.TrimSpace(string(data)) return strings.TrimSpace(string(data)), nil
} }
// ask the user to provide the password // If no parameter was set, ask the user to provide the password
if *askPass { if password == "" {
fmt.Print("Enter Password: ") fmt.Print("Enter Password: ")
bytePassword, err := term.ReadPassword(int(os.Stdin.Fd())) bytePassword, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil { if err != nil {
logrus.Fatalln("Unable to read password:", err) return "", fmt.Errorf("Unable to read password: %v", err)
} }
fmt.Println() fmt.Println()
fmt.Print("Confirm Password: ") fmt.Print("Confirm Password: ")
bytePassword2, err := term.ReadPassword(int(os.Stdin.Fd())) bytePassword2, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil { if err != nil {
logrus.Fatalln("Unable to read password:", err) return "", fmt.Errorf("Unable to read password: %v", err)
} }
fmt.Println() fmt.Println()
if strings.TrimSpace(string(bytePassword)) != strings.TrimSpace(string(bytePassword2)) { if strings.TrimSpace(string(bytePassword)) != strings.TrimSpace(string(bytePassword2)) {
logrus.Fatalln("Entered passwords don't match") return "", fmt.Errorf("Entered passwords don't match")
} }
return strings.TrimSpace(string(bytePassword)) return strings.TrimSpace(string(bytePassword)), nil
} }
return "" return password, nil
} }

View file

@ -8,45 +8,48 @@ import (
func Test_getPassword(t *testing.T) { func Test_getPassword(t *testing.T) {
type args struct { type args struct {
password *string password string
pwdFile *string pwdFile string
pwdStdin *bool pwdStdin bool
askPass *bool
reader io.Reader reader io.Reader
} }
pass := "mySecretPass" pass := "mySecretPass"
passwordFile := "testdata/my.pass" passwordFile := "testdata/my.pass"
passwordStdin := true
reader := &bytes.Buffer{} reader := &bytes.Buffer{}
_, err := reader.WriteString(pass) _, err := reader.WriteString(pass)
if err != nil { if err != nil {
t.Errorf("unable to write to buffer: %+v", err) t.Errorf("unable to write to buffer: %+v", err)
} }
tests := []struct { tests := []struct {
name string name string
args args args args
want string want string
wantErr bool
}{ }{
{
name: "no password defined",
args: args{},
want: "",
},
{ {
name: "password defined", name: "password defined",
args: args{password: &pass}, args: args{
password: pass,
},
want: pass, want: pass,
}, },
{ {
name: "pwdFile defined", name: "pwdFile defined",
args: args{pwdFile: &passwordFile}, args: args{
pwdFile: passwordFile,
},
want: pass, want: pass,
}, },
{
name: "pwdFile does not exist",
args: args{pwdFile: "iDontExist"},
wantErr: true,
},
{ {
name: "read pass from stdin defined", name: "read pass from stdin defined",
args: args{ args: args{
pwdStdin: &passwordStdin, pwdStdin: true,
reader: reader, reader: reader,
}, },
want: pass, want: pass,
@ -54,7 +57,11 @@ func Test_getPassword(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if got := getPassword(tt.args.password, tt.args.pwdFile, tt.args.pwdStdin, tt.args.askPass, tt.args.reader); got != tt.want { got, err := getPassword(tt.args.password, tt.args.pwdFile, tt.args.pwdStdin, tt.args.reader)
if !tt.wantErr && err != nil {
t.Errorf("expected no error, but got %v", err)
}
if got != tt.want {
t.Errorf("getPassword() = '%v', want '%v'", got, tt.want) t.Errorf("getPassword() = '%v', want '%v'", got, tt.want)
} }
}) })

View file

@ -22,7 +22,6 @@ import (
) )
func ClientAPI(base *basepkg.BaseDendrite, cfg *config.Dendrite) { func ClientAPI(base *basepkg.BaseDendrite, cfg *config.Dendrite) {
accountDB := base.CreateAccountsDB()
federation := base.CreateFederationClient() federation := base.CreateFederationClient()
asQuery := base.AppserviceHTTPClient() asQuery := base.AppserviceHTTPClient()
@ -33,9 +32,9 @@ func ClientAPI(base *basepkg.BaseDendrite, cfg *config.Dendrite) {
keyAPI := base.KeyServerHTTPClient() keyAPI := base.KeyServerHTTPClient()
clientapi.AddPublicRoutes( clientapi.AddPublicRoutes(
base.PublicClientAPIMux, base.SynapseAdminMux, &base.Cfg.ClientAPI, accountDB, federation, base.ProcessContext, base.PublicClientAPIMux, base.SynapseAdminMux, &base.Cfg.ClientAPI,
rsAPI, eduInputAPI, asQuery, transactions.New(), fsAPI, userAPI, keyAPI, nil, federation, rsAPI, eduInputAPI, asQuery, transactions.New(), fsAPI, userAPI,
&cfg.MSCs, keyAPI, nil, &cfg.MSCs,
) )
base.SetupAndServeHTTP( base.SetupAndServeHTTP(

View file

@ -12,6 +12,10 @@ No, although a good portion of the Matrix specification has been implemented. Mo
No, not at present. There will be in the future when Dendrite reaches version 1.0. No, not at present. There will be in the future when Dendrite reaches version 1.0.
### Can I use Dendrite with an existing Synapse database?
No, Dendrite has a very different database schema to Synapse and the two are not interchangeable.
### Should I run a monolith or a polylith deployment? ### Should I run a monolith or a polylith deployment?
Monolith deployments are always preferred where possible, and at this time, are far better tested than polylith deployments are. The only reason to consider a polylith deployment is if you wish to run different Dendrite components on separate physical machines. Monolith deployments are always preferred where possible, and at this time, are far better tested than polylith deployments are. The only reason to consider a polylith deployment is if you wish to run different Dendrite components on separate physical machines.
@ -33,7 +37,7 @@ It should do, although we are aware of some minor issues:
### Does Dendrite support push notifications? ### Does Dendrite support push notifications?
No, not yet. This is a planned feature. Yes, we have experimental support for push notifications. Configure them in the usual way in your Matrix client.
### Does Dendrite support application services/bridges? ### Does Dendrite support application services/bridges?

View file

@ -42,15 +42,15 @@ func NewInternalAPI(
) api.EDUServerInputAPI { ) api.EDUServerInputAPI {
cfg := &base.Cfg.EDUServer cfg := &base.Cfg.EDUServer
js, _ := jetstream.Prepare(&cfg.Matrix.JetStream) js, _ := jetstream.Prepare(base.ProcessContext, &cfg.Matrix.JetStream)
return &input.EDUServerInputAPI{ return &input.EDUServerInputAPI{
Cache: eduCache, Cache: eduCache,
UserAPI: userAPI, UserAPI: userAPI,
JetStream: js, JetStream: js,
OutputTypingEventTopic: cfg.Matrix.JetStream.TopicFor(jetstream.OutputTypingEvent), OutputTypingEventTopic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputTypingEvent),
OutputSendToDeviceEventTopic: cfg.Matrix.JetStream.TopicFor(jetstream.OutputSendToDeviceEvent), OutputSendToDeviceEventTopic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputSendToDeviceEvent),
OutputReceiptEventTopic: cfg.Matrix.JetStream.TopicFor(jetstream.OutputReceiptEvent), OutputReceiptEventTopic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputReceiptEvent),
ServerName: cfg.Matrix.ServerName, ServerName: cfg.Matrix.ServerName,
} }
} }

View file

@ -58,9 +58,9 @@ func NewOutputEDUConsumer(
db: store, db: store,
ServerName: cfg.Matrix.ServerName, ServerName: cfg.Matrix.ServerName,
durable: cfg.Matrix.JetStream.Durable("FederationAPIEDUServerConsumer"), durable: cfg.Matrix.JetStream.Durable("FederationAPIEDUServerConsumer"),
typingTopic: cfg.Matrix.JetStream.TopicFor(jetstream.OutputTypingEvent), typingTopic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputTypingEvent),
sendToDeviceTopic: cfg.Matrix.JetStream.TopicFor(jetstream.OutputSendToDeviceEvent), sendToDeviceTopic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputSendToDeviceEvent),
receiptTopic: cfg.Matrix.JetStream.TopicFor(jetstream.OutputReceiptEvent), receiptTopic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputReceiptEvent),
} }
} }

View file

@ -55,8 +55,8 @@ func NewKeyChangeConsumer(
return &KeyChangeConsumer{ return &KeyChangeConsumer{
ctx: process.Context(), ctx: process.Context(),
jetstream: js, jetstream: js,
durable: cfg.Matrix.JetStream.TopicFor("FederationAPIKeyChangeConsumer"), durable: cfg.Matrix.JetStream.Prefixed("FederationAPIKeyChangeConsumer"),
topic: cfg.Matrix.JetStream.TopicFor(jetstream.OutputKeyChangeEvent), topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputKeyChangeEvent),
queues: queues, queues: queues,
db: store, db: store,
serverName: cfg.Matrix.ServerName, serverName: cfg.Matrix.ServerName,

View file

@ -61,7 +61,7 @@ func NewOutputRoomEventConsumer(
queues: queues, queues: queues,
rsAPI: rsAPI, rsAPI: rsAPI,
durable: cfg.Matrix.JetStream.Durable("FederationAPIRoomServerConsumer"), durable: cfg.Matrix.JetStream.Durable("FederationAPIRoomServerConsumer"),
topic: cfg.Matrix.JetStream.TopicFor(jetstream.OutputRoomEvent), topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputRoomEvent),
} }
} }

View file

@ -92,7 +92,7 @@ func NewInternalAPI(
FailuresUntilBlacklist: cfg.FederationMaxRetries, FailuresUntilBlacklist: cfg.FederationMaxRetries,
} }
js, _ := jetstream.Prepare(&cfg.Matrix.JetStream) js, _ := jetstream.Prepare(base.ProcessContext, &cfg.Matrix.JetStream)
queues := queue.NewOutgoingQueues( queues := queue.NewOutgoingQueues(
federationDB, base.ProcessContext, federationDB, base.ProcessContext,

View file

@ -30,7 +30,6 @@ import (
// Database stores information needed by the federation sender // Database stores information needed by the federation sender
type Database struct { type Database struct {
shared.Database shared.Database
sqlutil.PartitionOffsetStatements
db *sql.DB db *sql.DB
writer sqlutil.Writer writer sqlutil.Writer
} }
@ -104,8 +103,5 @@ func NewDatabase(dbProperties *config.DatabaseOptions, cache caching.FederationC
NotaryServerKeysMetadata: notaryMetadata, NotaryServerKeysMetadata: notaryMetadata,
ServerSigningKeys: serverSigningKeys, ServerSigningKeys: serverSigningKeys,
} }
if err = d.PartitionOffsetStatements.Prepare(d.db, d.writer, "federationsender"); err != nil {
return nil, err
}
return &d, nil return &d, nil
} }

View file

@ -29,7 +29,6 @@ import (
// Database stores information needed by the federation sender // Database stores information needed by the federation sender
type Database struct { type Database struct {
shared.Database shared.Database
sqlutil.PartitionOffsetStatements
db *sql.DB db *sql.DB
writer sqlutil.Writer writer sqlutil.Writer
} }
@ -103,8 +102,5 @@ func NewDatabase(dbProperties *config.DatabaseOptions, cache caching.FederationC
NotaryServerKeysMetadata: notaryKeysMetadata, NotaryServerKeysMetadata: notaryKeysMetadata,
ServerSigningKeys: serverSigningKeys, ServerSigningKeys: serverSigningKeys,
} }
if err = d.PartitionOffsetStatements.Prepare(d.db, d.writer, "federationsender"); err != nil {
return nil, err
}
return &d, nil return &d, nil
} }

2
go.mod
View file

@ -39,7 +39,7 @@ require (
github.com/matrix-org/go-sqlite3-js v0.0.0-20210709140738-b0d1ba599a6d github.com/matrix-org/go-sqlite3-js v0.0.0-20210709140738-b0d1ba599a6d
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16
github.com/matrix-org/gomatrixserverlib v0.0.0-20220317164600-0980b7f341e0 github.com/matrix-org/gomatrixserverlib v0.0.0-20220317164600-0980b7f341e0
github.com/matrix-org/pinecone v0.0.0-20220308124038-cfde1f8054c5 github.com/matrix-org/pinecone v0.0.0-20220323142759-6fb077377278
github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4 github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4
github.com/mattn/go-sqlite3 v1.14.10 github.com/mattn/go-sqlite3 v1.14.10
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect

4
go.sum
View file

@ -943,8 +943,8 @@ github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 h1:ZtO5uywdd5d
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s= github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s=
github.com/matrix-org/gomatrixserverlib v0.0.0-20220317164600-0980b7f341e0 h1:IINbE/0jSYGb7M31StazufyIQdYWSivRlhuns3JYPOM= github.com/matrix-org/gomatrixserverlib v0.0.0-20220317164600-0980b7f341e0 h1:IINbE/0jSYGb7M31StazufyIQdYWSivRlhuns3JYPOM=
github.com/matrix-org/gomatrixserverlib v0.0.0-20220317164600-0980b7f341e0/go.mod h1:+WF5InseAMgi1fTnU46JH39IDpEvLep0fDzx9LDf2Bo= github.com/matrix-org/gomatrixserverlib v0.0.0-20220317164600-0980b7f341e0/go.mod h1:+WF5InseAMgi1fTnU46JH39IDpEvLep0fDzx9LDf2Bo=
github.com/matrix-org/pinecone v0.0.0-20220308124038-cfde1f8054c5 h1:7viLTiLAA2MtGKY+uf14j6TjfKvvGLAMj/qdm70jJuQ= github.com/matrix-org/pinecone v0.0.0-20220323142759-6fb077377278 h1:lRrvMMv7x1FIVW1mcBdU89lvbgAXKz6RyYR0VQTAr3E=
github.com/matrix-org/pinecone v0.0.0-20220308124038-cfde1f8054c5/go.mod h1:r6dsL+ylE0yXe/7zh8y/Bdh6aBYI1r+u4yZni9A4iyk= github.com/matrix-org/pinecone v0.0.0-20220323142759-6fb077377278/go.mod h1:r6dsL+ylE0yXe/7zh8y/Bdh6aBYI1r+u4yZni9A4iyk=
github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7/go.mod h1:vVQlW/emklohkZnOPwD3LrZUBqdfsbiyO3p1lNV8F6U= github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7/go.mod h1:vVQlW/emklohkZnOPwD3LrZUBqdfsbiyO3p1lNV8F6U=
github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4 h1:eCEHXWDv9Rm335MSuB49mFUK44bwZPFSDde3ORE3syk= github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4 h1:eCEHXWDv9Rm335MSuB49mFUK44bwZPFSDde3ORE3syk=
github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4/go.mod h1:vVQlW/emklohkZnOPwD3LrZUBqdfsbiyO3p1lNV8F6U= github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4/go.mod h1:vVQlW/emklohkZnOPwD3LrZUBqdfsbiyO3p1lNV8F6U=

View file

@ -21,4 +21,6 @@ const (
PublicMediaPathPrefix = "/_matrix/media/" PublicMediaPathPrefix = "/_matrix/media/"
PublicWellKnownPrefix = "/.well-known/matrix/" PublicWellKnownPrefix = "/.well-known/matrix/"
InternalPathPrefix = "/api/" InternalPathPrefix = "/api/"
DendriteAdminPathPrefix = "/_dendrite/"
SynapseAdminPathPrefix = "/_synapse/"
) )

View file

@ -1,133 +0,0 @@
// Copyright 2020 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sqlutil
import (
"context"
"database/sql"
"strings"
)
// A PartitionOffset is the offset into a partition of the input log.
type PartitionOffset struct {
// The ID of the partition.
Partition int32
// The offset into the partition.
Offset int64
}
const partitionOffsetsSchema = `
-- The offsets that the server has processed up to.
CREATE TABLE IF NOT EXISTS ${prefix}_partition_offsets (
-- The name of the topic.
topic TEXT NOT NULL,
-- The 32-bit partition ID
partition INTEGER NOT NULL,
-- The 64-bit offset.
partition_offset BIGINT NOT NULL,
UNIQUE (topic, partition)
);
`
const selectPartitionOffsetsSQL = "" +
"SELECT partition, partition_offset FROM ${prefix}_partition_offsets WHERE topic = $1"
const upsertPartitionOffsetsSQL = "" +
"INSERT INTO ${prefix}_partition_offsets (topic, partition, partition_offset) VALUES ($1, $2, $3)" +
" ON CONFLICT (topic, partition)" +
" DO UPDATE SET partition_offset = $3"
// PartitionOffsetStatements represents a set of statements that can be run on a partition_offsets table.
type PartitionOffsetStatements struct {
db *sql.DB
writer Writer
selectPartitionOffsetsStmt *sql.Stmt
upsertPartitionOffsetStmt *sql.Stmt
}
// Prepare converts the raw SQL statements into prepared statements.
// Takes a prefix to prepend to the table name used to store the partition offsets.
// This allows multiple components to share the same database schema.
func (s *PartitionOffsetStatements) Prepare(db *sql.DB, writer Writer, prefix string) (err error) {
s.db = db
s.writer = writer
_, err = db.Exec(strings.Replace(partitionOffsetsSchema, "${prefix}", prefix, -1))
if err != nil {
return
}
if s.selectPartitionOffsetsStmt, err = db.Prepare(
strings.Replace(selectPartitionOffsetsSQL, "${prefix}", prefix, -1),
); err != nil {
return
}
if s.upsertPartitionOffsetStmt, err = db.Prepare(
strings.Replace(upsertPartitionOffsetsSQL, "${prefix}", prefix, -1),
); err != nil {
return
}
return
}
// PartitionOffsets implements PartitionStorer
func (s *PartitionOffsetStatements) PartitionOffsets(
ctx context.Context, topic string,
) ([]PartitionOffset, error) {
return s.selectPartitionOffsets(ctx, topic)
}
// SetPartitionOffset implements PartitionStorer
func (s *PartitionOffsetStatements) SetPartitionOffset(
ctx context.Context, topic string, partition int32, offset int64,
) error {
return s.upsertPartitionOffset(ctx, topic, partition, offset)
}
// selectPartitionOffsets returns all the partition offsets for the given topic.
func (s *PartitionOffsetStatements) selectPartitionOffsets(
ctx context.Context, topic string,
) (results []PartitionOffset, err error) {
rows, err := s.selectPartitionOffsetsStmt.QueryContext(ctx, topic)
if err != nil {
return nil, err
}
defer checkNamedErr(rows.Close, &err)
for rows.Next() {
var offset PartitionOffset
if err = rows.Scan(&offset.Partition, &offset.Offset); err != nil {
return nil, err
}
results = append(results, offset)
}
err = rows.Err()
return results, err
}
// checkNamedErr calls fn and overwrite err if it was nil and fn returned non-nil
func checkNamedErr(fn func() error, err *error) {
if e := fn(); e != nil && *err == nil {
*err = e
}
}
// UpsertPartitionOffset updates or inserts the partition offset for the given topic.
func (s *PartitionOffsetStatements) upsertPartitionOffset(
ctx context.Context, topic string, partition int32, offset int64,
) error {
return s.writer.Do(s.db, nil, func(txn *sql.Tx) error {
stmt := TxStmt(txn, s.upsertPartitionOffsetStmt)
_, err := stmt.ExecContext(ctx, topic, partition, offset)
return err
})
}

View file

@ -16,8 +16,8 @@ var build string
const ( const (
VersionMajor = 0 VersionMajor = 0
VersionMinor = 6 VersionMinor = 7
VersionPatch = 5 VersionPatch = 0
VersionTag = "" // example: "rc1" VersionTag = "" // example: "rc1"
) )

View file

@ -223,6 +223,7 @@ func (a *KeyInternalAPI) QueryDeviceMessages(ctx context.Context, req *api.Query
res.StreamID = maxStreamID res.StreamID = maxStreamID
} }
// nolint:gocyclo
func (a *KeyInternalAPI) QueryKeys(ctx context.Context, req *api.QueryKeysRequest, res *api.QueryKeysResponse) { func (a *KeyInternalAPI) QueryKeys(ctx context.Context, req *api.QueryKeysRequest, res *api.QueryKeysResponse) {
res.DeviceKeys = make(map[string]map[string]json.RawMessage) res.DeviceKeys = make(map[string]map[string]json.RawMessage)
res.MasterKeys = make(map[string]gomatrixserverlib.CrossSigningKey) res.MasterKeys = make(map[string]gomatrixserverlib.CrossSigningKey)

View file

@ -39,14 +39,14 @@ func AddInternalRoutes(router *mux.Router, intAPI api.KeyInternalAPI) {
func NewInternalAPI( func NewInternalAPI(
base *base.BaseDendrite, cfg *config.KeyServer, fedClient fedsenderapi.FederationClient, base *base.BaseDendrite, cfg *config.KeyServer, fedClient fedsenderapi.FederationClient,
) api.KeyInternalAPI { ) api.KeyInternalAPI {
js, _ := jetstream.Prepare(&cfg.Matrix.JetStream) js, _ := jetstream.Prepare(base.ProcessContext, &cfg.Matrix.JetStream)
db, err := storage.NewDatabase(&cfg.Database) db, err := storage.NewDatabase(&cfg.Database)
if err != nil { if err != nil {
logrus.WithError(err).Panicf("failed to connect to key server database") logrus.WithError(err).Panicf("failed to connect to key server database")
} }
keyChangeProducer := &producers.KeyChange{ keyChangeProducer := &producers.KeyChange{
Topic: string(cfg.Matrix.JetStream.TopicFor(jetstream.OutputKeyChangeEvent)), Topic: string(cfg.Matrix.JetStream.Prefixed(jetstream.OutputKeyChangeEvent)),
JetStream: js, JetStream: js,
DB: db, DB: db,
} }

View file

@ -70,8 +70,5 @@ func NewDatabase(dbProperties *config.DatabaseOptions) (*shared.Database, error)
CrossSigningKeysTable: csk, CrossSigningKeysTable: csk,
CrossSigningSigsTable: css, CrossSigningSigsTable: css,
} }
if err = d.PartitionOffsetStatements.Prepare(db, d.Writer, "keyserver"); err != nil {
return nil, err
}
return d, nil return d, nil
} }

View file

@ -36,7 +36,6 @@ type Database struct {
StaleDeviceListsTable tables.StaleDeviceLists StaleDeviceListsTable tables.StaleDeviceLists
CrossSigningKeysTable tables.CrossSigningKeys CrossSigningKeysTable tables.CrossSigningKeys
CrossSigningSigsTable tables.CrossSigningSigs CrossSigningSigsTable tables.CrossSigningSigs
sqlutil.PartitionOffsetStatements
} }
func (d *Database) ExistingOneTimeKeys(ctx context.Context, userID, deviceID string, keyIDsWithAlgorithms []string) (map[string]json.RawMessage, error) { func (d *Database) ExistingOneTimeKeys(ctx context.Context, userID, deviceID string, keyIDsWithAlgorithms []string) (map[string]json.RawMessage, error) {

View file

@ -69,8 +69,5 @@ func NewDatabase(dbProperties *config.DatabaseOptions) (*shared.Database, error)
CrossSigningKeysTable: csk, CrossSigningKeysTable: csk,
CrossSigningSigsTable: css, CrossSigningSigsTable: css,
} }
if err = d.PartitionOffsetStatements.Prepare(db, d.Writer, "keyserver"); err != nil {
return nil, err
}
return d, nil return d, nil
} }

View file

@ -722,8 +722,8 @@ func (r *downloadRequest) fetchRemoteFile(
// create request for remote file // create request for remote file
resp, err := client.CreateMediaDownloadRequest(ctx, r.MediaMetadata.Origin, string(r.MediaMetadata.MediaID)) resp, err := client.CreateMediaDownloadRequest(ctx, r.MediaMetadata.Origin, string(r.MediaMetadata.MediaID))
if err != nil || resp.StatusCode != http.StatusOK { if err != nil || (resp != nil && resp.StatusCode != http.StatusOK) {
if resp.StatusCode == http.StatusNotFound { if resp != nil && resp.StatusCode == http.StatusNotFound {
return "", false, fmt.Errorf("File with media ID %q does not exist on %s", r.MediaMetadata.MediaID, r.MediaMetadata.Origin) return "", false, fmt.Errorf("File with media ID %q does not exist on %s", r.MediaMetadata.MediaID, r.MediaMetadata.Origin)
} }
return "", false, fmt.Errorf("file with media ID %q could not be downloaded from %s", r.MediaMetadata.MediaID, r.MediaMetadata.Origin) return "", false, fmt.Errorf("file with media ID %q could not be downloaded from %s", r.MediaMetadata.MediaID, r.MediaMetadata.Origin)

View file

@ -102,4 +102,3 @@ func (a AliasEvent) Valid() bool {
} }
return a.Alias == "" || validateAliasRegex.MatchString(a.Alias) return a.Alias == "" || validateAliasRegex.MatchString(a.Alias)
} }

View file

@ -22,29 +22,29 @@ func TestAliasEvent_Valid(t *testing.T) {
{ {
name: "empty alias, invalid alt aliases", name: "empty alias, invalid alt aliases",
fields: fields{ fields: fields{
Alias: "", Alias: "",
AltAliases: []string{ "%not:valid.local"}, AltAliases: []string{"%not:valid.local"},
}, },
}, },
{ {
name: "valid alias, invalid alt aliases", name: "valid alias, invalid alt aliases",
fields: fields{ fields: fields{
Alias: "#valid:test.local", Alias: "#valid:test.local",
AltAliases: []string{ "%not:valid.local"}, AltAliases: []string{"%not:valid.local"},
}, },
}, },
{ {
name: "empty alias, invalid alt aliases", name: "empty alias, invalid alt aliases",
fields: fields{ fields: fields{
Alias: "", Alias: "",
AltAliases: []string{ "%not:valid.local"}, AltAliases: []string{"%not:valid.local"},
}, },
}, },
{ {
name: "invalid alias", name: "invalid alias",
fields: fields{ fields: fields{
Alias: "%not:valid.local", Alias: "%not:valid.local",
AltAliases: []string{ }, AltAliases: []string{},
}, },
}, },
} }

View file

@ -173,12 +173,15 @@ func (r *RoomserverInternalAPI) RemoveRoomAlias(
} }
if creatorID != request.UserID { if creatorID != request.UserID {
plEvent, err := r.DB.GetStateEvent(ctx, roomID, gomatrixserverlib.MRoomPowerLevels, "") var plEvent *gomatrixserverlib.HeaderedEvent
var pls *gomatrixserverlib.PowerLevelContent
plEvent, err = r.DB.GetStateEvent(ctx, roomID, gomatrixserverlib.MRoomPowerLevels, "")
if err != nil { if err != nil {
return fmt.Errorf("r.DB.GetStateEvent: %w", err) return fmt.Errorf("r.DB.GetStateEvent: %w", err)
} }
pls, err := plEvent.PowerLevels() pls, err = plEvent.PowerLevels()
if err != nil { if err != nil {
return fmt.Errorf("plEvent.PowerLevels: %w", err) return fmt.Errorf("plEvent.PowerLevels: %w", err)
} }
@ -223,7 +226,7 @@ func (r *RoomserverInternalAPI) RemoveRoomAlias(
} }
stateRes := &api.QueryLatestEventsAndStateResponse{} stateRes := &api.QueryLatestEventsAndStateResponse{}
if err := helpers.QueryLatestEventsAndState(ctx, r.DB, &api.QueryLatestEventsAndStateRequest{RoomID: roomID, StateToFetch: eventsNeeded.Tuples()}, stateRes); err != nil { if err = helpers.QueryLatestEventsAndState(ctx, r.DB, &api.QueryLatestEventsAndStateRequest{RoomID: roomID, StateToFetch: eventsNeeded.Tuples()}, stateRes); err != nil {
return err return err
} }

View file

@ -90,6 +90,7 @@ func (r *RoomserverInternalAPI) SetFederationAPI(fsAPI fsAPI.FederationInternalA
r.KeyRing = keyRing r.KeyRing = keyRing
r.Inputer = &input.Inputer{ r.Inputer = &input.Inputer{
Cfg: r.Cfg,
ProcessContext: r.ProcessContext, ProcessContext: r.ProcessContext,
DB: r.DB, DB: r.DB,
InputRoomEventTopic: r.InputRoomEventTopic, InputRoomEventTopic: r.InputRoomEventTopic,

View file

@ -19,6 +19,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"sync" "sync"
"time" "time"
@ -29,6 +30,7 @@ import (
"github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/dendrite/roomserver/internal/query" "github.com/matrix-org/dendrite/roomserver/internal/query"
"github.com/matrix-org/dendrite/roomserver/storage" "github.com/matrix-org/dendrite/roomserver/storage"
"github.com/matrix-org/dendrite/setup/config"
"github.com/matrix-org/dendrite/setup/jetstream" "github.com/matrix-org/dendrite/setup/jetstream"
"github.com/matrix-org/dendrite/setup/process" "github.com/matrix-org/dendrite/setup/process"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
@ -45,7 +47,35 @@ var keyContentFields = map[string]string{
"m.room.member": "membership", "m.room.member": "membership",
} }
// Inputer is responsible for consuming from the roomserver input
// streams and processing the events. All input events are queued
// into a single NATS stream and the order is preserved strictly.
// The `room_id` message header will contain the room ID which will
// be used to assign the pending event to a per-room worker.
//
// The input API maintains an ephemeral headers-only consumer. It
// will speed through the stream working out which room IDs are
// pending and create durable consumers for them. The durable
// consumer will then be used for each room worker goroutine to
// fetch events one by one and process them. Each room having a
// durable consumer of its own means there is no head-of-line
// blocking between rooms. Filtering ensures that each durable
// consumer only receives events for the room it is interested in.
//
// The ephemeral consumer closely tracks the newest events. The
// per-room durable consumers will only progress through the stream
// as events are processed.
//
// A BC * -> positions of each consumer (* = ephemeral)
// ⌄ ⌄⌄ ⌄
// ABAABCAABCAA -> newest (letter = subject for each message)
//
// In this example, A is still processing an event but has two
// pending events to process afterwards. Both B and C are caught
// up, so they will do nothing until a new event comes in for B
// or C.
type Inputer struct { type Inputer struct {
Cfg *config.RoomServer
ProcessContext *process.ProcessContext ProcessContext *process.ProcessContext
DB storage.Database DB storage.Database
NATSClient *nats.Conn NATSClient *nats.Conn
@ -57,147 +87,275 @@ type Inputer struct {
ACLs *acls.ServerACLs ACLs *acls.ServerACLs
InputRoomEventTopic string InputRoomEventTopic string
OutputRoomEventTopic string OutputRoomEventTopic string
workers sync.Map // room ID -> *phony.Inbox workers sync.Map // room ID -> *worker
Queryer *query.Queryer Queryer *query.Queryer
} }
func (r *Inputer) workerForRoom(roomID string) *phony.Inbox { type worker struct {
inbox, _ := r.workers.LoadOrStore(roomID, &phony.Inbox{}) phony.Inbox
return inbox.(*phony.Inbox) sync.Mutex
r *Inputer
roomID string
subscription *nats.Subscription
} }
// eventsInProgress is an in-memory map to keep a track of which events we have func (r *Inputer) startWorkerForRoom(roomID string) {
// queued up for processing. If we get a redelivery from NATS and we still have v, loaded := r.workers.LoadOrStore(roomID, &worker{
// the queued up item then we won't do anything with the redelivered message. If r: r,
// we've restarted Dendrite and now this map is empty then it means that we will roomID: roomID,
// reload pending work from NATS. })
var eventsInProgress sync.Map w := v.(*worker)
w.Lock()
defer w.Unlock()
if !loaded || w.subscription == nil {
consumer := r.Cfg.Matrix.JetStream.Prefixed("RoomInput" + jetstream.Tokenise(w.roomID))
subject := r.Cfg.Matrix.JetStream.Prefixed(jetstream.InputRoomEventSubj(w.roomID))
// onMessage is called when a new event arrives in the roomserver input stream. // Create the consumer. We do this as a specific step rather than
// letting PullSubscribe create it for us because we need the consumer
// to outlive the subscription. If we do it this way, we can Bind in the
// next step, and when we Unsubscribe, the consumer continues to live. If
// we leave PullSubscribe to create the durable consumer, Unsubscribe will
// delete it because it thinks it "owns" it, which in turn breaks the
// interest-based retention storage policy.
// If the durable consumer already exists, this is effectively a no-op.
// Another interesting tid-bit here: the ACK policy is set to "all" so that
// if we acknowledge a message, we also acknowledge everything that comes
// before it. This is necessary because otherwise our consumer will never
// acknowledge things we filtered out for other subjects and therefore they
// will linger around forever.
if _, err := w.r.JetStream.AddConsumer(
r.Cfg.Matrix.JetStream.Prefixed(jetstream.InputRoomEvent),
&nats.ConsumerConfig{
Durable: consumer,
AckPolicy: nats.AckAllPolicy,
DeliverPolicy: nats.DeliverAllPolicy,
FilterSubject: subject,
AckWait: MaximumMissingProcessingTime + (time.Second * 10),
},
); err != nil {
logrus.WithError(err).Errorf("Failed to create consumer for room %q", w.roomID)
return
}
// Bind to our durable consumer. We want to receive all messages waiting
// for this subject and we want to manually acknowledge them, so that we
// can ensure they are only cleaned up when we are done processing them.
sub, err := w.r.JetStream.PullSubscribe(
subject, consumer,
nats.ManualAck(),
nats.DeliverAll(),
nats.AckWait(MaximumMissingProcessingTime+(time.Second*10)),
nats.Bind(r.InputRoomEventTopic, consumer),
)
if err != nil {
logrus.WithError(err).Errorf("Failed to subscribe to stream for room %q", w.roomID)
return
}
// Go and start pulling messages off the queue.
w.subscription = sub
w.Act(nil, w._next)
}
}
// Start creates an ephemeral non-durable consumer on the roomserver
// input topic. It is configured to deliver us headers only because we
// don't actually care about the contents of the message at this point,
// we only care about the `room_id` field. Once a message arrives, we
// will look to see if we have a worker for that room which has its
// own consumer. If we don't, we'll start one.
func (r *Inputer) Start() error { func (r *Inputer) Start() error {
_, err := r.JetStream.Subscribe( _, err := r.JetStream.Subscribe(
r.InputRoomEventTopic, "", // This is blank because we specified it in BindStream.
// We specifically don't use jetstream.WithJetStreamMessage here because we func(m *nats.Msg) {
// queue the task off to a room-specific queue and the ACK needs to be sent roomID := m.Header.Get(jetstream.RoomID)
// later, possibly with an error response to the inputter if synchronous. r.startWorkerForRoom(roomID)
func(msg *nats.Msg) { _ = m.Ack()
roomID := msg.Header.Get("room_id")
var inputRoomEvent api.InputRoomEvent
if err := json.Unmarshal(msg.Data, &inputRoomEvent); err != nil {
_ = msg.Term()
return
}
_ = msg.InProgress()
index := roomID + "\000" + inputRoomEvent.Event.EventID()
if _, ok := eventsInProgress.LoadOrStore(index, struct{}{}); ok {
// We're already waiting to deal with this event, so there's no
// point in queuing it up again. We've notified NATS that we're
// working on the message still, so that will have deferred the
// redelivery by a bit.
return
}
roomserverInputBackpressure.With(prometheus.Labels{"room_id": roomID}).Inc()
r.workerForRoom(roomID).Act(nil, func() {
_ = msg.InProgress() // resets the acknowledgement wait timer
defer eventsInProgress.Delete(index)
defer roomserverInputBackpressure.With(prometheus.Labels{"room_id": roomID}).Dec()
var errString string
if err := r.processRoomEvent(r.ProcessContext.Context(), &inputRoomEvent); err != nil {
if !errors.Is(err, context.DeadlineExceeded) && !errors.Is(err, context.Canceled) {
sentry.CaptureException(err)
}
logrus.WithError(err).WithFields(logrus.Fields{
"room_id": roomID,
"event_id": inputRoomEvent.Event.EventID(),
"type": inputRoomEvent.Event.Type(),
}).Warn("Roomserver failed to process async event")
_ = msg.Term()
errString = err.Error()
} else {
_ = msg.Ack()
}
if replyTo := msg.Header.Get("sync"); replyTo != "" {
if err := r.NATSClient.Publish(replyTo, []byte(errString)); err != nil {
logrus.WithError(err).WithFields(logrus.Fields{
"room_id": roomID,
"event_id": inputRoomEvent.Event.EventID(),
"type": inputRoomEvent.Event.Type(),
}).Warn("Roomserver failed to respond for sync event")
}
}
})
}, },
// NATS wants to acknowledge automatically by default when the message is nats.HeadersOnly(),
// read from the stream, but we want to override that behaviour by making
// sure that we only acknowledge when we're happy we've done everything we
// can. This ensures we retry things when it makes sense to do so.
nats.ManualAck(),
// Use a durable named consumer.
r.Durable,
// If we've missed things in the stream, e.g. we restarted, then replay
// all of the queued messages that were waiting for us.
nats.DeliverAll(), nats.DeliverAll(),
// Ensure that NATS doesn't try to resend us something that wasn't done nats.AckAll(),
// within the period of time that we might still be processing it. nats.BindStream(r.InputRoomEventTopic),
nats.AckWait(MaximumMissingProcessingTime+(time.Second*10)),
// It is recommended to disable this for pull consumers as per the docs:
// https://docs.nats.io/nats-concepts/jetstream/consumers#note-about-push-and-pull-consumers
nats.MaxAckPending(-1),
) )
return err return err
} }
// _next is called by the worker for the room. It must only be called
// by the actor embedded into the worker.
func (w *worker) _next() {
// Look up what the next event is that's waiting to be processed.
ctx, cancel := context.WithTimeout(w.r.ProcessContext.Context(), time.Minute)
defer cancel()
msgs, err := w.subscription.Fetch(1, nats.Context(ctx))
switch err {
case nil:
// Make sure that once we're done here, we queue up another call
// to _next in the inbox.
defer w.Act(nil, w._next)
// If no error was reported, but we didn't get exactly one message,
// then skip over this and try again on the next iteration.
if len(msgs) != 1 {
return
}
case context.DeadlineExceeded:
// The context exceeded, so we've been waiting for more than a
// minute for activity in this room. At this point we will shut
// down the subscriber to free up resources. It'll get started
// again if new activity happens.
if err = w.subscription.Unsubscribe(); err != nil {
logrus.WithError(err).Errorf("Failed to unsubscribe to stream for room %q", w.roomID)
}
w.Lock()
w.subscription = nil
w.Unlock()
return
default:
// Something went wrong while trying to fetch the next event
// from the queue. In which case, we'll shut down the subscriber
// and wait to be notified about new room activity again. Maybe
// the problem will be corrected by then.
logrus.WithError(err).Errorf("Failed to get next stream message for room %q", w.roomID)
if err = w.subscription.Unsubscribe(); err != nil {
logrus.WithError(err).Errorf("Failed to unsubscribe to stream for room %q", w.roomID)
}
w.Lock()
w.subscription = nil
w.Unlock()
return
}
// Try to unmarshal the input room event. If the JSON unmarshalling
// fails then we'll terminate the message — this notifies NATS that
// we are done with the message and never want to see it again.
msg := msgs[0]
var inputRoomEvent api.InputRoomEvent
if err = json.Unmarshal(msg.Data, &inputRoomEvent); err != nil {
_ = msg.Term()
return
}
roomserverInputBackpressure.With(prometheus.Labels{"room_id": w.roomID}).Inc()
defer roomserverInputBackpressure.With(prometheus.Labels{"room_id": w.roomID}).Dec()
// Process the room event. If something goes wrong then we'll tell
// NATS to terminate the message. We'll store the error result as
// a string, because we might want to return that to the caller if
// it was a synchronous request.
var errString string
if err = w.r.processRoomEvent(w.r.ProcessContext.Context(), &inputRoomEvent); err != nil {
if !errors.Is(err, context.DeadlineExceeded) && !errors.Is(err, context.Canceled) {
sentry.CaptureException(err)
}
logrus.WithError(err).WithFields(logrus.Fields{
"room_id": w.roomID,
"event_id": inputRoomEvent.Event.EventID(),
"type": inputRoomEvent.Event.Type(),
}).Warn("Roomserver failed to process async event")
_ = msg.Term()
errString = err.Error()
} else {
_ = msg.Ack()
}
// If it was a synchronous input request then the "sync" field
// will be present in the message. That means that someone is
// waiting for a response. The temporary inbox name is present in
// that field, so send back the error string (if any). If there
// was no error then we'll return a blank message, which means
// that everything was OK.
if replyTo := msg.Header.Get("sync"); replyTo != "" {
if err = w.r.NATSClient.Publish(replyTo, []byte(errString)); err != nil {
logrus.WithError(err).WithFields(logrus.Fields{
"room_id": w.roomID,
"event_id": inputRoomEvent.Event.EventID(),
"type": inputRoomEvent.Event.Type(),
}).Warn("Roomserver failed to respond for sync event")
}
}
}
// queueInputRoomEvents queues events into the roomserver input
// stream in NATS.
func (r *Inputer) queueInputRoomEvents(
ctx context.Context,
request *api.InputRoomEventsRequest,
) (replySub *nats.Subscription, err error) {
// If the request is synchronous then we need to create a
// temporary inbox to wait for responses on, and then create
// a subscription to it. If it's asynchronous then we won't
// bother, so these values will remain empty.
var replyTo string
if !request.Asynchronous {
replyTo = nats.NewInbox()
replySub, err = r.NATSClient.SubscribeSync(replyTo)
if err != nil {
return nil, fmt.Errorf("r.NATSClient.SubscribeSync: %w", err)
}
if replySub == nil {
// This shouldn't ever happen, but it doesn't hurt to check
// because we can potentially avoid a nil pointer panic later
// if it did for some reason.
return nil, fmt.Errorf("expected a subscription to the temporary inbox")
}
}
// For each event, marshal the input room event and then
// send it into the input queue.
for _, e := range request.InputRoomEvents {
roomID := e.Event.RoomID()
subj := r.Cfg.Matrix.JetStream.Prefixed(jetstream.InputRoomEventSubj(roomID))
msg := &nats.Msg{
Subject: subj,
Header: nats.Header{},
}
msg.Header.Set("room_id", roomID)
if replyTo != "" {
msg.Header.Set("sync", replyTo)
}
msg.Data, err = json.Marshal(e)
if err != nil {
return nil, fmt.Errorf("json.Marshal: %w", err)
}
if _, err = r.JetStream.PublishMsg(msg, nats.Context(ctx)); err != nil {
logrus.WithError(err).WithFields(logrus.Fields{
"room_id": roomID,
"event_id": e.Event.EventID(),
"subj": subj,
}).Error("Roomserver failed to queue async event")
return nil, fmt.Errorf("r.JetStream.PublishMsg: %w", err)
}
}
return
}
// InputRoomEvents implements api.RoomserverInternalAPI // InputRoomEvents implements api.RoomserverInternalAPI
func (r *Inputer) InputRoomEvents( func (r *Inputer) InputRoomEvents(
ctx context.Context, ctx context.Context,
request *api.InputRoomEventsRequest, request *api.InputRoomEventsRequest,
response *api.InputRoomEventsResponse, response *api.InputRoomEventsResponse,
) { ) {
var replyTo string // Queue up the event into the roomserver.
var replySub *nats.Subscription replySub, err := r.queueInputRoomEvents(ctx, request)
if !request.Asynchronous { if err != nil {
var err error response.ErrMsg = err.Error()
replyTo = nats.NewInbox()
replySub, err = r.NATSClient.SubscribeSync(replyTo)
if err != nil {
response.ErrMsg = err.Error()
return
}
}
var err error
for _, e := range request.InputRoomEvents {
msg := &nats.Msg{
Subject: r.InputRoomEventTopic,
Header: nats.Header{},
Reply: replyTo,
}
roomID := e.Event.RoomID()
msg.Header.Set("room_id", roomID)
if replyTo != "" {
msg.Header.Set("sync", replyTo)
}
msg.Data, err = json.Marshal(e)
if err != nil {
response.ErrMsg = err.Error()
return
}
if _, err = r.JetStream.PublishMsg(msg); err != nil {
logrus.WithError(err).WithFields(logrus.Fields{
"room_id": roomID,
"event_id": e.Event.EventID(),
}).Error("Roomserver failed to queue async event")
return
}
}
if request.Asynchronous || replySub == nil {
return return
} }
// If we aren't waiting for synchronous responses then we can
// give up here, there is nothing further to do.
if replySub == nil {
return
}
// Otherwise, we'll want to sit and wait for the responses
// from the roomserver. There will be one response for every
// input we submitted. The last error value we receive will
// be the one returned as the error string.
defer replySub.Drain() // nolint:errcheck defer replySub.Drain() // nolint:errcheck
for i := 0; i < len(request.InputRoomEvents); i++ { for i := 0; i < len(request.InputRoomEvents); i++ {
msg, err := replySub.NextMsgWithContext(ctx) msg, err := replySub.NextMsgWithContext(ctx)
@ -207,7 +365,6 @@ func (r *Inputer) InputRoomEvents(
} }
if len(msg.Data) > 0 { if len(msg.Data) > 0 {
response.ErrMsg = string(msg.Data) response.ErrMsg = string(msg.Data)
return
} }
} }
} }

View file

@ -212,12 +212,34 @@ func (r *Leaver) performFederatedRejectInvite(
ServerNames: []gomatrixserverlib.ServerName{domain}, ServerNames: []gomatrixserverlib.ServerName{domain},
} }
leaveRes := fsAPI.PerformLeaveResponse{} leaveRes := fsAPI.PerformLeaveResponse{}
if err := r.FSAPI.PerformLeave(ctx, &leaveReq, &leaveRes); err != nil { if err = r.FSAPI.PerformLeave(ctx, &leaveReq, &leaveRes); err != nil {
// failures in PerformLeave should NEVER stop us from telling other components like the // failures in PerformLeave should NEVER stop us from telling other components like the
// sync API that the invite was withdrawn. Otherwise we can end up with stuck invites. // sync API that the invite was withdrawn. Otherwise we can end up with stuck invites.
util.GetLogger(ctx).WithError(err).Errorf("failed to PerformLeave, still retiring invite event") util.GetLogger(ctx).WithError(err).Errorf("failed to PerformLeave, still retiring invite event")
} }
info, err := r.DB.RoomInfo(ctx, req.RoomID)
if err != nil {
util.GetLogger(ctx).WithError(err).Errorf("failed to get RoomInfo, still retiring invite event")
}
updater, err := r.DB.MembershipUpdater(ctx, req.RoomID, req.UserID, true, info.RoomVersion)
if err != nil {
util.GetLogger(ctx).WithError(err).Errorf("failed to get MembershipUpdater, still retiring invite event")
}
if updater != nil {
if _, err = updater.SetToLeave(req.UserID, eventID); err != nil {
util.GetLogger(ctx).WithError(err).Errorf("failed to set membership to leave, still retiring invite event")
if err = updater.Rollback(); err != nil {
util.GetLogger(ctx).WithError(err).Errorf("failed to rollback membership leave, still retiring invite event")
}
} else {
if err = updater.Commit(); err != nil {
util.GetLogger(ctx).WithError(err).Errorf("failed to commit membership update, still retiring invite event")
}
}
}
// Withdraw the invite, so that the sync API etc are // Withdraw the invite, so that the sync API etc are
// notified that we rejected it. // notified that we rejected it.
return []api.OutputEvent{ return []api.OutputEvent{

View file

@ -50,12 +50,12 @@ func NewInternalAPI(
logrus.WithError(err).Panicf("failed to connect to room server db") logrus.WithError(err).Panicf("failed to connect to room server db")
} }
js, nc := jetstream.Prepare(&cfg.Matrix.JetStream) js, nc := jetstream.Prepare(base.ProcessContext, &cfg.Matrix.JetStream)
return internal.NewRoomserverAPI( return internal.NewRoomserverAPI(
base.ProcessContext, cfg, roomserverDB, js, nc, base.ProcessContext, cfg, roomserverDB, js, nc,
cfg.Matrix.JetStream.TopicFor(jetstream.InputRoomEvent), cfg.Matrix.JetStream.Prefixed(jetstream.InputRoomEvent),
cfg.Matrix.JetStream.TopicFor(jetstream.OutputRoomEvent), cfg.Matrix.JetStream.Prefixed(jetstream.OutputRoomEvent),
base.Caches, perspectiveServerNames, base.Caches, perspectiveServerNames,
) )
} }

View file

@ -151,7 +151,7 @@ func (s *eventStateKeyStatements) BulkSelectEventStateKey(
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer selectPrep.Close() defer internal.CloseAndLogIfError(ctx, selectPrep, "selectPrep.close() failed")
stmt := sqlutil.TxStmt(txn, selectPrep) stmt := sqlutil.TxStmt(txn, selectPrep)
rows, err := stmt.QueryContext(ctx, iEventStateKeyNIDs...) rows, err := stmt.QueryContext(ctx, iEventStateKeyNIDs...)
if err != nil { if err != nil {

View file

@ -128,7 +128,7 @@ func (s *eventTypeStatements) BulkSelectEventTypeNID(
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer selectPrep.Close() defer internal.CloseAndLogIfError(ctx, selectPrep, "selectPrep.close() failed")
stmt := sqlutil.TxStmt(txn, selectPrep) stmt := sqlutil.TxStmt(txn, selectPrep)
/////////////// ///////////////

View file

@ -567,7 +567,7 @@ func (s *eventStatements) SelectMaxEventDepth(ctx context.Context, txn *sql.Tx,
if err != nil { if err != nil {
return 0, err return 0, err
} }
defer sqlPrep.Close() defer internal.CloseAndLogIfError(ctx, sqlPrep, "sqlPrep.close() failed")
err = sqlutil.TxStmt(txn, sqlPrep).QueryRowContext(ctx, iEventIDs...).Scan(&result) err = sqlutil.TxStmt(txn, sqlPrep).QueryRowContext(ctx, iEventIDs...).Scan(&result)
if err != nil { if err != nil {
return 0, fmt.Errorf("sqlutil.TxStmt.QueryRowContext: %w", err) return 0, fmt.Errorf("sqlutil.TxStmt.QueryRowContext: %w", err)
@ -583,7 +583,7 @@ func (s *eventStatements) SelectRoomNIDsForEventNIDs(
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer sqlPrep.Close() defer internal.CloseAndLogIfError(ctx, sqlPrep, "sqlPrep.close() failed")
sqlStmt := sqlutil.TxStmt(txn, sqlPrep) sqlStmt := sqlutil.TxStmt(txn, sqlPrep)
iEventNIDs := make([]interface{}, len(eventNIDs)) iEventNIDs := make([]interface{}, len(eventNIDs))
for i, v := range eventNIDs { for i, v := range eventNIDs {

View file

@ -75,6 +75,7 @@ type BaseDendrite struct {
PublicMediaAPIMux *mux.Router PublicMediaAPIMux *mux.Router
PublicWellKnownAPIMux *mux.Router PublicWellKnownAPIMux *mux.Router
InternalAPIMux *mux.Router InternalAPIMux *mux.Router
DendriteAdminMux *mux.Router
SynapseAdminMux *mux.Router SynapseAdminMux *mux.Router
UseHTTPAPIs bool UseHTTPAPIs bool
apiHttpClient *http.Client apiHttpClient *http.Client
@ -207,7 +208,8 @@ func NewBaseDendrite(cfg *config.Dendrite, componentName string, options ...Base
PublicMediaAPIMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.PublicMediaPathPrefix).Subrouter().UseEncodedPath(), PublicMediaAPIMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.PublicMediaPathPrefix).Subrouter().UseEncodedPath(),
PublicWellKnownAPIMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.PublicWellKnownPrefix).Subrouter().UseEncodedPath(), PublicWellKnownAPIMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.PublicWellKnownPrefix).Subrouter().UseEncodedPath(),
InternalAPIMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.InternalPathPrefix).Subrouter().UseEncodedPath(), InternalAPIMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.InternalPathPrefix).Subrouter().UseEncodedPath(),
SynapseAdminMux: mux.NewRouter().SkipClean(true).PathPrefix("/_synapse/").Subrouter().UseEncodedPath(), DendriteAdminMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.DendriteAdminPathPrefix).Subrouter().UseEncodedPath(),
SynapseAdminMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.SynapseAdminPathPrefix).Subrouter().UseEncodedPath(),
apiHttpClient: &apiClient, apiHttpClient: &apiClient,
} }
} }
@ -376,6 +378,17 @@ func (b *BaseDendrite) SetupAndServeHTTP(
internalRouter.Handle("/metrics", httputil.WrapHandlerInBasicAuth(promhttp.Handler(), b.Cfg.Global.Metrics.BasicAuth)) internalRouter.Handle("/metrics", httputil.WrapHandlerInBasicAuth(promhttp.Handler(), b.Cfg.Global.Metrics.BasicAuth))
} }
b.DendriteAdminMux.HandleFunc("/monitor/up", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
})
b.DendriteAdminMux.HandleFunc("/monitor/health", func(w http.ResponseWriter, r *http.Request) {
if b.ProcessContext.IsDegraded() {
w.WriteHeader(503)
return
}
w.WriteHeader(200)
})
var clientHandler http.Handler var clientHandler http.Handler
clientHandler = b.PublicClientAPIMux clientHandler = b.PublicClientAPIMux
if b.Cfg.Global.Sentry.Enabled { if b.Cfg.Global.Sentry.Enabled {
@ -392,12 +405,13 @@ func (b *BaseDendrite) SetupAndServeHTTP(
}) })
federationHandler = sentryHandler.Handle(b.PublicFederationAPIMux) federationHandler = sentryHandler.Handle(b.PublicFederationAPIMux)
} }
internalRouter.PathPrefix(httputil.DendriteAdminPathPrefix).Handler(b.DendriteAdminMux)
externalRouter.PathPrefix(httputil.PublicClientPathPrefix).Handler(clientHandler) externalRouter.PathPrefix(httputil.PublicClientPathPrefix).Handler(clientHandler)
if !b.Cfg.Global.DisableFederation { if !b.Cfg.Global.DisableFederation {
externalRouter.PathPrefix(httputil.PublicKeyPathPrefix).Handler(b.PublicKeyAPIMux) externalRouter.PathPrefix(httputil.PublicKeyPathPrefix).Handler(b.PublicKeyAPIMux)
externalRouter.PathPrefix(httputil.PublicFederationPathPrefix).Handler(federationHandler) externalRouter.PathPrefix(httputil.PublicFederationPathPrefix).Handler(federationHandler)
} }
externalRouter.PathPrefix("/_synapse/").Handler(b.SynapseAdminMux) externalRouter.PathPrefix(httputil.SynapseAdminPathPrefix).Handler(b.SynapseAdminMux)
externalRouter.PathPrefix(httputil.PublicMediaPathPrefix).Handler(b.PublicMediaAPIMux) externalRouter.PathPrefix(httputil.PublicMediaPathPrefix).Handler(b.PublicMediaAPIMux)
externalRouter.PathPrefix(httputil.PublicWellKnownPrefix).Handler(b.PublicWellKnownAPIMux) externalRouter.PathPrefix(httputil.PublicWellKnownPrefix).Handler(b.PublicWellKnownAPIMux)

View file

@ -209,13 +209,14 @@ func setupRegexps(asAPI *AppServiceAPI, derived *Derived) (err error) {
for _, appservice := range derived.ApplicationServices { for _, appservice := range derived.ApplicationServices {
// The sender_localpart can be considered an exclusive regex for a single user, so let's do that // The sender_localpart can be considered an exclusive regex for a single user, so let's do that
// to simplify the code // to simplify the code
var senderUserIDSlice = []string{fmt.Sprintf("@%s:%s", appservice.SenderLocalpart, asAPI.Matrix.ServerName)} users, found := appservice.NamespaceMap["users"]
usersSlice, found := appservice.NamespaceMap["users"]
if !found { if !found {
usersSlice = []ApplicationServiceNamespace{} users = []ApplicationServiceNamespace{}
appservice.NamespaceMap["users"] = usersSlice
} }
appendExclusiveNamespaceRegexs(&senderUserIDSlice, usersSlice) appservice.NamespaceMap["users"] = append(users, ApplicationServiceNamespace{
Exclusive: true,
Regex: regexp.QuoteMeta(fmt.Sprintf("@%s:%s", appservice.SenderLocalpart, asAPI.Matrix.ServerName)),
})
for key, namespaceSlice := range appservice.NamespaceMap { for key, namespaceSlice := range appservice.NamespaceMap {
switch key { switch key {

View file

@ -19,12 +19,12 @@ type JetStream struct {
InMemory bool `yaml:"in_memory"` InMemory bool `yaml:"in_memory"`
} }
func (c *JetStream) TopicFor(name string) string { func (c *JetStream) Prefixed(name string) string {
return fmt.Sprintf("%s%s", c.TopicPrefix, name) return fmt.Sprintf("%s%s", c.TopicPrefix, name)
} }
func (c *JetStream) Durable(name string) string { func (c *JetStream) Durable(name string) string {
return c.TopicFor(name) return c.Prefixed(name)
} }
func (c *JetStream) Defaults(generate bool) { func (c *JetStream) Defaults(generate bool) {

View file

@ -1,11 +1,15 @@
package jetstream package jetstream
import ( import (
"fmt"
"reflect"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/getsentry/sentry-go"
"github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/setup/config"
"github.com/matrix-org/dendrite/setup/process"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
natsserver "github.com/nats-io/nats-server/v2/server" natsserver "github.com/nats-io/nats-server/v2/server"
@ -15,10 +19,10 @@ import (
var natsServer *natsserver.Server var natsServer *natsserver.Server
var natsServerMutex sync.Mutex var natsServerMutex sync.Mutex
func Prepare(cfg *config.JetStream) (natsclient.JetStreamContext, *natsclient.Conn) { func Prepare(process *process.ProcessContext, cfg *config.JetStream) (natsclient.JetStreamContext, *natsclient.Conn) {
// check if we need an in-process NATS Server // check if we need an in-process NATS Server
if len(cfg.Addresses) != 0 { if len(cfg.Addresses) != 0 {
return setupNATS(cfg, nil) return setupNATS(process, cfg, nil)
} }
natsServerMutex.Lock() natsServerMutex.Lock()
if natsServer == nil { if natsServer == nil {
@ -35,7 +39,16 @@ func Prepare(cfg *config.JetStream) (natsclient.JetStreamContext, *natsclient.Co
panic(err) panic(err)
} }
natsServer.ConfigureLogger() natsServer.ConfigureLogger()
go natsServer.Start() go func() {
process.ComponentStarted()
natsServer.Start()
}()
go func() {
<-process.WaitForShutdown()
natsServer.Shutdown()
natsServer.WaitForShutdown()
process.ComponentFinished()
}()
} }
natsServerMutex.Unlock() natsServerMutex.Unlock()
if !natsServer.ReadyForConnections(time.Second * 10) { if !natsServer.ReadyForConnections(time.Second * 10) {
@ -45,10 +58,10 @@ func Prepare(cfg *config.JetStream) (natsclient.JetStreamContext, *natsclient.Co
if err != nil { if err != nil {
logrus.Fatalln("Failed to create NATS client") logrus.Fatalln("Failed to create NATS client")
} }
return setupNATS(cfg, nc) return setupNATS(process, cfg, nc)
} }
func setupNATS(cfg *config.JetStream, nc *natsclient.Conn) (natsclient.JetStreamContext, *natsclient.Conn) { func setupNATS(process *process.ProcessContext, cfg *config.JetStream, nc *natsclient.Conn) (natsclient.JetStreamContext, *natsclient.Conn) {
if nc == nil { if nc == nil {
var err error var err error
nc, err = natsclient.Connect(strings.Join(cfg.Addresses, ",")) nc, err = natsclient.Connect(strings.Join(cfg.Addresses, ","))
@ -65,14 +78,35 @@ func setupNATS(cfg *config.JetStream, nc *natsclient.Conn) (natsclient.JetStream
} }
for _, stream := range streams { // streams are defined in streams.go for _, stream := range streams { // streams are defined in streams.go
name := cfg.TopicFor(stream.Name) name := cfg.Prefixed(stream.Name)
info, err := s.StreamInfo(name) info, err := s.StreamInfo(name)
if err != nil && err != natsclient.ErrStreamNotFound { if err != nil && err != natsclient.ErrStreamNotFound {
logrus.WithError(err).Fatal("Unable to get stream info") logrus.WithError(err).Fatal("Unable to get stream info")
} }
subjects := stream.Subjects
if len(subjects) == 0 {
// By default we want each stream to listen for the subjects
// that are either an exact match for the stream name, or where
// the first part of the subject is the stream name. ">" is a
// wildcard in NATS for one or more subject tokens. In the case
// that the stream is called "Foo", this will match any message
// with the subject "Foo", "Foo.Bar" or "Foo.Bar.Baz" etc.
subjects = []string{name, name + ".>"}
}
if info != nil {
switch {
case !reflect.DeepEqual(info.Config.Subjects, subjects):
fallthrough
case info.Config.Retention != stream.Retention:
fallthrough
case info.Config.Storage != stream.Storage:
if err = s.DeleteStream(name); err != nil {
logrus.WithError(err).Fatal("Unable to delete stream")
}
info = nil
}
}
if info == nil { if info == nil {
stream.Subjects = []string{name}
// If we're trying to keep everything in memory (e.g. unit tests) // If we're trying to keep everything in memory (e.g. unit tests)
// then overwrite the storage policy. // then overwrite the storage policy.
if cfg.InMemory { if cfg.InMemory {
@ -83,8 +117,42 @@ func setupNATS(cfg *config.JetStream, nc *natsclient.Conn) (natsclient.JetStream
// array, otherwise we end up with namespaces on namespaces. // array, otherwise we end up with namespaces on namespaces.
namespaced := *stream namespaced := *stream
namespaced.Name = name namespaced.Name = name
namespaced.Subjects = subjects
if _, err = s.AddStream(&namespaced); err != nil { if _, err = s.AddStream(&namespaced); err != nil {
logrus.WithError(err).WithField("stream", name).Fatal("Unable to add stream") logger := logrus.WithError(err).WithFields(logrus.Fields{
"stream": namespaced.Name,
"subjects": namespaced.Subjects,
})
// If the stream was supposed to be in-memory to begin with
// then an error here is fatal so we'll give up.
if namespaced.Storage == natsclient.MemoryStorage {
logger.WithError(err).Fatal("Unable to add in-memory stream")
}
// The stream was supposed to be on disk. Let's try starting
// Dendrite with the stream in-memory instead. That'll mean that
// we can't recover anything that was queued on the disk but we
// will still be able to start and run hopefully in the meantime.
logger.WithError(err).Error("Unable to add stream")
sentry.CaptureException(fmt.Errorf("Unable to add stream %q: %w", namespaced.Name, err))
namespaced.Storage = natsclient.MemoryStorage
if _, err = s.AddStream(&namespaced); err != nil {
// We tried to add the stream in-memory instead but something
// went wrong. That's an unrecoverable situation so we will
// give up at this point.
logger.WithError(err).Fatal("Unable to add in-memory stream")
}
if stream.Storage != namespaced.Storage {
// We've managed to add the stream in memory. What's on the
// disk will be left alone, but our ability to recover from a
// future crash will be limited. Yell about it.
sentry.CaptureException(fmt.Errorf("Stream %q is running in-memory; this may be due to data corruption in the JetStream storage directory, investigate as soon as possible", namespaced.Name))
logrus.Warn("Stream is running in-memory; this may be due to data corruption in the JetStream storage directory, investigate as soon as possible")
process.Degraded()
}
} }
} }
} }

View file

@ -1,6 +1,8 @@
package jetstream package jetstream
import ( import (
"fmt"
"regexp"
"time" "time"
"github.com/nats-io/nats.go" "github.com/nats-io/nats.go"
@ -24,10 +26,20 @@ var (
OutputReadUpdate = "OutputReadUpdate" OutputReadUpdate = "OutputReadUpdate"
) )
var safeCharacters = regexp.MustCompile("[^A-Za-z0-9$]+")
func Tokenise(str string) string {
return safeCharacters.ReplaceAllString(str, "_")
}
func InputRoomEventSubj(roomID string) string {
return fmt.Sprintf("%s.%s", InputRoomEvent, Tokenise(roomID))
}
var streams = []*nats.StreamConfig{ var streams = []*nats.StreamConfig{
{ {
Name: InputRoomEvent, Name: InputRoomEvent,
Retention: nats.WorkQueuePolicy, Retention: nats.InterestPolicy,
Storage: nats.FileStorage, Storage: nats.FileStorage,
}, },
{ {

View file

@ -57,7 +57,7 @@ type Monolith struct {
// AddAllPublicRoutes attaches all public paths to the given router // AddAllPublicRoutes attaches all public paths to the given router
func (m *Monolith) AddAllPublicRoutes(process *process.ProcessContext, csMux, ssMux, keyMux, wkMux, mediaMux, synapseMux *mux.Router) { func (m *Monolith) AddAllPublicRoutes(process *process.ProcessContext, csMux, ssMux, keyMux, wkMux, mediaMux, synapseMux *mux.Router) {
clientapi.AddPublicRoutes( clientapi.AddPublicRoutes(
csMux, synapseMux, &m.Config.ClientAPI, m.AccountDB, process, csMux, synapseMux, &m.Config.ClientAPI,
m.FedClient, m.RoomserverAPI, m.FedClient, m.RoomserverAPI,
m.EDUInternalAPI, m.AppserviceAPI, transactions.New(), m.EDUInternalAPI, m.AppserviceAPI, transactions.New(),
m.FederationAPI, m.UserAPI, m.KeyAPI, m.FederationAPI, m.UserAPI, m.KeyAPI,

View file

@ -283,11 +283,7 @@ func (w *walker) walk() util.JSONResponse {
if !roomExists { if !roomExists {
// attempt to query this room over federation, as either we've never heard of it before // attempt to query this room over federation, as either we've never heard of it before
// or we've left it and hence are not authorised (but info may be exposed regardless) // or we've left it and hence are not authorised (but info may be exposed regardless)
fedRes, err := w.federatedRoomInfo(rv.roomID, rv.vias) fedRes := w.federatedRoomInfo(rv.roomID, rv.vias)
if err != nil {
util.GetLogger(w.ctx).WithError(err).WithField("room_id", rv.roomID).Errorf("failed to query federated spaces")
continue
}
if fedRes != nil { if fedRes != nil {
discoveredChildEvents = fedRes.Room.ChildrenState discoveredChildEvents = fedRes.Room.ChildrenState
discoveredRooms = append(discoveredRooms, fedRes.Room) discoveredRooms = append(discoveredRooms, fedRes.Room)
@ -420,15 +416,15 @@ func (w *walker) publicRoomsChunk(roomID string) *gomatrixserverlib.PublicRoom {
// federatedRoomInfo returns more of the spaces graph from another server. Returns nil if this was // federatedRoomInfo returns more of the spaces graph from another server. Returns nil if this was
// unsuccessful. // unsuccessful.
func (w *walker) federatedRoomInfo(roomID string, vias []string) (*gomatrixserverlib.MSC2946SpacesResponse, error) { func (w *walker) federatedRoomInfo(roomID string, vias []string) *gomatrixserverlib.MSC2946SpacesResponse {
// only do federated requests for client requests // only do federated requests for client requests
if w.caller == nil { if w.caller == nil {
return nil, nil return nil
} }
resp, ok := w.cache.GetSpaceSummary(roomID) resp, ok := w.cache.GetSpaceSummary(roomID)
if ok { if ok {
util.GetLogger(w.ctx).Debugf("Returning cached response for %s", roomID) util.GetLogger(w.ctx).Debugf("Returning cached response for %s", roomID)
return &resp, nil return &resp
} }
util.GetLogger(w.ctx).Debugf("Querying %s via %+v", roomID, vias) util.GetLogger(w.ctx).Debugf("Querying %s via %+v", roomID, vias)
ctx := context.Background() ctx := context.Background()
@ -455,9 +451,9 @@ func (w *walker) federatedRoomInfo(roomID string, vias []string) (*gomatrixserve
} }
w.cache.StoreSpaceSummary(roomID, res) w.cache.StoreSpaceSummary(roomID, res)
return &res, nil return &res
} }
return nil, nil return nil
} }
func (w *walker) roomExists(roomID string) bool { func (w *walker) roomExists(roomID string) bool {
@ -717,23 +713,6 @@ func stripped(ev *gomatrixserverlib.Event) *gomatrixserverlib.MSC2946StrippedEve
} }
} }
func eventKey(event *gomatrixserverlib.MSC2946StrippedEvent) string {
return event.RoomID + "|" + event.Type + "|" + event.StateKey
}
func spaceTargetStripped(event *gomatrixserverlib.MSC2946StrippedEvent) string {
if event.StateKey == "" {
return "" // no-op
}
switch event.Type {
case ConstSpaceParentEventType:
return event.StateKey
case ConstSpaceChildEventType:
return event.StateKey
}
return ""
}
func parseInt(intstr string, defaultVal int) int { func parseInt(intstr string, defaultVal int) int {
i, err := strconv.ParseInt(intstr, 10, 32) i, err := strconv.ParseInt(intstr, 10, 32)
if err != nil { if err != nil {

View file

@ -2,13 +2,19 @@ package process
import ( import (
"context" "context"
"fmt"
"sync" "sync"
"github.com/getsentry/sentry-go"
"github.com/sirupsen/logrus"
"go.uber.org/atomic"
) )
type ProcessContext struct { type ProcessContext struct {
wg *sync.WaitGroup // used to wait for components to shutdown wg *sync.WaitGroup // used to wait for components to shutdown
ctx context.Context // cancelled when Stop is called ctx context.Context // cancelled when Stop is called
shutdown context.CancelFunc // shut down Dendrite shutdown context.CancelFunc // shut down Dendrite
degraded atomic.Bool
} }
func NewProcessContext() *ProcessContext { func NewProcessContext() *ProcessContext {
@ -43,3 +49,14 @@ func (b *ProcessContext) WaitForShutdown() <-chan struct{} {
func (b *ProcessContext) WaitForComponentsToFinish() { func (b *ProcessContext) WaitForComponentsToFinish() {
b.wg.Wait() b.wg.Wait()
} }
func (b *ProcessContext) Degraded() {
if b.degraded.CAS(false, true) {
logrus.Warn("Dendrite is running in a degraded state")
sentry.CaptureException(fmt.Errorf("Process is running in a degraded state"))
}
}
func (b *ProcessContext) IsDegraded() bool {
return b.degraded.Load()
}

View file

@ -61,7 +61,7 @@ func NewOutputClientDataConsumer(
return &OutputClientDataConsumer{ return &OutputClientDataConsumer{
ctx: process.Context(), ctx: process.Context(),
jetstream: js, jetstream: js,
topic: cfg.Matrix.JetStream.TopicFor(jetstream.OutputClientData), topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputClientData),
durable: cfg.Matrix.JetStream.Durable("SyncAPIClientAPIConsumer"), durable: cfg.Matrix.JetStream.Durable("SyncAPIClientAPIConsumer"),
db: store, db: store,
notifier: notifier, notifier: notifier,

View file

@ -62,7 +62,7 @@ func NewOutputReceiptEventConsumer(
return &OutputReceiptEventConsumer{ return &OutputReceiptEventConsumer{
ctx: process.Context(), ctx: process.Context(),
jetstream: js, jetstream: js,
topic: cfg.Matrix.JetStream.TopicFor(jetstream.OutputReceiptEvent), topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputReceiptEvent),
durable: cfg.Matrix.JetStream.Durable("SyncAPIEDUServerReceiptConsumer"), durable: cfg.Matrix.JetStream.Durable("SyncAPIEDUServerReceiptConsumer"),
db: store, db: store,
notifier: notifier, notifier: notifier,

View file

@ -57,7 +57,7 @@ func NewOutputSendToDeviceEventConsumer(
return &OutputSendToDeviceEventConsumer{ return &OutputSendToDeviceEventConsumer{
ctx: process.Context(), ctx: process.Context(),
jetstream: js, jetstream: js,
topic: cfg.Matrix.JetStream.TopicFor(jetstream.OutputSendToDeviceEvent), topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputSendToDeviceEvent),
durable: cfg.Matrix.JetStream.Durable("SyncAPIEDUServerSendToDeviceConsumer"), durable: cfg.Matrix.JetStream.Durable("SyncAPIEDUServerSendToDeviceConsumer"),
db: store, db: store,
serverName: cfg.Matrix.ServerName, serverName: cfg.Matrix.ServerName,

View file

@ -56,7 +56,7 @@ func NewOutputTypingEventConsumer(
return &OutputTypingEventConsumer{ return &OutputTypingEventConsumer{
ctx: process.Context(), ctx: process.Context(),
jetstream: js, jetstream: js,
topic: cfg.Matrix.JetStream.TopicFor(jetstream.OutputTypingEvent), topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputTypingEvent),
durable: cfg.Matrix.JetStream.Durable("SyncAPIEDUServerTypingConsumer"), durable: cfg.Matrix.JetStream.Durable("SyncAPIEDUServerTypingConsumer"),
eduCache: eduCache, eduCache: eduCache,
notifier: notifier, notifier: notifier,

View file

@ -65,7 +65,7 @@ func NewOutputRoomEventConsumer(
ctx: process.Context(), ctx: process.Context(),
cfg: cfg, cfg: cfg,
jetstream: js, jetstream: js,
topic: cfg.Matrix.JetStream.TopicFor(jetstream.OutputRoomEvent), topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputRoomEvent),
durable: cfg.Matrix.JetStream.Durable("SyncAPIRoomServerConsumer"), durable: cfg.Matrix.JetStream.Durable("SyncAPIRoomServerConsumer"),
db: store, db: store,
notifier: notifier, notifier: notifier,

View file

@ -56,7 +56,7 @@ func NewOutputNotificationDataConsumer(
ctx: process.Context(), ctx: process.Context(),
jetstream: js, jetstream: js,
durable: cfg.Matrix.JetStream.Durable("SyncAPINotificationDataConsumer"), durable: cfg.Matrix.JetStream.Durable("SyncAPINotificationDataConsumer"),
topic: cfg.Matrix.JetStream.TopicFor(jetstream.OutputNotificationData), topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputNotificationData),
db: store, db: store,
notifier: notifier, notifier: notifier,
stream: stream, stream: stream,

View file

@ -41,7 +41,6 @@ type messagesReq struct {
roomID string roomID string
from *types.TopologyToken from *types.TopologyToken
to *types.TopologyToken to *types.TopologyToken
fromStream *types.StreamingToken
device *userapi.Device device *userapi.Device
wasToProvided bool wasToProvided bool
backwardOrdering bool backwardOrdering bool
@ -50,7 +49,7 @@ type messagesReq struct {
type messagesResp struct { type messagesResp struct {
Start string `json:"start"` Start string `json:"start"`
StartStream string `json:"start_stream,omitempty"` // NOTSPEC: so clients can hit /messages then immediately /sync with a latest sync token StartStream string `json:"start_stream,omitempty"` // NOTSPEC: used by Cerulean, so clients can hit /messages then immediately /sync with a latest sync token
End string `json:"end"` End string `json:"end"`
Chunk []gomatrixserverlib.ClientEvent `json:"chunk"` Chunk []gomatrixserverlib.ClientEvent `json:"chunk"`
State []gomatrixserverlib.ClientEvent `json:"state"` State []gomatrixserverlib.ClientEvent `json:"state"`
@ -93,6 +92,7 @@ func OnIncomingMessagesRequest(
// Pagination tokens. // Pagination tokens.
var fromStream *types.StreamingToken var fromStream *types.StreamingToken
fromQuery := req.URL.Query().Get("from") fromQuery := req.URL.Query().Get("from")
toQuery := req.URL.Query().Get("to")
emptyFromSupplied := fromQuery == "" emptyFromSupplied := fromQuery == ""
if emptyFromSupplied { if emptyFromSupplied {
// NOTSPEC: We will pretend they used the latest sync token if no ?from= was provided. // NOTSPEC: We will pretend they used the latest sync token if no ?from= was provided.
@ -101,18 +101,6 @@ func OnIncomingMessagesRequest(
fromQuery = currPos.String() fromQuery = currPos.String()
} }
from, err := types.NewTopologyTokenFromString(fromQuery)
if err != nil {
fs, err2 := types.NewStreamTokenFromString(fromQuery)
fromStream = &fs
if err2 != nil {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: jsonerror.InvalidArgumentValue("Invalid from parameter: " + err2.Error()),
}
}
}
// Direction to return events from. // Direction to return events from.
dir := req.URL.Query().Get("dir") dir := req.URL.Query().Get("dir")
if dir != "b" && dir != "f" { if dir != "b" && dir != "f" {
@ -125,16 +113,43 @@ func OnIncomingMessagesRequest(
// to have one of the two accepted values (so dir == "f" <=> !backwardOrdering). // to have one of the two accepted values (so dir == "f" <=> !backwardOrdering).
backwardOrdering := (dir == "b") backwardOrdering := (dir == "b")
from, err := types.NewTopologyTokenFromString(fromQuery)
if err != nil {
var streamToken types.StreamingToken
if streamToken, err = types.NewStreamTokenFromString(fromQuery); err != nil {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: jsonerror.InvalidArgumentValue("Invalid from parameter: " + err.Error()),
}
} else {
fromStream = &streamToken
from, err = db.StreamToTopologicalPosition(req.Context(), roomID, streamToken.PDUPosition, backwardOrdering)
if err != nil {
logrus.WithError(err).Errorf("Failed to get topological position for streaming token %v", streamToken)
return jsonerror.InternalServerError()
}
}
}
// Pagination tokens. To is optional, and its default value depends on the // Pagination tokens. To is optional, and its default value depends on the
// direction ("b" or "f"). // direction ("b" or "f").
var to types.TopologyToken var to types.TopologyToken
wasToProvided := true wasToProvided := true
if s := req.URL.Query().Get("to"); len(s) > 0 { if len(toQuery) > 0 {
to, err = types.NewTopologyTokenFromString(s) to, err = types.NewTopologyTokenFromString(toQuery)
if err != nil { if err != nil {
return util.JSONResponse{ var streamToken types.StreamingToken
Code: http.StatusBadRequest, if streamToken, err = types.NewStreamTokenFromString(toQuery); err != nil {
JSON: jsonerror.InvalidArgumentValue("Invalid to parameter: " + err.Error()), return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: jsonerror.InvalidArgumentValue("Invalid to parameter: " + err.Error()),
}
} else {
to, err = db.StreamToTopologicalPosition(req.Context(), roomID, streamToken.PDUPosition, !backwardOrdering)
if err != nil {
logrus.WithError(err).Errorf("Failed to get topological position for streaming token %v", streamToken)
return jsonerror.InternalServerError()
}
} }
} }
} else { } else {
@ -168,7 +183,6 @@ func OnIncomingMessagesRequest(
roomID: roomID, roomID: roomID,
from: &from, from: &from,
to: &to, to: &to,
fromStream: fromStream,
wasToProvided: wasToProvided, wasToProvided: wasToProvided,
filter: filter, filter: filter,
backwardOrdering: backwardOrdering, backwardOrdering: backwardOrdering,
@ -215,7 +229,7 @@ func OnIncomingMessagesRequest(
End: end.String(), End: end.String(),
State: state, State: state,
} }
if emptyFromSupplied { if fromStream != nil {
res.StartStream = fromStream.String() res.StartStream = fromStream.String()
} }
@ -251,17 +265,9 @@ func (r *messagesReq) retrieveEvents() (
eventFilter := r.filter eventFilter := r.filter
// Retrieve the events from the local database. // Retrieve the events from the local database.
var streamEvents []types.StreamEvent streamEvents, err := r.db.GetEventsInTopologicalRange(
if r.fromStream != nil { r.ctx, r.from, r.to, r.roomID, eventFilter.Limit, r.backwardOrdering,
toStream := r.to.StreamToken() )
streamEvents, err = r.db.GetEventsInStreamingRange(
r.ctx, r.fromStream, &toStream, r.roomID, eventFilter, r.backwardOrdering,
)
} else {
streamEvents, err = r.db.GetEventsInTopologicalRange(
r.ctx, r.from, r.to, r.roomID, eventFilter.Limit, r.backwardOrdering,
)
}
if err != nil { if err != nil {
err = fmt.Errorf("GetEventsInRange: %w", err) err = fmt.Errorf("GetEventsInRange: %w", err)
return return

View file

@ -103,8 +103,6 @@ type Database interface {
// DeletePeek deletes all peeks for a given room by a given user // DeletePeek deletes all peeks for a given room by a given user
// Returns an error if there was a problem communicating with the database. // Returns an error if there was a problem communicating with the database.
DeletePeeks(ctx context.Context, RoomID, UserID string) (types.StreamPosition, error) DeletePeeks(ctx context.Context, RoomID, UserID string) (types.StreamPosition, error)
// GetEventsInStreamingRange retrieves all of the events on a given ordering using the given extremities and limit.
GetEventsInStreamingRange(ctx context.Context, from, to *types.StreamingToken, roomID string, eventFilter *gomatrixserverlib.RoomEventFilter, backwardOrdering bool) (events []types.StreamEvent, err error)
// GetEventsInTopologicalRange retrieves all of the events on a given ordering using the given extremities and limit. // GetEventsInTopologicalRange retrieves all of the events on a given ordering using the given extremities and limit.
GetEventsInTopologicalRange(ctx context.Context, from, to *types.TopologyToken, roomID string, limit int, backwardOrdering bool) (events []types.StreamEvent, err error) GetEventsInTopologicalRange(ctx context.Context, from, to *types.TopologyToken, roomID string, limit int, backwardOrdering bool) (events []types.StreamEvent, err error)
// EventPositionInTopology returns the depth and stream position of the given event. // EventPositionInTopology returns the depth and stream position of the given event.
@ -149,4 +147,6 @@ type Database interface {
SelectContextEvent(ctx context.Context, roomID, eventID string) (int, gomatrixserverlib.HeaderedEvent, error) SelectContextEvent(ctx context.Context, roomID, eventID string) (int, gomatrixserverlib.HeaderedEvent, error)
SelectContextBeforeEvent(ctx context.Context, id int, roomID string, filter *gomatrixserverlib.RoomEventFilter) ([]*gomatrixserverlib.HeaderedEvent, error) SelectContextBeforeEvent(ctx context.Context, id int, roomID string, filter *gomatrixserverlib.RoomEventFilter) ([]*gomatrixserverlib.HeaderedEvent, error)
SelectContextAfterEvent(ctx context.Context, id int, roomID string, filter *gomatrixserverlib.RoomEventFilter) (int, []*gomatrixserverlib.HeaderedEvent, error) SelectContextAfterEvent(ctx context.Context, id int, roomID string, filter *gomatrixserverlib.RoomEventFilter) (int, []*gomatrixserverlib.HeaderedEvent, error)
StreamToTopologicalPosition(ctx context.Context, roomID string, streamPos types.StreamPosition, backwardOrdering bool) (types.TopologyToken, error)
} }

View file

@ -472,7 +472,7 @@ func (s *outputRoomEventsStatements) SelectContextBeforeEvent(
if err != nil { if err != nil {
return return
} }
defer rows.Close() defer internal.CloseAndLogIfError(ctx, rows, "rows.close() failed")
for rows.Next() { for rows.Next() {
var ( var (
@ -504,7 +504,7 @@ func (s *outputRoomEventsStatements) SelectContextAfterEvent(
if err != nil { if err != nil {
return return
} }
defer rows.Close() defer internal.CloseAndLogIfError(ctx, rows, "rows.close() failed")
for rows.Next() { for rows.Next() {
var ( var (

View file

@ -51,7 +51,7 @@ const selectEventIDsInRangeASCSQL = "" +
"SELECT event_id FROM syncapi_output_room_events_topology" + "SELECT event_id FROM syncapi_output_room_events_topology" +
" WHERE room_id = $1 AND (" + " WHERE room_id = $1 AND (" +
"(topological_position > $2 AND topological_position < $3) OR" + "(topological_position > $2 AND topological_position < $3) OR" +
"(topological_position = $4 AND stream_position <= $5)" + "(topological_position = $4 AND stream_position >= $5)" +
") ORDER BY topological_position ASC, stream_position ASC LIMIT $6" ") ORDER BY topological_position ASC, stream_position ASC LIMIT $6"
const selectEventIDsInRangeDESCSQL = "" + const selectEventIDsInRangeDESCSQL = "" +
@ -76,13 +76,21 @@ const selectMaxPositionInTopologySQL = "" +
const deleteTopologyForRoomSQL = "" + const deleteTopologyForRoomSQL = "" +
"DELETE FROM syncapi_output_room_events_topology WHERE room_id = $1" "DELETE FROM syncapi_output_room_events_topology WHERE room_id = $1"
const selectStreamToTopologicalPositionAscSQL = "" +
"SELECT topological_position FROM syncapi_output_room_events_topology WHERE room_id = $1 AND stream_position >= $2 ORDER BY topological_position ASC LIMIT 1;"
const selectStreamToTopologicalPositionDescSQL = "" +
"SELECT topological_position FROM syncapi_output_room_events_topology WHERE room_id = $1 AND stream_position <= $2 ORDER BY topological_position DESC LIMIT 1;"
type outputRoomEventsTopologyStatements struct { type outputRoomEventsTopologyStatements struct {
insertEventInTopologyStmt *sql.Stmt insertEventInTopologyStmt *sql.Stmt
selectEventIDsInRangeASCStmt *sql.Stmt selectEventIDsInRangeASCStmt *sql.Stmt
selectEventIDsInRangeDESCStmt *sql.Stmt selectEventIDsInRangeDESCStmt *sql.Stmt
selectPositionInTopologyStmt *sql.Stmt selectPositionInTopologyStmt *sql.Stmt
selectMaxPositionInTopologyStmt *sql.Stmt selectMaxPositionInTopologyStmt *sql.Stmt
deleteTopologyForRoomStmt *sql.Stmt deleteTopologyForRoomStmt *sql.Stmt
selectStreamToTopologicalPositionAscStmt *sql.Stmt
selectStreamToTopologicalPositionDescStmt *sql.Stmt
} }
func NewPostgresTopologyTable(db *sql.DB) (tables.Topology, error) { func NewPostgresTopologyTable(db *sql.DB) (tables.Topology, error) {
@ -109,6 +117,12 @@ func NewPostgresTopologyTable(db *sql.DB) (tables.Topology, error) {
if s.deleteTopologyForRoomStmt, err = db.Prepare(deleteTopologyForRoomSQL); err != nil { if s.deleteTopologyForRoomStmt, err = db.Prepare(deleteTopologyForRoomSQL); err != nil {
return nil, err return nil, err
} }
if s.selectStreamToTopologicalPositionAscStmt, err = db.Prepare(selectStreamToTopologicalPositionAscSQL); err != nil {
return nil, err
}
if s.selectStreamToTopologicalPositionDescStmt, err = db.Prepare(selectStreamToTopologicalPositionDescSQL); err != nil {
return nil, err
}
return s, nil return s, nil
} }
@ -170,6 +184,19 @@ func (s *outputRoomEventsTopologyStatements) SelectPositionInTopology(
return return
} }
// SelectStreamToTopologicalPosition returns the closest position of a given event
// in the topology of the room it belongs to from the given stream position.
func (s *outputRoomEventsTopologyStatements) SelectStreamToTopologicalPosition(
ctx context.Context, txn *sql.Tx, roomID string, streamPos types.StreamPosition, backwardOrdering bool,
) (topoPos types.StreamPosition, err error) {
if backwardOrdering {
err = s.selectStreamToTopologicalPositionDescStmt.QueryRowContext(ctx, roomID, streamPos).Scan(&topoPos)
} else {
err = s.selectStreamToTopologicalPositionAscStmt.QueryRowContext(ctx, roomID, streamPos).Scan(&topoPos)
}
return
}
func (s *outputRoomEventsTopologyStatements) SelectMaxPositionInTopology( func (s *outputRoomEventsTopologyStatements) SelectMaxPositionInTopology(
ctx context.Context, txn *sql.Tx, roomID string, ctx context.Context, txn *sql.Tx, roomID string,
) (pos types.StreamPosition, spos types.StreamPosition, err error) { ) (pos types.StreamPosition, spos types.StreamPosition, err error) {

View file

@ -32,7 +32,6 @@ type SyncServerDatasource struct {
shared.Database shared.Database
db *sql.DB db *sql.DB
writer sqlutil.Writer writer sqlutil.Writer
sqlutil.PartitionOffsetStatements
} }
// NewDatabase creates a new sync server database // NewDatabase creates a new sync server database
@ -43,9 +42,6 @@ func NewDatabase(dbProperties *config.DatabaseOptions) (*SyncServerDatasource, e
return nil, err return nil, err
} }
d.writer = sqlutil.NewDummyWriter() d.writer = sqlutil.NewDummyWriter()
if err = d.PartitionOffsetStatements.Prepare(d.db, d.writer, "syncapi"); err != nil {
return nil, err
}
accountData, err := NewPostgresAccountDataTable(d.db) accountData, err := NewPostgresAccountDataTable(d.db)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -155,37 +155,6 @@ func (d *Database) Events(ctx context.Context, eventIDs []string) ([]*gomatrixse
return d.StreamEventsToEvents(nil, streamEvents), nil return d.StreamEventsToEvents(nil, streamEvents), nil
} }
// GetEventsInStreamingRange retrieves all of the events on a given ordering using the
// given extremities and limit.
func (d *Database) GetEventsInStreamingRange(
ctx context.Context,
from, to *types.StreamingToken,
roomID string, eventFilter *gomatrixserverlib.RoomEventFilter,
backwardOrdering bool,
) (events []types.StreamEvent, err error) {
r := types.Range{
From: from.PDUPosition,
To: to.PDUPosition,
Backwards: backwardOrdering,
}
if backwardOrdering {
// When using backward ordering, we want the most recent events first.
if events, _, err = d.OutputEvents.SelectRecentEvents(
ctx, nil, roomID, r, eventFilter, false, false,
); err != nil {
return
}
} else {
// When using forward ordering, we want the least recent events first.
if events, err = d.OutputEvents.SelectEarlyEvents(
ctx, nil, roomID, r, eventFilter,
); err != nil {
return
}
}
return events, err
}
func (d *Database) AllJoinedUsersInRooms(ctx context.Context) (map[string][]string, error) { func (d *Database) AllJoinedUsersInRooms(ctx context.Context) (map[string][]string, error) {
return d.CurrentRoomState.SelectJoinedUsers(ctx) return d.CurrentRoomState.SelectJoinedUsers(ctx)
} }
@ -513,6 +482,26 @@ func (d *Database) EventPositionInTopology(
return types.TopologyToken{Depth: depth, PDUPosition: stream}, nil return types.TopologyToken{Depth: depth, PDUPosition: stream}, nil
} }
func (d *Database) StreamToTopologicalPosition(
ctx context.Context, roomID string, streamPos types.StreamPosition, backwardOrdering bool,
) (types.TopologyToken, error) {
topoPos, err := d.Topology.SelectStreamToTopologicalPosition(ctx, nil, roomID, streamPos, backwardOrdering)
switch {
case err == sql.ErrNoRows && backwardOrdering: // no events in range, going backward
return types.TopologyToken{PDUPosition: streamPos}, nil
case err == sql.ErrNoRows && !backwardOrdering: // no events in range, going forward
topoPos, streamPos, err = d.Topology.SelectMaxPositionInTopology(ctx, nil, roomID)
if err != nil {
return types.TopologyToken{}, fmt.Errorf("d.Topology.SelectMaxPositionInTopology: %w", err)
}
return types.TopologyToken{Depth: topoPos, PDUPosition: streamPos}, nil
case err != nil: // some other error happened
return types.TopologyToken{}, fmt.Errorf("d.Topology.SelectStreamToTopologicalPosition: %w", err)
default:
return types.TopologyToken{Depth: topoPos, PDUPosition: streamPos}, nil
}
}
func (d *Database) GetFilter( func (d *Database) GetFilter(
ctx context.Context, localpart string, filterID string, ctx context.Context, localpart string, filterID string,
) (*gomatrixserverlib.Filter, error) { ) (*gomatrixserverlib.Filter, error) {

View file

@ -514,7 +514,7 @@ func (s *outputRoomEventsStatements) SelectContextBeforeEvent(
if err != nil { if err != nil {
return return
} }
defer rows.Close() defer internal.CloseAndLogIfError(ctx, rows, "rows.close() failed")
for rows.Next() { for rows.Next() {
var ( var (
@ -550,7 +550,7 @@ func (s *outputRoomEventsStatements) SelectContextAfterEvent(
if err != nil { if err != nil {
return return
} }
defer rows.Close() defer internal.CloseAndLogIfError(ctx, rows, "rows.close() failed")
for rows.Next() { for rows.Next() {
var ( var (

View file

@ -47,7 +47,7 @@ const selectEventIDsInRangeASCSQL = "" +
"SELECT event_id FROM syncapi_output_room_events_topology" + "SELECT event_id FROM syncapi_output_room_events_topology" +
" WHERE room_id = $1 AND (" + " WHERE room_id = $1 AND (" +
"(topological_position > $2 AND topological_position < $3) OR" + "(topological_position > $2 AND topological_position < $3) OR" +
"(topological_position = $4 AND stream_position <= $5)" + "(topological_position = $4 AND stream_position >= $5)" +
") ORDER BY topological_position ASC, stream_position ASC LIMIT $6" ") ORDER BY topological_position ASC, stream_position ASC LIMIT $6"
const selectEventIDsInRangeDESCSQL = "" + const selectEventIDsInRangeDESCSQL = "" +
@ -65,17 +65,22 @@ const selectMaxPositionInTopologySQL = "" +
"SELECT MAX(topological_position), stream_position FROM syncapi_output_room_events_topology" + "SELECT MAX(topological_position), stream_position FROM syncapi_output_room_events_topology" +
" WHERE room_id = $1 ORDER BY stream_position DESC" " WHERE room_id = $1 ORDER BY stream_position DESC"
const deleteTopologyForRoomSQL = "" + const selectStreamToTopologicalPositionAscSQL = "" +
"DELETE FROM syncapi_output_room_events_topology WHERE room_id = $1" "SELECT topological_position FROM syncapi_output_room_events_topology WHERE room_id = $1 AND stream_position >= $2 ORDER BY topological_position ASC LIMIT 1;"
const selectStreamToTopologicalPositionDescSQL = "" +
"SELECT topological_position FROM syncapi_output_room_events_topology WHERE room_id = $1 AND stream_position <= $2 ORDER BY topological_position DESC LIMIT 1;"
type outputRoomEventsTopologyStatements struct { type outputRoomEventsTopologyStatements struct {
db *sql.DB db *sql.DB
insertEventInTopologyStmt *sql.Stmt insertEventInTopologyStmt *sql.Stmt
selectEventIDsInRangeASCStmt *sql.Stmt selectEventIDsInRangeASCStmt *sql.Stmt
selectEventIDsInRangeDESCStmt *sql.Stmt selectEventIDsInRangeDESCStmt *sql.Stmt
selectPositionInTopologyStmt *sql.Stmt selectPositionInTopologyStmt *sql.Stmt
selectMaxPositionInTopologyStmt *sql.Stmt selectMaxPositionInTopologyStmt *sql.Stmt
deleteTopologyForRoomStmt *sql.Stmt deleteTopologyForRoomStmt *sql.Stmt
selectStreamToTopologicalPositionAscStmt *sql.Stmt
selectStreamToTopologicalPositionDescStmt *sql.Stmt
} }
func NewSqliteTopologyTable(db *sql.DB) (tables.Topology, error) { func NewSqliteTopologyTable(db *sql.DB) (tables.Topology, error) {
@ -101,7 +106,10 @@ func NewSqliteTopologyTable(db *sql.DB) (tables.Topology, error) {
if s.selectMaxPositionInTopologyStmt, err = db.Prepare(selectMaxPositionInTopologySQL); err != nil { if s.selectMaxPositionInTopologyStmt, err = db.Prepare(selectMaxPositionInTopologySQL); err != nil {
return nil, err return nil, err
} }
if s.deleteTopologyForRoomStmt, err = db.Prepare(deleteTopologyForRoomSQL); err != nil { if s.selectStreamToTopologicalPositionAscStmt, err = db.Prepare(selectStreamToTopologicalPositionAscSQL); err != nil {
return nil, err
}
if s.selectStreamToTopologicalPositionDescStmt, err = db.Prepare(selectStreamToTopologicalPositionDescSQL); err != nil {
return nil, err return nil, err
} }
return s, nil return s, nil
@ -163,6 +171,19 @@ func (s *outputRoomEventsTopologyStatements) SelectPositionInTopology(
return return
} }
// SelectStreamToTopologicalPosition returns the closest position of a given event
// in the topology of the room it belongs to from the given stream position.
func (s *outputRoomEventsTopologyStatements) SelectStreamToTopologicalPosition(
ctx context.Context, txn *sql.Tx, roomID string, streamPos types.StreamPosition, backwardOrdering bool,
) (topoPos types.StreamPosition, err error) {
if backwardOrdering {
err = s.selectStreamToTopologicalPositionDescStmt.QueryRowContext(ctx, roomID, streamPos).Scan(&topoPos)
} else {
err = s.selectStreamToTopologicalPositionAscStmt.QueryRowContext(ctx, roomID, streamPos).Scan(&topoPos)
}
return
}
func (s *outputRoomEventsTopologyStatements) SelectMaxPositionInTopology( func (s *outputRoomEventsTopologyStatements) SelectMaxPositionInTopology(
ctx context.Context, txn *sql.Tx, roomID string, ctx context.Context, txn *sql.Tx, roomID string,
) (pos types.StreamPosition, spos types.StreamPosition, err error) { ) (pos types.StreamPosition, spos types.StreamPosition, err error) {

View file

@ -28,9 +28,8 @@ import (
// both the database for PDUs and caches for EDUs. // both the database for PDUs and caches for EDUs.
type SyncServerDatasource struct { type SyncServerDatasource struct {
shared.Database shared.Database
db *sql.DB db *sql.DB
writer sqlutil.Writer writer sqlutil.Writer
sqlutil.PartitionOffsetStatements
streamID streamIDStatements streamID streamIDStatements
} }
@ -50,9 +49,6 @@ func NewDatabase(dbProperties *config.DatabaseOptions) (*SyncServerDatasource, e
} }
func (d *SyncServerDatasource) prepare(dbProperties *config.DatabaseOptions) (err error) { func (d *SyncServerDatasource) prepare(dbProperties *config.DatabaseOptions) (err error) {
if err = d.PartitionOffsetStatements.Prepare(d.db, d.writer, "syncapi"); err != nil {
return err
}
if err = d.streamID.prepare(d.db); err != nil { if err = d.streamID.prepare(d.db); err != nil {
return err return err
} }

View file

@ -87,6 +87,8 @@ type Topology interface {
SelectMaxPositionInTopology(ctx context.Context, txn *sql.Tx, roomID string) (depth types.StreamPosition, spos types.StreamPosition, err error) SelectMaxPositionInTopology(ctx context.Context, txn *sql.Tx, roomID string) (depth types.StreamPosition, spos types.StreamPosition, err error)
// DeleteTopologyForRoom removes all topological information for a room. This should only be done when removing the room entirely. // DeleteTopologyForRoom removes all topological information for a room. This should only be done when removing the room entirely.
DeleteTopologyForRoom(ctx context.Context, txn *sql.Tx, roomID string) (err error) DeleteTopologyForRoom(ctx context.Context, txn *sql.Tx, roomID string) (err error)
// SelectStreamToTopologicalPosition converts a stream position to a topological position by finding the nearest topological position in the room.
SelectStreamToTopologicalPosition(ctx context.Context, txn *sql.Tx, roomID string, streamPos types.StreamPosition, forward bool) (topoPos types.StreamPosition, err error)
} }
type CurrentRoomState interface { type CurrentRoomState interface {

View file

@ -147,7 +147,6 @@ func (p *PDUStreamProvider) IncrementalSync(
To: to, To: to,
Backwards: from > to, Backwards: from > to,
} }
newPos = to
var err error var err error
var stateDeltas []types.StateDelta var stateDeltas []types.StateDelta
@ -172,14 +171,26 @@ func (p *PDUStreamProvider) IncrementalSync(
req.Rooms[roomID] = gomatrixserverlib.Join req.Rooms[roomID] = gomatrixserverlib.Join
} }
if len(stateDeltas) == 0 {
return to
}
newPos = from
for _, delta := range stateDeltas { for _, delta := range stateDeltas {
if err = p.addRoomDeltaToResponse(ctx, req.Device, r, delta, &eventFilter, req.Response); err != nil { var pos types.StreamPosition
if pos, err = p.addRoomDeltaToResponse(ctx, req.Device, r, delta, &eventFilter, req.Response); err != nil {
req.Log.WithError(err).Error("d.addRoomDeltaToResponse failed") req.Log.WithError(err).Error("d.addRoomDeltaToResponse failed")
return newPos return to
}
switch {
case r.Backwards && pos < newPos:
fallthrough
case !r.Backwards && pos > newPos:
newPos = pos
} }
} }
return r.To return newPos
} }
func (p *PDUStreamProvider) addRoomDeltaToResponse( func (p *PDUStreamProvider) addRoomDeltaToResponse(
@ -189,7 +200,7 @@ func (p *PDUStreamProvider) addRoomDeltaToResponse(
delta types.StateDelta, delta types.StateDelta,
eventFilter *gomatrixserverlib.RoomEventFilter, eventFilter *gomatrixserverlib.RoomEventFilter,
res *types.Response, res *types.Response,
) error { ) (types.StreamPosition, error) {
if delta.MembershipPos > 0 && delta.Membership == gomatrixserverlib.Leave { if delta.MembershipPos > 0 && delta.Membership == gomatrixserverlib.Leave {
// make sure we don't leak recent events after the leave event. // make sure we don't leak recent events after the leave event.
// TODO: History visibility makes this somewhat complex to handle correctly. For example: // TODO: History visibility makes this somewhat complex to handle correctly. For example:
@ -204,19 +215,42 @@ func (p *PDUStreamProvider) addRoomDeltaToResponse(
eventFilter, true, true, eventFilter, true, true,
) )
if err != nil { if err != nil {
return err return r.From, err
} }
recentEvents := p.DB.StreamEventsToEvents(device, recentStreamEvents) recentEvents := p.DB.StreamEventsToEvents(device, recentStreamEvents)
delta.StateEvents = removeDuplicates(delta.StateEvents, recentEvents) // roll back delta.StateEvents = removeDuplicates(delta.StateEvents, recentEvents) // roll back
prevBatch, err := p.DB.GetBackwardTopologyPos(ctx, recentStreamEvents) prevBatch, err := p.DB.GetBackwardTopologyPos(ctx, recentStreamEvents)
if err != nil { if err != nil {
return err return r.From, err
} }
// XXX: should we ever get this far if we have no recent events or state in this room? // If we didn't return any events at all then don't bother doing anything else.
// in practice we do for peeks, but possibly not joins?
if len(recentEvents) == 0 && len(delta.StateEvents) == 0 { if len(recentEvents) == 0 && len(delta.StateEvents) == 0 {
return nil return r.To, nil
}
// Sort the events so that we can pick out the latest events from both sections.
recentEvents = gomatrixserverlib.HeaderedReverseTopologicalOrdering(recentEvents, gomatrixserverlib.TopologicalOrderByPrevEvents)
delta.StateEvents = gomatrixserverlib.HeaderedReverseTopologicalOrdering(delta.StateEvents, gomatrixserverlib.TopologicalOrderByAuthEvents)
// Work out what the highest stream position is for all of the events in this
// room that were returned.
latestPosition := r.To
updateLatestPosition := func(mostRecentEventID string) {
if _, pos, err := p.DB.PositionInTopology(ctx, mostRecentEventID); err == nil {
switch {
case r.Backwards && pos > latestPosition:
fallthrough
case !r.Backwards && pos < latestPosition:
latestPosition = pos
}
}
}
if len(recentEvents) > 0 {
updateLatestPosition(recentEvents[len(recentEvents)-1].EventID())
}
if len(delta.StateEvents) > 0 {
updateLatestPosition(delta.StateEvents[len(delta.StateEvents)-1].EventID())
} }
switch delta.Membership { switch delta.Membership {
@ -250,7 +284,7 @@ func (p *PDUStreamProvider) addRoomDeltaToResponse(
res.Rooms.Leave[delta.RoomID] = *lr res.Rooms.Leave[delta.RoomID] = *lr
} }
return nil return latestPosition, nil
} }
func (p *PDUStreamProvider) getJoinResponseForCompleteSync( func (p *PDUStreamProvider) getJoinResponseForCompleteSync(

View file

@ -49,7 +49,7 @@ func AddPublicRoutes(
federation *gomatrixserverlib.FederationClient, federation *gomatrixserverlib.FederationClient,
cfg *config.SyncAPI, cfg *config.SyncAPI,
) { ) {
js, _ := jetstream.Prepare(&cfg.Matrix.JetStream) js, _ := jetstream.Prepare(process, &cfg.Matrix.JetStream)
syncDB, err := storage.NewSyncServerDatasource(&cfg.Database) syncDB, err := storage.NewSyncServerDatasource(&cfg.Database)
if err != nil { if err != nil {
@ -67,18 +67,18 @@ func AddPublicRoutes(
userAPIStreamEventProducer := &producers.UserAPIStreamEventProducer{ userAPIStreamEventProducer := &producers.UserAPIStreamEventProducer{
JetStream: js, JetStream: js,
Topic: cfg.Matrix.JetStream.TopicFor(jetstream.OutputStreamEvent), Topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputStreamEvent),
} }
userAPIReadUpdateProducer := &producers.UserAPIReadProducer{ userAPIReadUpdateProducer := &producers.UserAPIReadProducer{
JetStream: js, JetStream: js,
Topic: cfg.Matrix.JetStream.TopicFor(jetstream.OutputReadUpdate), Topic: cfg.Matrix.JetStream.Prefixed(jetstream.OutputReadUpdate),
} }
_ = userAPIReadUpdateProducer _ = userAPIReadUpdateProducer
keyChangeConsumer := consumers.NewOutputKeyChangeEventConsumer( keyChangeConsumer := consumers.NewOutputKeyChangeEventConsumer(
process, cfg, cfg.Matrix.JetStream.TopicFor(jetstream.OutputKeyChangeEvent), process, cfg, cfg.Matrix.JetStream.Prefixed(jetstream.OutputKeyChangeEvent),
js, keyAPI, rsAPI, syncDB, notifier, js, keyAPI, rsAPI, syncDB, notifier,
streams.DeviceListStreamProvider, streams.DeviceListStreamProvider,
) )

View file

@ -239,7 +239,6 @@ Inbound federation can query room alias directory
Outbound federation can query v2 /send_join Outbound federation can query v2 /send_join
Inbound federation can receive v2 /send_join Inbound federation can receive v2 /send_join
Message history can be paginated Message history can be paginated
Getting messages going forward is limited for a departed room (SPEC-216)
Backfill works correctly with history visibility set to joined Backfill works correctly with history visibility set to joined
Guest user cannot call /events globally Guest user cannot call /events globally
Guest users can join guest_access rooms Guest users can join guest_access rooms

View file

@ -27,16 +27,16 @@ import (
// UserInternalAPI is the internal API for information about users and devices. // UserInternalAPI is the internal API for information about users and devices.
type UserInternalAPI interface { type UserInternalAPI interface {
LoginTokenInternalAPI LoginTokenInternalAPI
UserProfileAPI
UserRegisterAPI
UserAccountAPI
UserThreePIDAPI
InputAccountData(ctx context.Context, req *InputAccountDataRequest, res *InputAccountDataResponse) error InputAccountData(ctx context.Context, req *InputAccountDataRequest, res *InputAccountDataResponse) error
PerformAccountCreation(ctx context.Context, req *PerformAccountCreationRequest, res *PerformAccountCreationResponse) error
PerformPasswordUpdate(ctx context.Context, req *PerformPasswordUpdateRequest, res *PerformPasswordUpdateResponse) error
PerformDeviceCreation(ctx context.Context, req *PerformDeviceCreationRequest, res *PerformDeviceCreationResponse) error
PerformDeviceDeletion(ctx context.Context, req *PerformDeviceDeletionRequest, res *PerformDeviceDeletionResponse) error PerformDeviceDeletion(ctx context.Context, req *PerformDeviceDeletionRequest, res *PerformDeviceDeletionResponse) error
PerformLastSeenUpdate(ctx context.Context, req *PerformLastSeenUpdateRequest, res *PerformLastSeenUpdateResponse) error PerformLastSeenUpdate(ctx context.Context, req *PerformLastSeenUpdateRequest, res *PerformLastSeenUpdateResponse) error
PerformDeviceUpdate(ctx context.Context, req *PerformDeviceUpdateRequest, res *PerformDeviceUpdateResponse) error PerformDeviceUpdate(ctx context.Context, req *PerformDeviceUpdateRequest, res *PerformDeviceUpdateResponse) error
PerformAccountDeactivation(ctx context.Context, req *PerformAccountDeactivationRequest, res *PerformAccountDeactivationResponse) error
PerformOpenIDTokenCreation(ctx context.Context, req *PerformOpenIDTokenCreationRequest, res *PerformOpenIDTokenCreationResponse) error PerformOpenIDTokenCreation(ctx context.Context, req *PerformOpenIDTokenCreationRequest, res *PerformOpenIDTokenCreationResponse) error
PerformKeyBackup(ctx context.Context, req *PerformKeyBackupRequest, res *PerformKeyBackupResponse) error PerformKeyBackup(ctx context.Context, req *PerformKeyBackupRequest, res *PerformKeyBackupResponse) error
PerformPusherSet(ctx context.Context, req *PerformPusherSetRequest, res *struct{}) error PerformPusherSet(ctx context.Context, req *PerformPusherSetRequest, res *struct{}) error
@ -44,12 +44,10 @@ type UserInternalAPI interface {
PerformPushRulesPut(ctx context.Context, req *PerformPushRulesPutRequest, res *struct{}) error PerformPushRulesPut(ctx context.Context, req *PerformPushRulesPutRequest, res *struct{}) error
QueryKeyBackup(ctx context.Context, req *QueryKeyBackupRequest, res *QueryKeyBackupResponse) QueryKeyBackup(ctx context.Context, req *QueryKeyBackupRequest, res *QueryKeyBackupResponse)
QueryProfile(ctx context.Context, req *QueryProfileRequest, res *QueryProfileResponse) error
QueryAccessToken(ctx context.Context, req *QueryAccessTokenRequest, res *QueryAccessTokenResponse) error QueryAccessToken(ctx context.Context, req *QueryAccessTokenRequest, res *QueryAccessTokenResponse) error
QueryDevices(ctx context.Context, req *QueryDevicesRequest, res *QueryDevicesResponse) error QueryDevices(ctx context.Context, req *QueryDevicesRequest, res *QueryDevicesResponse) error
QueryAccountData(ctx context.Context, req *QueryAccountDataRequest, res *QueryAccountDataResponse) error QueryAccountData(ctx context.Context, req *QueryAccountDataRequest, res *QueryAccountDataResponse) error
QueryDeviceInfos(ctx context.Context, req *QueryDeviceInfosRequest, res *QueryDeviceInfosResponse) error QueryDeviceInfos(ctx context.Context, req *QueryDeviceInfosRequest, res *QueryDeviceInfosResponse) error
QuerySearchProfiles(ctx context.Context, req *QuerySearchProfilesRequest, res *QuerySearchProfilesResponse) error
QueryOpenIDToken(ctx context.Context, req *QueryOpenIDTokenRequest, res *QueryOpenIDTokenResponse) error QueryOpenIDToken(ctx context.Context, req *QueryOpenIDTokenRequest, res *QueryOpenIDTokenResponse) error
QueryPushers(ctx context.Context, req *QueryPushersRequest, res *QueryPushersResponse) error QueryPushers(ctx context.Context, req *QueryPushersRequest, res *QueryPushersResponse) error
QueryPushRules(ctx context.Context, req *QueryPushRulesRequest, res *QueryPushRulesResponse) error QueryPushRules(ctx context.Context, req *QueryPushRulesRequest, res *QueryPushRulesResponse) error
@ -61,6 +59,37 @@ type UserInternalAPI interface {
UpdateServerNoticeRoomID(ctx context.Context, req *UpdateServerNoticeRoomRequest, res *UpdateServerNoticeRoomResponse) (err error) UpdateServerNoticeRoomID(ctx context.Context, req *UpdateServerNoticeRoomRequest, res *UpdateServerNoticeRoomResponse) (err error)
} }
// UserProfileAPI provides functions for getting user profiles
type UserProfileAPI interface {
QueryProfile(ctx context.Context, req *QueryProfileRequest, res *QueryProfileResponse) error
QuerySearchProfiles(ctx context.Context, req *QuerySearchProfilesRequest, res *QuerySearchProfilesResponse) error
SetAvatarURL(ctx context.Context, req *PerformSetAvatarURLRequest, res *PerformSetAvatarURLResponse) error
SetDisplayName(ctx context.Context, req *PerformUpdateDisplayNameRequest, res *struct{}) error
}
// UserRegisterAPI defines functions for registering accounts
type UserRegisterAPI interface {
QueryNumericLocalpart(ctx context.Context, res *QueryNumericLocalpartResponse) error
QueryAccountAvailability(ctx context.Context, req *QueryAccountAvailabilityRequest, res *QueryAccountAvailabilityResponse) error
PerformAccountCreation(ctx context.Context, req *PerformAccountCreationRequest, res *PerformAccountCreationResponse) error
PerformDeviceCreation(ctx context.Context, req *PerformDeviceCreationRequest, res *PerformDeviceCreationResponse) error
}
// UserAccountAPI defines functions for changing an account
type UserAccountAPI interface {
PerformPasswordUpdate(ctx context.Context, req *PerformPasswordUpdateRequest, res *PerformPasswordUpdateResponse) error
PerformAccountDeactivation(ctx context.Context, req *PerformAccountDeactivationRequest, res *PerformAccountDeactivationResponse) error
QueryAccountByPassword(ctx context.Context, req *QueryAccountByPasswordRequest, res *QueryAccountByPasswordResponse) error
}
// UserThreePIDAPI defines functions for 3PID
type UserThreePIDAPI interface {
QueryLocalpartForThreePID(ctx context.Context, req *QueryLocalpartForThreePIDRequest, res *QueryLocalpartForThreePIDResponse) error
QueryThreePIDsForLocalpart(ctx context.Context, req *QueryThreePIDsForLocalpartRequest, res *QueryThreePIDsForLocalpartResponse) error
PerformForgetThreePID(ctx context.Context, req *PerformForgetThreePIDRequest, res *struct{}) error
PerformSaveThreePIDAssociation(ctx context.Context, req *PerformSaveThreePIDAssociationRequest, res *struct{}) error
}
type PerformKeyBackupRequest struct { type PerformKeyBackupRequest struct {
UserID string UserID string
Version string // optional if modifying a key backup Version string // optional if modifying a key backup
@ -559,3 +588,55 @@ type Notification struct {
RoomID string `json:"room_id"` // Required. RoomID string `json:"room_id"` // Required.
TS gomatrixserverlib.Timestamp `json:"ts"` // Required. TS gomatrixserverlib.Timestamp `json:"ts"` // Required.
} }
type PerformSetAvatarURLRequest struct {
Localpart, AvatarURL string
}
type PerformSetAvatarURLResponse struct{}
type QueryNumericLocalpartResponse struct {
ID int64
}
type QueryAccountAvailabilityRequest struct {
Localpart string
}
type QueryAccountAvailabilityResponse struct {
Available bool
}
type QueryAccountByPasswordRequest struct {
Localpart, PlaintextPassword string
}
type QueryAccountByPasswordResponse struct {
Account *Account
Exists bool
}
type PerformUpdateDisplayNameRequest struct {
Localpart, DisplayName string
}
type QueryLocalpartForThreePIDRequest struct {
ThreePID, Medium string
}
type QueryLocalpartForThreePIDResponse struct {
Localpart string
}
type QueryThreePIDsForLocalpartRequest struct {
Localpart string
}
type QueryThreePIDsForLocalpartResponse struct {
ThreePIDs []authtypes.ThreePID
}
type PerformForgetThreePIDRequest QueryLocalpartForThreePIDRequest
type PerformSaveThreePIDAssociationRequest struct {
ThreePID, Localpart, Medium string
}

Some files were not shown because too many files have changed in this diff Show more