Compare commits

..

36 Commits

Author SHA1 Message Date
Ginger 83b94cc375 feat: Add animated flag to Dim 2025-09-10 10:49:20 -04:00
Ginger aab4c5eaee fix: Use apt-get and dpkg instead of apt (which isn't stable in scripts) 2025-09-10 10:12:57 -04:00
Ginger 660f1ee790 fix: Change cargo-deb steps to play nice with the setup-rust action 2025-09-10 09:41:13 -04:00
Ginger d8f6c0fc67 fix: More logging 2025-09-10 09:20:01 -04:00
Ginger 01dd4575b7 fix: Insert whereis call 2025-09-10 09:13:31 -04:00
Ginger 7f2cd69bd9 fix: More ls calls 2025-09-10 09:03:36 -04:00
Ginger 6ed175f75d fix: Add debugging ls. what is the problem here 2025-09-10 09:00:34 -04:00
ginger 3723233605 fix: Fix cargo-deb cache keys 2025-09-09 20:13:24 +00:00
Ginger f169d68ba7 docs: Add a note about the dev component 2025-09-09 09:21:16 -04:00
Ginger 6805f16d9c fix: Name artifacts for their distro 2025-09-09 09:16:00 -04:00
Ginger 25732963c2 feat: Build for both Debian and Ubuntu, take 2 2025-09-09 08:52:54 -04:00
Ginger 8b6ffc3700 Revert "feat: Build for both Debian and Ubuntu"
This reverts commit 5cd0704e14.
2025-09-08 14:03:33 -04:00
Ginger 5cd0704e14 feat: Build for both Debian and Ubuntu 2025-09-08 13:44:47 -04:00
Ginger 3509c5a65a fix: No slashes in components I guess 2025-09-08 13:33:56 -04:00
Ginger 6c800a293c fix: Fix incorrect quoting, again 2025-09-08 13:18:36 -04:00
Ginger 696e29d087 fix: Fix incorrect quoting 2025-09-08 13:16:37 -04:00
Ginger 863239f341 fix: Minor component and version format fixes 2025-09-08 13:13:47 -04:00
Ginger e7a79b1e03 docs: Update Debian installation guide 2025-09-08 13:11:04 -04:00
Ginger 4af08c2b07 fix: Rename debian_version for consistency 2025-09-08 13:01:40 -04:00
Ginger 980b39c946 fix: Munge version names better 2025-09-08 12:42:24 -04:00
Ginger 67810682cd fix: Install build dependencies _before_ running the build 💀 2025-09-08 12:34:17 -04:00
Ginger 53a7e815da fix: Use month instead of minute in package timestamps 2025-09-08 12:31:09 -04:00
Ginger d3c88c6242 fix: Fix step order 2025-09-08 12:25:21 -04:00
Ginger 30df03524f fix: Cache cargo-deb, use better versioning scheme 2025-09-08 12:24:30 -04:00
Ginger fa4e0b8802 fix: Mark continuwuity as replacing conduwuit 2025-09-08 11:51:38 -04:00
Ginger a3422d5625 fix: Fix typo 2025-09-08 11:29:17 -04:00
Ginger f5003206fa fix: More debug logging 2025-09-08 11:27:38 -04:00
Ginger dbd5d347ec feat: Publish the deb to Forgejo's registry 2025-09-08 11:23:35 -04:00
Ginger a873329a42 fix: Fix ambiguous redirect 2025-09-08 10:27:58 -04:00
Ginger d14310a1e5 fix: Run apt-get update first 2025-09-08 10:23:35 -04:00
Ginger 52fb7b5ede fix: Use binstall for cargo-deb 2025-09-08 10:21:35 -04:00
Ginger ede03e30a7 fix: Remove duplicate checkout step 2025-09-08 10:20:33 -04:00
Ginger 452afb7829 fix: Use Ubuntu runners for now 2025-09-08 10:19:32 -04:00
Ginger 5a2288fe06 fix: Remove copied Fedora-specific workflow step 2025-09-08 09:49:29 -04:00
Ginger 3fc03c9cd7 feat(ci): Initial debian build workflow 2025-09-08 09:46:55 -04:00
Ginger fd501fc290 fix: Update debian package metadata 2025-09-08 09:46:12 -04:00
114 changed files with 1751 additions and 2860 deletions
@@ -1,108 +0,0 @@
name: create-manifest
description: |
Create and push a multi-platform Docker manifest from individual platform digests.
Handles downloading digests, creating manifest lists, and pushing to registry.
inputs:
digest_pattern:
description: Glob pattern to match digest artifacts (e.g. "digests-linux-{amd64,arm64}")
required: true
tag_suffix:
description: Suffix to add to all Docker tags (e.g. "-maxperf")
required: false
default: ""
images:
description: Container registry images (newline-separated)
required: true
registry_user:
description: Registry username for authentication
required: false
registry_password:
description: Registry password for authentication
required: false
outputs:
version:
description: The version tag created for the manifest
value: ${{ steps.meta.outputs.version }}
tags:
description: All tags created for the manifest
value: ${{ steps.meta.outputs.tags }}
runs:
using: composite
steps:
- name: Download digests
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: forgejo/download-artifact@v4
with:
path: /tmp/digests
pattern: ${{ inputs.digest_pattern }}
merge-multiple: true
- name: Login to builtin registry
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/login-action@v3
with:
registry: ${{ env.BUILTIN_REGISTRY }}
username: ${{ inputs.registry_user }}
password: ${{ inputs.registry_password }}
- name: Set up Docker Buildx
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/setup-buildx-action@v3
with:
# Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125)
driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }}
endpoint: ${{ env.BUILDKIT_ENDPOINT || '' }}
- name: Extract metadata (tags) for Docker
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
id: meta
uses: docker/metadata-action@v5
with:
flavour: |
suffix=${{ inputs.tag_suffix }}
tags: |
type=semver,pattern={{version}},prefix=v
type=semver,pattern={{major}}.{{minor}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.0.') }},prefix=v
type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }},prefix=v
type=ref,event=branch,prefix=${{ format('refs/heads/{0}', github.event.repository.default_branch) != github.ref && 'branch-' || '' }},
type=ref,event=pr
type=sha,format=short
type=raw,value=latest${{ inputs.tag_suffix }},enable=${{ startsWith(github.ref, 'refs/tags/v') }},priority=1100
images: ${{ inputs.images }}
# default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
- name: Create manifest list and push
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
working-directory: /tmp/digests
shell: bash
env:
IMAGES: ${{ inputs.images }}
run: |
set -o xtrace
IFS=$'\n'
IMAGES_LIST=($IMAGES)
ANNOTATIONS_LIST=($DOCKER_METADATA_OUTPUT_ANNOTATIONS)
TAGS_LIST=($DOCKER_METADATA_OUTPUT_TAGS)
for REPO in "${IMAGES_LIST[@]}"; do
docker buildx imagetools create \
$(for tag in "${TAGS_LIST[@]}"; do echo "--tag"; echo "$tag"; done) \
$(for annotation in "${ANNOTATIONS_LIST[@]}"; do echo "--annotation"; echo "$annotation"; done) \
$(for reference in *; do printf "$REPO@sha256:%s\n" $reference; done)
done
- name: Inspect image
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
shell: bash
env:
IMAGES: ${{ inputs.images }}
run: |
set -o xtrace
IMAGES_LIST=($IMAGES)
for REPO in "${IMAGES_LIST[@]}"; do
docker buildx imagetools inspect $REPO:${{ steps.meta.outputs.version }}
done
@@ -0,0 +1,58 @@
name: detect-runner-os
description: |
Detect the actual OS name and version of the runner.
Provides separate outputs for name, version, and a combined slug.
outputs:
name:
description: 'OS name (e.g. Ubuntu, Debian)'
value: ${{ steps.detect.outputs.name }}
version:
description: 'OS version (e.g. 22.04, 11)'
value: ${{ steps.detect.outputs.version }}
slug:
description: 'Combined OS slug (e.g. Ubuntu-22.04)'
value: ${{ steps.detect.outputs.slug }}
node_major:
description: 'Major version of Node.js if available (e.g. 22)'
value: ${{ steps.detect.outputs.node_major }}
node_version:
description: 'Full Node.js version if available (e.g. 22.19.0)'
value: ${{ steps.detect.outputs.node_version }}
runs:
using: composite
steps:
- name: Detect runner OS
id: detect
shell: bash
run: |
# Detect OS version (try lsb_release first, fall back to /etc/os-release)
OS_VERSION=$(lsb_release -rs 2>/dev/null || grep VERSION_ID /etc/os-release | cut -d'"' -f2)
# Detect OS name and capitalise (try lsb_release first, fall back to /etc/os-release)
OS_NAME=$(lsb_release -is 2>/dev/null || grep "^ID=" /etc/os-release | cut -d'=' -f2 | tr -d '"' | sed 's/\b\(.\)/\u\1/g')
# Create combined slug
OS_SLUG="${OS_NAME}-${OS_VERSION}"
# Detect Node.js version if available
if command -v node >/dev/null 2>&1; then
NODE_VERSION=$(node --version | sed 's/v//')
NODE_MAJOR=$(echo $NODE_VERSION | cut -d. -f1)
echo "node_version=${NODE_VERSION}" >> $GITHUB_OUTPUT
echo "node_major=${NODE_MAJOR}" >> $GITHUB_OUTPUT
echo "🔍 Detected Node.js: v${NODE_VERSION}"
else
echo "node_version=" >> $GITHUB_OUTPUT
echo "node_major=" >> $GITHUB_OUTPUT
echo "🔍 Node.js not found"
fi
# Set OS outputs
echo "name=${OS_NAME}" >> $GITHUB_OUTPUT
echo "version=${OS_VERSION}" >> $GITHUB_OUTPUT
echo "slug=${OS_SLUG}" >> $GITHUB_OUTPUT
# Log detection results
echo "🔍 Detected Runner OS: ${OS_NAME} ${OS_VERSION}"
@@ -1,169 +0,0 @@
name: prepare-docker-build
description: |
Prepare the Docker build environment for Continuwuity builds.
Sets up Rust toolchain, Docker Buildx, caching, and extracts metadata for Docker builds.
inputs:
platform:
description: Target platform (e.g. linux/amd64, linux/arm64)
required: true
slug:
description: Platform slug for artifact naming (e.g. linux-amd64, linux-arm64)
required: true
target_cpu:
description: Target CPU architecture (e.g. haswell, empty for base)
required: false
default: ""
profile:
description: Cargo build profile (release or release-max-perf)
required: true
images:
description: Container registry images (newline-separated)
required: true
registry_user:
description: Registry username for authentication
required: false
registry_password:
description: Registry password for authentication
required: false
outputs:
cpu_suffix:
description: CPU suffix for artifact naming
value: ${{ steps.cpu-suffix.outputs.suffix }}
metadata_labels:
description: Docker labels for the image
value: ${{ steps.meta.outputs.labels }}
metadata_annotations:
description: Docker annotations for the image
value: ${{ steps.meta.outputs.annotations }}
runs:
using: composite
steps:
- name: Set CPU suffix variable
id: cpu-suffix
shell: bash
run: |
if [[ -n "${{ inputs.target_cpu }}" ]]; then
echo "suffix=-${{ inputs.target_cpu }}" >> $GITHUB_OUTPUT
echo "CPU_SUFFIX=-${{ inputs.target_cpu }}" >> $GITHUB_ENV
else
echo "suffix=" >> $GITHUB_OUTPUT
echo "CPU_SUFFIX=" >> $GITHUB_ENV
fi
- name: Echo matrix configuration
shell: bash
run: |
echo "Platform: ${{ inputs.platform }}"
echo "Slug: ${{ inputs.slug }}"
echo "Target CPU: ${{ inputs.target_cpu }}"
echo "Profile: ${{ inputs.profile }}"
- name: Install rust
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
id: rust-toolchain
uses: ./.forgejo/actions/rust-toolchain
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
# Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125)
driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }}
endpoint: ${{ env.BUILDKIT_ENDPOINT || '' }}
- name: Set up QEMU
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
uses: docker/setup-qemu-action@v3
- name: Login to builtin registry
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/login-action@v3
with:
registry: ${{ env.BUILTIN_REGISTRY }}
username: ${{ inputs.registry_user }}
password: ${{ inputs.registry_password }}
- name: Extract metadata (labels, annotations) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ inputs.images }}
# default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
- name: Get short git commit SHA
id: sha
shell: bash
run: |
calculatedSha=$(git rev-parse --short ${{ github.sha }})
echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV
echo "Short SHA: $calculatedSha"
- name: Get Git commit timestamps
shell: bash
run: |
timestamp=$(git log -1 --pretty=%ct)
echo "TIMESTAMP=$timestamp" >> $GITHUB_ENV
echo "Commit timestamp: $timestamp"
- uses: ./.forgejo/actions/timelord
id: timelord
- name: Cache Rust registry
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
uses: actions/cache@v3
with:
path: |
.cargo/git
.cargo/git/checkouts
.cargo/registry
.cargo/registry/src
key: continuwuity-rust-registry-image-${{hashFiles('**/Cargo.lock') }}
- name: Cache cargo target
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
id: cache-cargo-target
uses: actions/cache@v3
with:
path: |
cargo-target${{ env.CPU_SUFFIX }}-${{ inputs.slug }}-${{ inputs.profile }}
key: continuwuity-cargo-target${{ env.CPU_SUFFIX }}-${{ inputs.slug }}-${{ inputs.profile }}-${{hashFiles('**/Cargo.lock') }}-${{steps.rust-toolchain.outputs.rustc_version}}
- name: Cache apt cache
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
id: cache-apt
uses: actions/cache@v3
with:
path: |
var-cache-apt-${{ inputs.slug }}
key: continuwuity-var-cache-apt-${{ inputs.slug }}
- name: Cache apt lib
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
id: cache-apt-lib
uses: actions/cache@v3
with:
path: |
var-lib-apt-${{ inputs.slug }}
key: continuwuity-var-lib-apt-${{ inputs.slug }}
- name: inject cache into docker
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
uses: https://github.com/reproducible-containers/buildkit-cache-dance@v3.3.0
with:
cache-map: |
{
".cargo/registry": "/usr/local/cargo/registry",
".cargo/git/db": "/usr/local/cargo/git/db",
"cargo-target${{ env.CPU_SUFFIX }}-${{ inputs.slug }}-${{ inputs.profile }}": {
"target": "/app/target",
"id": "cargo-target${{ env.CPU_SUFFIX }}-${{ inputs.slug }}-${{ inputs.profile }}"
},
"var-cache-apt-${{ inputs.slug }}": "/var/cache/apt",
"var-lib-apt-${{ inputs.slug }}": "/var/lib/apt",
"${{ steps.timelord.outputs.database-path }}":"/timelord"
}
skip-extraction: ${{ steps.cache.outputs.cache-hit }}
+1 -1
View File
@@ -40,7 +40,7 @@ runs:
!~/.rustup/tmp
!~/.rustup/downloads
# Requires repo to be cloned if toolchain is not specified
key: continuwuity-${{ runner.os }}-rustup-${{ inputs.toolchain || hashFiles('**/rust-toolchain.toml') }}
key: ${{ runner.os }}-rustup-${{ inputs.toolchain || hashFiles('**/rust-toolchain.toml') }}
- name: Install Rust toolchain
if: steps.rustup-version.outputs.version == ''
shell: bash
+1 -1
View File
@@ -9,7 +9,7 @@ runs:
- name: Install sccache
uses: https://git.tomfos.tr/tom/sccache-action@v1
- name: Configure sccache
uses: https://github.com/actions/github-script@v8
uses: https://github.com/actions/github-script@v7
with:
script: |
core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || '');
@@ -29,7 +29,7 @@ runs:
steps:
- name: Detect runner OS
id: runner-os
uses: https://git.tomfos.tr/actions/detect-versions@v1
uses: ./.forgejo/actions/detect-runner-os
- name: Configure cross-compilation architecture
if: inputs.dpkg-arch != ''
@@ -57,7 +57,7 @@ runs:
- name: Check for LLVM cache
id: cache
uses: actions/cache@v4
uses: https://github.com/actions/cache@v4
with:
path: |
/usr/bin/clang-*
@@ -69,7 +69,7 @@ runs:
/usr/lib/x86_64-linux-gnu/libclang*.so*
/etc/apt/sources.list.d/archive_uri-*
/etc/apt/trusted.gpg.d/apt.llvm.org.asc
key: continuwuity-llvm-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-v${{ inputs.llvm-version }}-${{ hashFiles('**/Cargo.lock', 'rust-toolchain.toml') }}
key: llvm-${{ steps.runner-os.outputs.slug }}-v${{ inputs.llvm-version }}-v3-${{ hashFiles('**/Cargo.lock', 'rust-toolchain.toml') }}
- name: End LLVM cache group
shell: bash
+17 -30
View File
@@ -39,7 +39,7 @@ runs:
steps:
- name: Detect runner OS
id: runner-os
uses: https://git.tomfos.tr/actions/detect-versions@v1
uses: ./.forgejo/actions/detect-runner-os
- name: Configure Cargo environment
shell: bash
@@ -65,7 +65,7 @@ runs:
- name: Cache Cargo registry and git
id: registry-cache
uses: actions/cache@v4
uses: https://github.com/actions/cache@v4
with:
path: |
.cargo/registry/index
@@ -73,55 +73,42 @@ runs:
.cargo/git/db
# Registry cache saved per workflow, restored from any workflow's cache
# Each workflow maintains its own registry that accumulates its needed crates
key: continuwuity-cargo-registry-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ github.workflow }}
key: cargo-registry-${{ steps.runner-os.outputs.slug }}-${{ github.workflow }}
restore-keys: |
continuwuity-cargo-registry-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-
cargo-registry-${{ steps.runner-os.outputs.slug }}-
- name: Cache toolchain binaries
id: toolchain-cache
uses: actions/cache@v4
uses: https://github.com/actions/cache@v4
with:
path: |
.cargo/bin
.rustup/toolchains
.rustup/update-hashes
# Shared toolchain cache across all Rust versions
key: continuwuity-toolchain-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}
key: toolchain-${{ steps.runner-os.outputs.slug }}
- name: Setup sccache
uses: https://git.tomfos.tr/tom/sccache-action@v1
- name: Cache dependencies
id: deps-cache
uses: actions/cache@v4
- name: Cache build artifacts
id: build-cache
uses: https://github.com/actions/cache@v4
with:
path: |
target/**/.fingerprint
target/**/deps
target/**/*.d
target/**/.cargo-lock
target/**/CACHEDIR.TAG
target/**/.rustc_info.json
/timelord/
# Dependencies cache - based on Cargo.lock, survives source code changes
key: >-
continuwuity-deps-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ inputs.rust-version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-${{ hashFiles('rust-toolchain.toml', '**/Cargo.lock') }}
restore-keys: |
continuwuity-deps-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ inputs.rust-version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-
- name: Cache incremental compilation
id: incremental-cache
uses: actions/cache@v4
with:
path: |
!target/**/deps/*.rlib
target/**/build
target/**/.fingerprint
target/**/incremental
# Incremental cache - based on source code changes
target/**/*.d
/timelord/
# Build artifacts - cache per code change, restore from deps when code changes
key: >-
continuwuity-incremental-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ inputs.rust-version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-${{ hashFiles('rust-toolchain.toml', '**/Cargo.lock') }}-${{ hashFiles('**/*.rs', '**/Cargo.toml') }}
build-${{ steps.runner-os.outputs.slug }}-${{ inputs.rust-version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-${{ hashFiles('rust-toolchain.toml', '**/Cargo.lock') }}-${{ hashFiles('**/*.rs', '**/Cargo.toml') }}
restore-keys: |
continuwuity-incremental-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ inputs.rust-version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-${{ hashFiles('rust-toolchain.toml', '**/Cargo.lock') }}-
continuwuity-incremental-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ inputs.rust-version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-
build-${{ steps.runner-os.outputs.slug }}-${{ inputs.rust-version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-${{ hashFiles('rust-toolchain.toml', '**/Cargo.lock') }}-
- name: End cache restore group
shell: bash
+28 -102
View File
@@ -1,120 +1,46 @@
name: timelord
description: |
Use timelord to set file timestamps with git-warp-time fallback for cache misses
Use timelord to set file timestamps
inputs:
key:
description: |
The key to use for caching the timelord data.
required: false
default: ''
This should be unique to the repository and the runner.
required: true
default: timelord-v0
path:
description: |
The path to the directory to be timestamped.
required: false
default: ''
outputs:
database-path:
description: Path to timelord database
value: '${{ env.TIMELORD_CACHE_PATH }}'
This should be the root of the repository.
required: true
default: .
runs:
using: composite
steps:
- name: Set defaults
- name: Cache timelord-cli installation
id: cache-timelord-bin
uses: actions/cache@v3
with:
path: ~/.cargo/bin/timelord
key: timelord-cli-v3.0.1
- name: Install timelord-cli
uses: https://github.com/cargo-bins/cargo-binstall@main
if: steps.cache-timelord-bin.outputs.cache-hit != 'true'
- run: cargo binstall timelord-cli@3.0.1
shell: bash
run: |
echo "TIMELORD_KEY=${{ inputs.key || format('timelord-v1-{0}-{1}', github.repository, hashFiles('**/*.rs', '**/Cargo.toml', '**/Cargo.lock')) }}" >> $GITHUB_ENV
echo "TIMELORD_PATH=${{ inputs.path || '.' }}" >> $GITHUB_ENV
echo "TIMELORD_CACHE_PATH=$HOME/.cache/timelord" >> $GITHUB_ENV
echo "PATH=$HOME/.cargo/bin:/usr/share/rust/.cargo/bin:$PATH" >> $GITHUB_ENV
if: steps.cache-timelord-bin.outputs.cache-hit != 'true'
- name: Restore binary cache
id: binary-cache
uses: actions/cache/restore@v4
- name: Load timelord files
uses: actions/cache/restore@v3
with:
path: |
/usr/share/rust/.cargo/bin
~/.cargo/bin
key: continuwuity-timelord-binaries
- name: Check if binaries need installation
path: /timelord/
key: ${{ inputs.key }}
- name: Run timelord to set timestamps
shell: bash
id: check-binaries
run: |
NEED_INSTALL=false
# Ensure ~/.cargo/bin exists
mkdir -p ~/.cargo/bin
# Check and move timelord if needed
if [ -f /usr/share/rust/.cargo/bin/timelord ] && [ ! -f ~/.cargo/bin/timelord ]; then
echo "Moving timelord from /usr/share/rust/.cargo/bin to ~/.cargo/bin"
mv /usr/share/rust/.cargo/bin/timelord ~/.cargo/bin/
fi
if [ ! -f ~/.cargo/bin/timelord ]; then
echo "timelord-cli not found, needs installation"
NEED_INSTALL=true
fi
# Check and move git-warp-time if needed
if [ -f /usr/share/rust/.cargo/bin/git-warp-time ] && [ ! -f ~/.cargo/bin/git-warp-time ]; then
echo "Moving git-warp-time from /usr/share/rust/.cargo/bin to ~/.cargo/bin"
mv /usr/share/rust/.cargo/bin/git-warp-time ~/.cargo/bin/
fi
if [ ! -f ~/.cargo/bin/git-warp-time ]; then
echo "git-warp-time not found, needs installation"
NEED_INSTALL=true
fi
echo "need-install=$NEED_INSTALL" >> $GITHUB_OUTPUT
- name: Install timelord-cli and git-warp-time
if: steps.check-binaries.outputs.need-install == 'true'
uses: https://github.com/taiki-e/install-action@v2
run: timelord sync --source-dir ${{ inputs.path }} --cache-dir /timelord/
- name: Save timelord
uses: actions/cache/save@v3
with:
tool: git-warp-time,timelord-cli@3.0.1
- name: Save binary cache
if: steps.check-binaries.outputs.need-install == 'true'
uses: actions/cache/save@v4
with:
path: |
/usr/share/rust/.cargo/bin
~/.cargo/bin
key: continuwuity-timelord-binaries
- name: Restore timelord cache with fallbacks
id: timelord-restore
uses: actions/cache/restore@v4
with:
path: ${{ env.TIMELORD_CACHE_PATH }}
key: ${{ env.TIMELORD_KEY }}
restore-keys: |
continuwuity-timelord-${{ github.repository }}-
- name: Initialize timestamps on complete cache miss
if: steps.timelord-restore.outputs.cache-hit != 'true'
shell: bash
run: |
echo "Complete timelord cache miss - running git-warp-time"
git fetch --unshallow
if [ "${{ env.TIMELORD_PATH }}" = "." ]; then
git-warp-time --quiet
else
git-warp-time --quiet ${{ env.TIMELORD_PATH }}
fi
echo "Git timestamps restored"
- name: Run timelord sync
shell: bash
run: |
mkdir -p ${{ env.TIMELORD_CACHE_PATH }}
timelord sync --source-dir ${{ env.TIMELORD_PATH }} --cache-dir ${{ env.TIMELORD_CACHE_PATH }}
- name: Save updated timelord cache immediately
uses: actions/cache/save@v4
with:
path: ${{ env.TIMELORD_CACHE_PATH }}
key: ${{ env.TIMELORD_KEY }}
path: /timelord/
key: ${{ inputs.key }}
@@ -1,70 +0,0 @@
name: upload-docker-artifacts
description: |
Upload Docker build artifacts including binary and digest files.
Handles artifact naming and conditional digest uploads for registry publishing.
inputs:
slug:
description: Platform slug for artifact naming (e.g. linux-amd64, linux-arm64)
required: true
cpu_suffix:
description: CPU suffix for artifact naming (e.g. -haswell)
required: false
default: ""
artifact_suffix:
description: Suffix for binary artifacts (e.g. -maxperf)
required: false
default: ""
digest_suffix:
description: Suffix for digest artifacts (e.g. -maxperf)
required: false
default: ""
digest:
description: The digest of the built Docker image
required: true
outputs:
binary_artifact_name:
description: The name of the uploaded binary artifact
value: conduwuit${{ inputs.cpu_suffix }}-${{ inputs.slug }}${{ inputs.artifact_suffix }}
runs:
using: composite
steps:
- name: Export digest
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
shell: bash
run: |
mkdir -p /tmp/digests
digest="${{ inputs.digest }}"
echo "🔍 Build step digest output: '$digest'"
if [[ -z "$digest" ]]; then
echo "❌ ERROR: No digest found from build step"
exit 1
fi
digest_file="/tmp/digests/${digest#sha256:}"
echo "📁 Creating digest file: $digest_file"
touch "$digest_file"
echo "✅ Digest file created successfully"
echo "📋 Contents of /tmp/digests:"
ls -la /tmp/digests/
- name: Rename extracted binary
shell: bash
run: mv /tmp/binaries/sbin/conduwuit /tmp/binaries/conduwuit${{ inputs.cpu_suffix }}-${{ inputs.slug }}${{ inputs.artifact_suffix }}
- name: Upload binary artifact
uses: forgejo/upload-artifact@v4
with:
name: conduwuit${{ inputs.cpu_suffix }}-${{ inputs.slug }}${{ inputs.artifact_suffix }}
path: /tmp/binaries/conduwuit${{ inputs.cpu_suffix }}-${{ inputs.slug }}${{ inputs.artifact_suffix }}
if-no-files-found: error
- name: Upload digest
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: forgejo/upload-artifact@v4
with:
name: digests${{ inputs.digest_suffix }}-${{ inputs.slug }}${{ inputs.cpu_suffix }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 5
+9 -3
View File
@@ -6,11 +6,17 @@ concurrency:
on:
push:
branches:
- '**'
tags:
- "v*.*.*"
- 'v*'
paths:
- 'pkg/debian/**'
- 'src/**'
- 'Cargo.toml'
- 'Cargo.lock'
- '.forgejo/workflows/build-debian.yml'
workflow_dispatch:
schedule:
- cron: '30 0 * * *'
jobs:
build:
+6 -4
View File
@@ -21,7 +21,7 @@ jobs:
steps:
- name: Sync repository
uses: actions/checkout@v5
uses: https://github.com/actions/checkout@v4
with:
persist-credentials: false
fetch-depth: 0
@@ -51,11 +51,11 @@ jobs:
- name: Detect runner environment
id: runner-env
uses: https://git.tomfos.tr/actions/detect-versions@v1
uses: ./.forgejo/actions/detect-runner-os
- name: Setup Node.js
if: steps.runner-env.outputs.node_major == '' || steps.runner-env.outputs.node_major < '20'
uses: https://github.com/actions/setup-node@v5
uses: https://github.com/actions/setup-node@v4
with:
node-version: 22
@@ -63,7 +63,9 @@ jobs:
uses: actions/cache@v3
with:
path: ~/.npm
key: continuwuity-${{ steps.runner-env.outputs.slug }}-${{ steps.runner-env.outputs.arch }}-node-${{ steps.runner-env.outputs.node_version }}
key: ${{ steps.runner-env.outputs.slug }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ steps.runner-env.outputs.slug }}-node-
- name: Install dependencies
run: npm install --save-dev wrangler@latest
+2 -10
View File
@@ -4,14 +4,6 @@ on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
pull_request:
paths:
- ".forgejo/workflows/element.yml"
push:
branches:
- main
paths:
- ".forgejo/workflows/element.yml"
concurrency:
group: "element-${{ github.ref }}"
@@ -24,7 +16,7 @@ jobs:
steps:
- name: 📦 Setup Node.js
uses: https://github.com/actions/setup-node@v5
uses: https://github.com/actions/setup-node@v4
with:
node-version: "22"
@@ -109,7 +101,7 @@ jobs:
cat ./element-web/webapp/config.json
- name: 📤 Upload Artifact
uses: forgejo/upload-artifact@v4
uses: https://code.forgejo.org/actions/upload-artifact@v3
with:
name: element-web
path: ./element-web/webapp/
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
persist-credentials: false
+2 -2
View File
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
persist-credentials: false
@@ -47,7 +47,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
persist-credentials: false
+257 -130
View File
@@ -23,20 +23,55 @@ on:
- "renovate.json"
- "pkg/**"
- "docs/**"
tags:
- "v*.*.*"
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
env:
BUILTIN_REGISTRY: forgejo.ellis.link
BUILTIN_REGISTRY_ENABLED: "${{ ((vars.BUILTIN_REGISTRY_USER && secrets.BUILTIN_REGISTRY_PASSWORD) || (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)) && 'true' || 'false' }}"
IMAGE_PATH: forgejo.ellis.link/continuwuation/continuwuity
jobs:
build-release:
name: "Build ${{ matrix.slug }} (release)"
define-variables:
runs-on: ubuntu-latest
outputs:
images: ${{ steps.var.outputs.images }}
images_list: ${{ steps.var.outputs.images_list }}
build_matrix: ${{ steps.var.outputs.build_matrix }}
steps:
- name: Setting variables
uses: https://github.com/actions/github-script@v7
id: var
with:
script: |
const githubRepo = '${{ github.repository }}'.toLowerCase()
const repoId = githubRepo.split('/')[1]
core.setOutput('github_repository', githubRepo)
const builtinImage = '${{ env.BUILTIN_REGISTRY }}/' + githubRepo
let images = []
if (process.env.BUILTIN_REGISTRY_ENABLED === "true") {
images.push(builtinImage)
} else {
// Fallback to official registry for forks/PRs without credentials
images.push('forgejo.ellis.link/continuwuation/continuwuity')
}
core.setOutput('images', images.join("\n"))
core.setOutput('images_list', images.join(","))
const platforms = ['linux/amd64', 'linux/arm64']
core.setOutput('build_matrix', JSON.stringify({
platform: platforms,
target_cpu: ['base'],
include: platforms.map(platform => { return {
platform,
slug: platform.replace('/', '-')
}})
}))
build-image:
runs-on: dind
needs: define-variables
permissions:
contents: read
packages: write
@@ -44,28 +79,133 @@ jobs:
id-token: write
strategy:
matrix:
include:
- platform: "linux/amd64"
slug: "linux-amd64"
- platform: "linux/arm64"
slug: "linux-arm64"
{
"target_cpu": ["base"],
"profile": ["release"],
"include":
[
{ "platform": "linux/amd64", "slug": "linux-amd64" },
{ "platform": "linux/arm64", "slug": "linux-arm64" },
],
"platform": ["linux/amd64", "linux/arm64"],
}
steps:
- name: Echo strategy
run: echo '${{ toJSON(fromJSON(needs.define-variables.outputs.build_matrix)) }}'
- name: Echo matrix
run: echo '${{ toJSON(matrix) }}'
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Prepare Docker build environment
id: prepare
uses: ./.forgejo/actions/prepare-docker-build
- name: Install rust
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
id: rust-toolchain
uses: ./.forgejo/actions/rust-toolchain
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
platform: ${{ matrix.platform }}
slug: ${{ matrix.slug }}
target_cpu: ""
profile: "release"
images: ${{ env.IMAGE_PATH }}
registry_user: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }}
registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
# Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125)
driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }}
endpoint: ${{ env.BUILDKIT_ENDPOINT || '' }}
- name: Set up QEMU
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
uses: docker/setup-qemu-action@v3
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
- name: Login to builtin registry
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/login-action@v3
with:
registry: ${{ env.BUILTIN_REGISTRY }}
username: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }}
password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
# This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
- name: Extract metadata (labels, annotations) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{needs.define-variables.outputs.images}}
# default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
# It will not push images generated from a pull request
- name: Get short git commit SHA
id: sha
run: |
calculatedSha=$(git rev-parse --short ${{ github.sha }})
echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV
echo "Short SHA: $calculatedSha"
- name: Get Git commit timestamps
run: |
timestamp=$(git log -1 --pretty=%ct)
echo "TIMESTAMP=$timestamp" >> $GITHUB_ENV
echo "Commit timestamp: $timestamp"
- uses: ./.forgejo/actions/timelord
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
with:
key: timelord-v0
path: .
- name: Cache Rust registry
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
uses: actions/cache@v3
with:
path: |
.cargo/git
.cargo/git/checkouts
.cargo/registry
.cargo/registry/src
key: rust-registry-image-${{hashFiles('**/Cargo.lock') }}
- name: Cache cargo target
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
id: cache-cargo-target
uses: actions/cache@v3
with:
path: |
cargo-target-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}
key: cargo-target-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}-${{hashFiles('**/Cargo.lock') }}-${{steps.rust-toolchain.outputs.rustc_version}}
- name: Cache apt cache
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
id: cache-apt
uses: actions/cache@v3
with:
path: |
var-cache-apt-${{ matrix.slug }}
key: var-cache-apt-${{ matrix.slug }}
- name: Cache apt lib
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
id: cache-apt-lib
uses: actions/cache@v3
with:
path: |
var-lib-apt-${{ matrix.slug }}
key: var-lib-apt-${{ matrix.slug }}
- name: inject cache into docker
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
uses: https://github.com/reproducible-containers/buildkit-cache-dance@v3.3.0
with:
cache-map: |
{
".cargo/registry": "/usr/local/cargo/registry",
".cargo/git/db": "/usr/local/cargo/git/db",
"cargo-target-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}": {
"target": "/app/target",
"id": "cargo-target-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}"
},
"var-cache-apt-${{ matrix.slug }}": "/var/cache/apt",
"var-lib-apt-${{ matrix.slug }}": "/var/lib/apt"
}
skip-extraction: ${{ steps.cache.outputs.cache-hit }}
- name: Build and push Docker image by digest
id: build
uses: docker/build-push-action@v6
@@ -77,130 +217,117 @@ jobs:
GIT_COMMIT_HASH_SHORT=${{ env.COMMIT_SHORT_SHA }}
GIT_REMOTE_URL=${{github.event.repository.html_url }}
GIT_REMOTE_COMMIT_URL=${{github.event.head_commit.url }}
CARGO_INCREMENTAL=${{ env.BUILDKIT_ENDPOINT != '' && '1' || '0' }}
TARGET_CPU=
RUST_PROFILE=release
platforms: ${{ matrix.platform }}
labels: ${{ steps.prepare.outputs.metadata_labels }}
annotations: ${{ steps.prepare.outputs.metadata_annotations }}
labels: ${{ steps.meta.outputs.labels }}
annotations: ${{ steps.meta.outputs.annotations }}
cache-from: type=gha
# cache-to: type=gha,mode=max
sbom: true
outputs: |
${{ env.BUILTIN_REGISTRY_ENABLED == 'true' && format('type=image,"name={0}",push-by-digest=true,name-canonical=true,push=true', env.IMAGE_PATH) || format('type=image,"name={0}",push=false', env.IMAGE_PATH) }}
${{ env.BUILTIN_REGISTRY_ENABLED == 'true' && format('type=image,"name={0}",push-by-digest=true,name-canonical=true,push=true', needs.define-variables.outputs.images_list) || format('type=image,"name={0}",push=false', needs.define-variables.outputs.images_list) }}
type=local,dest=/tmp/binaries
env:
SOURCE_DATE_EPOCH: ${{ env.TIMESTAMP }}
- name: Upload Docker artifacts
uses: ./.forgejo/actions/upload-docker-artifacts
with:
slug: ${{ matrix.slug }}
cpu_suffix: ${{ steps.prepare.outputs.cpu_suffix }}
artifact_suffix: ""
digest_suffix: ""
digest: ${{ steps.build.outputs.digest }}
merge-release:
name: "Create Multi-arch Release Manifest"
# For publishing multi-platform manifests
- name: Export digest
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
# Binary extracted via local output for all builds
- name: Rename extracted binary
run: mv /tmp/binaries/sbin/conduwuit /tmp/binaries/conduwuit-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}
- name: Upload binary artifact
uses: forgejo/upload-artifact@v4
with:
name: conduwuit-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}
path: /tmp/binaries/conduwuit-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}
if-no-files-found: error
- name: Upload digest
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: forgejo/upload-artifact@v4
with:
name: digests-${{ matrix.slug }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 5
merge:
runs-on: dind
needs: build-release
needs: [define-variables, build-image]
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Download digests
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: forgejo/download-artifact@v4
with:
persist-credentials: false
- name: Create multi-platform manifest
uses: ./.forgejo/actions/create-docker-manifest
path: /tmp/digests
pattern: digests-*
merge-multiple: true
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
- name: Login to builtin registry
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/login-action@v3
with:
digest_pattern: "digests-linux-{amd64,arm64}"
tag_suffix: ""
images: ${{ env.IMAGE_PATH }}
registry_user: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }}
registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
registry: ${{ env.BUILTIN_REGISTRY }}
username: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }}
password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
build-maxperf:
name: "Build ${{ matrix.slug }} (max-perf)"
runs-on: dind
needs: build-release
permissions:
contents: read
packages: write
attestations: write
id-token: write
strategy:
matrix:
include:
- platform: "linux/amd64"
slug: "linux-amd64"
target_cpu: "haswell"
- platform: "linux/arm64"
slug: "linux-arm64"
target_cpu: ""
- name: Set up Docker Buildx
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/setup-buildx-action@v3
with:
# Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125)
driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }}
endpoint: ${{ env.BUILDKIT_ENDPOINT || '' }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Extract metadata (tags) for Docker
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
id: meta
uses: docker/metadata-action@v5
with:
persist-credentials: false
- name: Prepare max-perf Docker build environment
id: prepare
uses: ./.forgejo/actions/prepare-docker-build
with:
platform: ${{ matrix.platform }}
slug: ${{ matrix.slug }}
target_cpu: ${{ matrix.target_cpu }}
profile: "release-max-perf"
images: ${{ env.IMAGE_PATH }}
registry_user: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }}
registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
- name: Build and push max-perf Docker image by digest
id: build
uses: docker/build-push-action@v6
with:
context: .
file: "docker/Dockerfile"
build-args: |
GIT_COMMIT_HASH=${{ github.sha }}
GIT_COMMIT_HASH_SHORT=${{ env.COMMIT_SHORT_SHA }}
GIT_REMOTE_URL=${{github.event.repository.html_url }}
GIT_REMOTE_COMMIT_URL=${{github.event.head_commit.url }}
CARGO_INCREMENTAL=${{ env.BUILDKIT_ENDPOINT != '' && '1' || '0' }}
TARGET_CPU=${{ matrix.target_cpu }}
RUST_PROFILE=release-max-perf
platforms: ${{ matrix.platform }}
labels: ${{ steps.prepare.outputs.metadata_labels }}
annotations: ${{ steps.prepare.outputs.metadata_annotations }}
cache-from: type=gha
# cache-to: type=gha,mode=max
sbom: true
outputs: |
${{ env.BUILTIN_REGISTRY_ENABLED == 'true' && format('type=image,"name={0}",push-by-digest=true,name-canonical=true,push=true', env.IMAGE_PATH) || format('type=image,"name={0}",push=false', env.IMAGE_PATH) }}
type=local,dest=/tmp/binaries
tags: |
type=semver,pattern={{version}},prefix=v
type=semver,pattern={{major}}.{{minor}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.0.') }},prefix=v
type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }},prefix=v
type=ref,event=branch,prefix=${{ format('refs/heads/{0}', github.event.repository.default_branch) != github.ref && 'branch-' || '' }}
type=ref,event=pr
type=sha,format=long
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
images: ${{needs.define-variables.outputs.images}}
# default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509
env:
SOURCE_DATE_EPOCH: ${{ env.TIMESTAMP }}
- name: Upload max-perf Docker artifacts
uses: ./.forgejo/actions/upload-docker-artifacts
with:
slug: ${{ matrix.slug }}
cpu_suffix: ${{ steps.prepare.outputs.cpu_suffix }}
artifact_suffix: "-maxperf"
digest_suffix: "-maxperf"
digest: ${{ steps.build.outputs.digest }}
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
merge-maxperf:
name: "Create Max-Perf Manifest"
runs-on: dind
needs: build-maxperf
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
persist-credentials: false
- name: Create max-perf manifest
uses: ./.forgejo/actions/create-docker-manifest
with:
digest_pattern: "digests-maxperf-linux-{amd64-haswell,arm64}"
tag_suffix: "-maxperf"
images: ${{ env.IMAGE_PATH }}
registry_user: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }}
registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
- name: Create manifest list and push
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
working-directory: /tmp/digests
env:
IMAGES: ${{needs.define-variables.outputs.images}}
shell: bash
run: |
IFS=$'\n'
IMAGES_LIST=($IMAGES)
ANNOTATIONS_LIST=($DOCKER_METADATA_OUTPUT_ANNOTATIONS)
TAGS_LIST=($DOCKER_METADATA_OUTPUT_TAGS)
for REPO in "${IMAGES_LIST[@]}"; do
docker buildx imagetools create \
$(for tag in "${TAGS_LIST[@]}"; do echo "--tag"; echo "$tag"; done) \
$(for annotation in "${ANNOTATIONS_LIST[@]}"; do echo "--annotation"; echo "$annotation"; done) \
$(for reference in *; do printf "$REPO@sha256:%s\n" $reference; done)
done
- name: Inspect image
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
env:
IMAGES: ${{needs.define-variables.outputs.images}}
shell: bash
run: |
IMAGES_LIST=($IMAGES)
for REPO in "${IMAGES_LIST[@]}"; do
docker buildx imagetools inspect $REPO:${{ steps.meta.outputs.version }}
done
+21 -42
View File
@@ -1,7 +1,5 @@
name: Maintenance / Renovate
enable-email-notifications: true
on:
schedule:
# Run at 5am UTC daily to avoid late-night dev
@@ -12,10 +10,10 @@ on:
dryRun:
description: 'Dry run mode'
required: false
default: ''
default: null
type: choice
options:
- ''
- null
- 'extract'
- 'lookup'
- 'full'
@@ -25,7 +23,6 @@ on:
default: 'info'
type: choice
options:
- 'debug'
- 'info'
- 'warning'
- 'critical'
@@ -43,11 +40,11 @@ jobs:
name: Renovate
runs-on: ubuntu-latest
container:
image: ghcr.io/renovatebot/renovate:41.125.2@sha256:880470695f96411e67d4ada54ea11998a6f7ddf6fddacf7d2eeae7c49e89d420
image: ghcr.io/renovatebot/renovate:41
options: --tmpfs /tmp:exec
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
show-progress: false
@@ -55,34 +52,25 @@ jobs:
run: /usr/local/renovate/node -e 'console.log(`node heap limit = ${require("v8").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'
- name: Restore renovate repo cache
uses: actions/cache/restore@v4
uses: https://github.com/actions/cache@v4
with:
path: |
/tmp/renovate/cache/renovate/repository
key: renovate-repo-cache-${{ github.run_id }}
key: repo-cache-${{ github.run_id }}
restore-keys: |
renovate-repo-cache-
repo-cache-
- name: Restore renovate package cache
uses: actions/cache/restore@v4
uses: https://github.com/actions/cache@v4
with:
path: |
/tmp/renovate/cache/renovate/renovate-cache-sqlite
key: renovate-package-cache-${{ github.run_id }}
key: package-cache-${{ github.run_id }}
restore-keys: |
renovate-package-cache-
- name: Restore renovate OSV cache
uses: actions/cache/restore@v4
with:
path: |
/tmp/osv
key: renovate-osv-cache-${{ github.run_id }}
restore-keys: |
renovate-osv-cache-
package-cache-
- name: Self-hosted Renovate
run: renovate
uses: https://github.com/renovatebot/github-action@v43.0.11
env:
LOG_LEVEL: ${{ inputs.logLevel || 'info' }}
RENOVATE_DRY_RUN: ${{ inputs.dryRun || 'false' }}
@@ -96,37 +84,28 @@ jobs:
RENOVATE_REQUIRE_CONFIG: 'required'
RENOVATE_ONBOARDING: 'false'
RENOVATE_INHERIT_CONFIG: 'true'
RENOVATE_PR_COMMITS_PER_RUN_LIMIT: 3
RENOVATE_GITHUB_TOKEN_WARN: 'false'
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
GITHUB_COM_TOKEN: ${{ secrets.GH_PUBLIC_RO || secrets.GH_TOKEN }}
GITHUB_COM_TOKEN: ${{ secrets.GH_PUBLIC_RO }}
RENOVATE_REPOSITORY_CACHE: 'enabled'
RENOVATE_X_SQLITE_PACKAGE_CACHE: 'true'
OSV_OFFLINE_ROOT_DIR: /tmp/osv
RENOVATE_X_SQLITE_PACKAGE_CACHE: true
- name: Save renovate repo cache
if: always()
uses:
actions/cache/save@v4
if: always() && env.RENOVATE_DRY_RUN != 'full'
uses: https://github.com/actions/cache@v4
with:
path: |
/tmp/renovate/cache/renovate/repository
key: renovate-repo-cache-${{ github.run_id }}
key: repo-cache-${{ github.run_id }}
- name: Save renovate package cache
if: always()
uses: actions/cache/save@v4
if: always() && env.RENOVATE_DRY_RUN != 'full'
uses: https://github.com/actions/cache@v4
with:
path: |
/tmp/renovate/cache/renovate/renovate-cache-sqlite
key: renovate-package-cache-${{ github.run_id }}
- name: Save renovate OSV cache
if: always()
uses: actions/cache/save@v4
with:
path: |
/tmp/osv
key: renovate-osv-cache-${{ github.run_id }}
key: package-cache-${{ github.run_id }}
-107
View File
@@ -1,107 +0,0 @@
name: Update flake hashes
on:
workflow_dispatch:
pull_request:
paths:
- "Cargo.lock"
- "Cargo.toml"
- "rust-toolchain.toml"
jobs:
update-flake-hashes:
runs-on: ubuntu-latest
steps:
- uses: https://code.forgejo.org/actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
fetch-depth: 1
fetch-tags: false
fetch-single-branch: true
submodules: false
persist-credentials: false
- uses: https://github.com/cachix/install-nix-action@a809471b5c7c913aa67bec8f459a11a0decc3fce # v31.6.2
with:
nix_path: nixpkgs=channel:nixos-unstable
# We can skip getting a toolchain hash if this was ran as a dispatch with the intent
# to update just the rocksdb hash. If this was ran as a dispatch and the toolchain
# files are changed, we still update them, as well as the rocksdb import.
- name: Detect changed files
id: changes
run: |
git fetch origin ${{ github.base_ref }} --depth=1 || true
if [ -n "${{ github.event.pull_request.base.sha }}" ]; then
base=${{ github.event.pull_request.base.sha }}
else
base=$(git rev-parse HEAD~1)
fi
echo "Base: $base"
echo "HEAD: $(git rev-parse HEAD)"
git diff --name-only $base HEAD > changed_files.txt
echo "files=$(cat changed_files.txt)" >> $FORGEJO_OUTPUT
- name: Get new toolchain hash
if: contains(steps.changes.outputs.files, 'Cargo.toml') || contains(steps.changes.outputs.files, 'Cargo.lock') || contains(steps.changes.outputs.files, 'rust-toolchain.toml')
run: |
# Set the current sha256 to an empty hash to make `nix build` calculate a new one
awk '/fromToolchainFile *\{/{found=1; print; next} found && /sha256 =/{sub(/sha256 = .*/, "sha256 = pkgsHost.lib.fakeSha256;"); found=0} 1' flake.nix > temp.nix && mv temp.nix flake.nix
# Build continuwuity and filter for the new hash
# We do `|| true` because we want this to fail without stopping the workflow
nix build .#default 2>&1 | tee >(grep 'got:' | awk '{print $2}' > new_toolchain_hash.txt) || true
# Place the new hash in place of the empty hash
new_hash=$(cat new_toolchain_hash.txt)
sed -i "s|pkgsHost.lib.fakeSha256|\"$new_hash\"|" flake.nix
echo "New hash:"
awk -F'"' '/fromToolchainFile/{found=1; next} found && /sha256 =/{print $2; found=0}' flake.nix
echo "Expected new hash:"
cat new_toolchain_hash.txt
rm new_toolchain_hash.txt
- name: Get new rocksdb hash
run: |
# Set the current sha256 to an empty hash to make `nix build` calculate a new one
awk '/repo = "rocksdb";/{found=1; print; next} found && /sha256 =/{sub(/sha256 = .*/, "sha256 = pkgsHost.lib.fakeSha256;"); found=0} 1' flake.nix > temp.nix && mv temp.nix flake.nix
# Build continuwuity and filter for the new hash
# We do `|| true` because we want this to fail without stopping the workflow
nix build .#default 2>&1 | tee >(grep 'got:' | awk '{print $2}' > new_rocksdb_hash.txt) || true
# Place the new hash in place of the empty hash
new_hash=$(cat new_rocksdb_hash.txt)
sed -i "s|pkgsHost.lib.fakeSha256|\"$new_hash\"|" flake.nix
echo "New hash:"
awk -F'"' '/repo = "rocksdb";/{found=1; next} found && /sha256 =/{print $2; found=0}' flake.nix
echo "Expected new hash:"
cat new_rocksdb_hash.txt
rm new_rocksdb_hash.txt
- name: Show diff
run: git diff flake.nix
- name: Push changes
run: |
set -euo pipefail
if git diff --quiet --exit-code; then
echo "No changes to commit."
exit 0
fi
git config user.email "renovate@mail.ellis.link"
git config user.name "renovate"
REF="${{ github.head_ref }}"
git fetch origin "$REF"
git checkout "$REF"
git commit -a -m "chore(Nix): Updated flake hashes"
git push origin HEAD:refs/heads/"$REF"
-3
View File
@@ -13,9 +13,6 @@ extend-ignore-re = [
"[0-9+][A-Za-z0-9+]{30,}[a-z0-9+]",
"\\$[A-Z0-9+][A-Za-z0-9+]{6,}[a-z0-9+]",
"\\b[a-z0-9+/=][A-Za-z0-9+/=]{7,}[a-z0-9+/=][A-Z]\\b",
# In the renovate config
".ontainer"
]
[default.extend-words]
Generated
+325 -340
View File
File diff suppressed because it is too large Load Diff
+13 -11
View File
@@ -21,7 +21,7 @@ license = "Apache-2.0"
readme = "README.md"
repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
rust-version = "1.86.0"
version = "0.5.0-rc.8"
version = "0.5.0-rc.7"
[workspace.metadata.crane]
name = "conduwuit"
@@ -45,7 +45,7 @@ version = "0.3"
features = ["ffi", "std", "union"]
[workspace.dependencies.const-str]
version = "0.7.0"
version = "0.6.2"
[workspace.dependencies.ctor]
version = "0.5.0"
@@ -166,8 +166,8 @@ default-features = false
features = ["raw_value"]
# Used for appservice registration files
[workspace.dependencies.serde_yml]
version = "0.0.12"
[workspace.dependencies.serde_yaml]
version = "0.9.34"
# Used to load forbidden room/user regex from config
[workspace.dependencies.serde_regex]
@@ -351,7 +351,8 @@ version = "0.1.2"
# Used for matrix spec type definitions and helpers
[workspace.dependencies.ruma]
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
rev = "d18823471ab3c09e77ff03eea346d4c07e572654"
#branch = "conduwuit-changes"
rev = "8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd"
features = [
"compat",
"rand",
@@ -381,18 +382,16 @@ features = [
"unstable-msc4095",
"unstable-msc4121",
"unstable-msc4125",
"unstable-msc4155",
"unstable-msc4186",
"unstable-msc4203", # sending to-device events to appservices
"unstable-msc4210", # remove legacy mentions
"unstable-extensible-events",
"unstable-pdu",
"unstable-msc4155"
]
[workspace.dependencies.rust-rocksdb]
git = "https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1"
rev = "61d9d23872197e9ace4a477f2617d5c9f50ecb23"
rev = "99b0319416b64830dd6f8943e1f65e15aeef18bc"
default-features = false
features = [
"multi-threaded-cf",
@@ -555,9 +554,6 @@ version = "0.11.5"
default-features = false
features = ["sync", "tls-rustls"]
[workspace.dependencies.resolv-conf]
version = "0.7.5"
#
# Patches
#
@@ -602,6 +598,12 @@ rev = "9c8e51510c35077df888ee72a36b4b05637147da"
git = "https://forgejo.ellis.link/continuwuation/hyper-util"
rev = "e4ae7628fe4fcdacef9788c4c8415317a4489941"
# Allows no-aaaa option in resolv.conf
# Use 1-indexed line numbers when displaying parse error messages
[patch.crates-io.resolv-conf]
git = "https://forgejo.ellis.link/continuwuation/resolv-conf"
rev = "56251316cc4127bcbf36e68ce5e2093f4d33e227"
#
# Our crates
#
+6 -15
View File
@@ -48,13 +48,11 @@ EOF
# Developer tool versions
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
ENV BINSTALL_VERSION=1.15.5
ENV BINSTALL_VERSION=1.13.0
# renovate: datasource=github-releases depName=psastras/sbom-rs
ENV CARGO_SBOM_VERSION=0.9.1
# renovate: datasource=crate depName=lddtree
ENV LDDTREE_VERSION=0.3.7
# renovate: datasource=crate depName=timelord-cli
ENV TIMELORD_VERSION=3.0.1
# Install unpackaged tools
RUN <<EOF
@@ -62,7 +60,6 @@ RUN <<EOF
curl --retry 5 -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
cargo binstall --no-confirm cargo-sbom --version $CARGO_SBOM_VERSION
cargo binstall --no-confirm lddtree --version $LDDTREE_VERSION
cargo binstall --no-confirm timelord-cli --version $TIMELORD_VERSION
EOF
# Set up xx (cross-compilation scripts)
@@ -84,9 +81,8 @@ RUN rustc --version \
&& xx-cargo --setup-target-triple
# Build binary
# Configure incremental compilation based on build context
ARG CARGO_INCREMENTAL=0
RUN echo "CARGO_INCREMENTAL=${CARGO_INCREMENTAL}" >> /etc/environment
# We disable incremental compilation to save disk space, as it only produces a minimal speedup for this case.
RUN echo "CARGO_INCREMENTAL=0" >> /etc/environment
# Configure pkg-config
RUN <<EOF
@@ -137,11 +133,6 @@ FROM toolchain AS builder
# Get source
COPY . .
# Restore timestamps from timelord cache if available
RUN --mount=type=cache,target=/timelord/ \
echo "Restoring timestamps from timelord cache"; \
timelord sync --source-dir /app --cache-dir /timelord;
ARG TARGETPLATFORM
# Verify environment configuration
@@ -166,7 +157,7 @@ ARG RUST_PROFILE=release
# Build the binary
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git/db \
--mount=type=cache,target=/app/target,id=continuwuity-cargo-target-${TARGET_CPU}-${TARGETPLATFORM}-${RUST_PROFILE} \
--mount=type=cache,target=/app/target,id=cargo-target-${TARGET_CPU}-${TARGETPLATFORM}-${RUST_PROFILE} \
bash <<'EOF'
set -o allexport
set -o xtrace
@@ -181,8 +172,8 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
jq -r ".packages[] | select(.name == \"$PACKAGE\") | .targets[] | select( .kind | map(. == \"bin\") | any ) | .name"))
for BINARY in "${BINARIES[@]}"; do
echo $BINARY
xx-verify $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE}/$BINARY
cp $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE}/$BINARY /out/sbin/$BINARY
xx-verify $TARGET_DIR/$(xx-cargo --print-target-triple)/release/$BINARY
cp $TARGET_DIR/$(xx-cargo --print-target-triple)/release/$BINARY /out/sbin/$BINARY
done
EOF
+2 -2
View File
@@ -18,7 +18,7 @@ RUN --mount=type=cache,target=/etc/apk/cache apk add \
# Developer tool versions
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
ENV BINSTALL_VERSION=1.15.5
ENV BINSTALL_VERSION=1.13.0
# renovate: datasource=github-releases depName=psastras/sbom-rs
ENV CARGO_SBOM_VERSION=0.9.1
# renovate: datasource=crate depName=lddtree
@@ -122,7 +122,7 @@ ARG RUST_PROFILE=release
# Build the binary
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git/db \
--mount=type=cache,target=/app/target,id=continuwuity-cargo-target-${TARGET_CPU}-${TARGETPLATFORM}-musl-${RUST_PROFILE} \
--mount=type=cache,target=/app/target,id=cargo-target-${TARGET_CPU}-${TARGETPLATFORM}-musl-${RUST_PROFILE} \
bash <<'EOF'
set -o allexport
set -o xtrace
Generated
+126 -82
View File
@@ -10,11 +10,11 @@
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1757683818,
"narHash": "sha256-q7q0pWT+wu5AUU1Qlbwq8Mqb+AzHKhaMCVUq/HNZfo8=",
"lastModified": 1751403276,
"narHash": "sha256-V0EPQNsQko1a8OqIWc2lLviLnMpR1m08Ej00z5RVTfs=",
"owner": "zhaofengli",
"repo": "attic",
"rev": "7c5d79ad62cda340cb8c80c99b921b7b7ffacf69",
"rev": "896ad88fa57ad5dbcd267c0ac51f1b71ccfcb4dd",
"type": "github"
},
"original": {
@@ -29,14 +29,14 @@
"devenv": "devenv",
"flake-compat": "flake-compat_2",
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs_2"
"nixpkgs": "nixpkgs_4"
},
"locked": {
"lastModified": 1756385612,
"narHash": "sha256-+NU5MMhuPHHRyvZZWNFG7zt+leRSPsJu1MwhOUzkPUk=",
"lastModified": 1748883665,
"narHash": "sha256-R0W7uAg+BLoHjMRMQ8+oiSbTq8nkGz5RDpQ+ZfxxP3A=",
"owner": "cachix",
"repo": "cachix",
"rev": "dc24688cd67518c3711d511fa369c0f5a131063a",
"rev": "f707778d902af4d62d8dd92c269f8e70de09acbe",
"type": "github"
},
"original": {
@@ -58,21 +58,16 @@
],
"git-hooks": [
"cachix",
"devenv",
"git-hooks"
"devenv"
],
"nixpkgs": [
"cachix",
"devenv",
"nixpkgs"
]
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1748883665,
"narHash": "sha256-R0W7uAg+BLoHjMRMQ8+oiSbTq8nkGz5RDpQ+ZfxxP3A=",
"lastModified": 1744206633,
"narHash": "sha256-pb5aYkE8FOoa4n123slgHiOf1UbNSnKe5pEZC+xXD5g=",
"owner": "cachix",
"repo": "cachix",
"rev": "f707778d902af4d62d8dd92c269f8e70de09acbe",
"rev": "8a60090640b96f9df95d1ab99e5763a586be1404",
"type": "github"
},
"original": {
@@ -83,12 +78,18 @@
}
},
"crane": {
"inputs": {
"nixpkgs": [
"attic",
"nixpkgs"
]
},
"locked": {
"lastModified": 1751562746,
"narHash": "sha256-smpugNIkmDeicNz301Ll1bD7nFOty97T79m4GUMUczA=",
"lastModified": 1722960479,
"narHash": "sha256-NhCkJJQhD5GUib8zN9JrmYGMwt4lCRp6ZVNzIiYCl0Y=",
"owner": "ipetkov",
"repo": "crane",
"rev": "aed2020fd3dc26e1e857d4107a5a67a33ab6c1fd",
"rev": "4c6c77920b8d44cd6660c1621dea6b3fc4b4c4f4",
"type": "github"
},
"original": {
@@ -99,11 +100,11 @@
},
"crane_2": {
"locked": {
"lastModified": 1757183466,
"narHash": "sha256-kTdCCMuRE+/HNHES5JYsbRHmgtr+l9mOtf5dpcMppVc=",
"lastModified": 1750266157,
"narHash": "sha256-tL42YoNg9y30u7zAqtoGDNdTyXTi8EALDeCB13FtbQA=",
"owner": "ipetkov",
"repo": "crane",
"rev": "d599ae4847e7f87603e7082d73ca673aa93c916d",
"rev": "e37c943371b73ed87faf33f7583860f81f1d5a48",
"type": "github"
},
"original": {
@@ -131,11 +132,11 @@
]
},
"locked": {
"lastModified": 1754404745,
"narHash": "sha256-BdbW/iTImczgcuATgQIa9sPGuYIBxVq2xqcvICsa2AQ=",
"lastModified": 1748273445,
"narHash": "sha256-5V0dzpNgQM0CHDsMzh+ludYeu1S+Y+IMjbaskSSdFh0=",
"owner": "cachix",
"repo": "devenv",
"rev": "6563b21105168f90394dfaf58284b078af2d7275",
"rev": "668a50d8b7bdb19a0131f53c9f6c25c9071e1ffb",
"type": "github"
},
"original": {
@@ -152,11 +153,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1758004879,
"narHash": "sha256-kV7tQzcNbmo58wg2uE2MQ/etaTx+PxBMHeNrLP8vOgk=",
"lastModified": 1755585599,
"narHash": "sha256-tl/0cnsqB/Yt7DbaGMel2RLa7QG5elA8lkaOXli6VdY=",
"owner": "nix-community",
"repo": "fenix",
"rev": "07e5ce53dd020e6b337fdddc934561bee0698fa2",
"rev": "6ed03ef4c8ec36d193c18e06b9ecddde78fb7e42",
"type": "github"
},
"original": {
@@ -169,11 +170,11 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1747046372,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
@@ -223,11 +224,11 @@
]
},
"locked": {
"lastModified": 1751413152,
"narHash": "sha256-Tyw1RjYEsp5scoigs1384gIg6e0GoBVjms4aXFfRssQ=",
"lastModified": 1722555600,
"narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "77826244401ea9de6e3bac47c2db46005e1f30b5",
"rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
"type": "github"
},
"original": {
@@ -246,11 +247,11 @@
]
},
"locked": {
"lastModified": 1733312601,
"narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
"lastModified": 1712014858,
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
"type": "github"
},
"original": {
@@ -291,11 +292,11 @@
]
},
"locked": {
"lastModified": 1750779888,
"narHash": "sha256-wibppH3g/E2lxU43ZQHC5yA/7kIKLGxVEnsnVK1BtRg=",
"lastModified": 1747372754,
"narHash": "sha256-2Y53NGIX2vxfie1rOW0Qb86vjRZ7ngizoo+bnXU9D9k=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "16ec914f6fb6f599ce988427d9d94efddf25fe6d",
"rev": "80479b6ec16fefd9c1db3ea13aeb038c60530f46",
"type": "github"
},
"original": {
@@ -326,24 +327,31 @@
"type": "github"
}
},
"libgit2": {
"flake": false,
"locked": {
"lastModified": 1697646580,
"narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=",
"owner": "libgit2",
"repo": "libgit2",
"rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5",
"type": "github"
},
"original": {
"owner": "libgit2",
"repo": "libgit2",
"type": "github"
}
},
"nix": {
"inputs": {
"flake-compat": [
"cachix",
"devenv",
"flake-compat"
"devenv"
],
"flake-parts": "flake-parts_2",
"git-hooks-nix": [
"cachix",
"devenv",
"git-hooks"
],
"nixpkgs": [
"cachix",
"devenv",
"nixpkgs"
],
"libgit2": "libgit2",
"nixpkgs": "nixpkgs_3",
"nixpkgs-23-11": [
"cachix",
"devenv"
@@ -351,30 +359,34 @@
"nixpkgs-regression": [
"cachix",
"devenv"
],
"pre-commit-hooks": [
"cachix",
"devenv"
]
},
"locked": {
"lastModified": 1752773918,
"narHash": "sha256-dOi/M6yNeuJlj88exI+7k154z+hAhFcuB8tZktiW7rg=",
"owner": "cachix",
"lastModified": 1745930071,
"narHash": "sha256-bYyjarS3qSNqxfgc89IoVz8cAFDkF9yPE63EJr+h50s=",
"owner": "domenkozar",
"repo": "nix",
"rev": "031c3cf42d2e9391eee373507d8c12e0f9606779",
"rev": "b455edf3505f1bf0172b39a735caef94687d0d9c",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "devenv-2.30",
"owner": "domenkozar",
"ref": "devenv-2.24",
"repo": "nix",
"type": "github"
}
},
"nix-filter": {
"locked": {
"lastModified": 1757882181,
"narHash": "sha256-+cCxYIh2UNalTz364p+QYmWHs0P+6wDhiWR4jDIKQIU=",
"lastModified": 1731533336,
"narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=",
"owner": "numtide",
"repo": "nix-filter",
"rev": "59c44d1909c72441144b93cf0f054be7fe764de5",
"rev": "f7653272fd234696ae94229839a99b73c9ab7de0",
"type": "github"
},
"original": {
@@ -392,11 +404,11 @@
]
},
"locked": {
"lastModified": 1737420293,
"narHash": "sha256-F1G5ifvqTpJq7fdkT34e/Jy9VCyzd5XfJ9TO8fHhJWE=",
"lastModified": 1729742964,
"narHash": "sha256-B4mzTcQ0FZHdpeWcpDYPERtyjJd/NIuaQ9+BV1h+MpA=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "f4158fa080ef4503c8f4c820967d946c2af31ec9",
"rev": "e04df33f62cdcf93d73e9a04142464753a16db67",
"type": "github"
},
"original": {
@@ -407,11 +419,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1751949589,
"narHash": "sha256-mgFxAPLWw0Kq+C8P3dRrZrOYEQXOtKuYVlo9xvPntt8=",
"lastModified": 1726042813,
"narHash": "sha256-LnNKCCxnwgF+575y0pxUdlGZBO/ru1CtGHIqQVfvjlA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9b008d60392981ad674e04016d25619281550a9d",
"rev": "159be5db480d1df880a0135ca0bfed84c2f88353",
"type": "github"
},
"original": {
@@ -423,27 +435,27 @@
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1751741127,
"narHash": "sha256-t75Shs76NgxjZSgvvZZ9qOmz5zuBE8buUaYD28BMTxg=",
"lastModified": 1724316499,
"narHash": "sha256-Qb9MhKBUTCfWg/wqqaxt89Xfi6qTD3XpTzQ9eXi3JmE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "29e290002bfff26af1db6f64d070698019460302",
"rev": "797f7dc49e0bc7fab4b57c021cdf68f595e47841",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.05",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1754214453,
"narHash": "sha256-Q/I2xJn/j1wpkGhWkQnm20nShYnG7TI99foDBpXm1SY=",
"lastModified": 1733212471,
"narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5b09dc45f24cf32316283e62aec81ffee3c3e376",
"rev": "55d15ad12a74eb7d4646254e13638ad0c4128776",
"type": "github"
},
"original": {
@@ -455,11 +467,43 @@
},
"nixpkgs_3": {
"locked": {
"lastModified": 1758029226,
"narHash": "sha256-TjqVmbpoCqWywY9xIZLTf6ANFvDCXdctCjoYuYPYdMI=",
"lastModified": 1717432640,
"narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "08b8f92ac6354983f5382124fef6006cade4a1c1",
"rev": "88269ab3044128b7c2f4c7d68448b2fb50456870",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "release-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1748190013,
"narHash": "sha256-R5HJFflOfsP5FBtk+zE8FpL8uqE7n62jqOsADvVshhE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "62b852f6c6742134ade1abdd2a21685fd617a291",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_5": {
"locked": {
"lastModified": 1751498133,
"narHash": "sha256-QWJ+NQbMU+NcU2xiyo7SNox1fAuwksGlQhpzBl76g1I=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d55716bb59b91ae9d1ced4b1ccdea7a442ecbfdb",
"type": "github"
},
"original": {
@@ -478,17 +522,17 @@
"flake-compat": "flake-compat_3",
"flake-utils": "flake-utils",
"nix-filter": "nix-filter",
"nixpkgs": "nixpkgs_3"
"nixpkgs": "nixpkgs_5"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1757362324,
"narHash": "sha256-/PAhxheUq4WBrW5i/JHzcCqK5fGWwLKdH6/Lu1tyS18=",
"lastModified": 1755504847,
"narHash": "sha256-VX0B9hwhJypCGqncVVLC+SmeMVd/GAYbJZ0MiiUn2Pk=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "9edc9cbe5d8e832b5864e09854fa94861697d2fd",
"rev": "a905e3b21b144d77e1b304e49f3264f6f8d4db75",
"type": "github"
},
"original": {
+15 -44
View File
@@ -1,12 +1,10 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended", "replacements:all"],
"osvVulnerabilityAlerts": true,
"extends": ["config:recommended"],
"lockFileMaintenance": {
"enabled": true,
"schedule": ["at any time"]
},
"platformAutomerge": true,
"nix": {
"enabled": true
},
@@ -31,15 +29,10 @@
},
"packageRules": [
{
"description": "Batch patch-level Rust dependency updates",
"matchManagers": ["cargo"],
"matchUpdateTypes": ["patch"],
"groupName": "rust-patch-updates"
},
{
"description": "Limit concurrent Cargo PRs",
"matchManagers": ["cargo"],
"prConcurrentLimit": 5
"description": "Batch minor and patch GitHub Actions updates",
"matchManagers": ["github-actions"],
"matchUpdateTypes": ["minor", "patch"],
"groupName": "github-actions-non-major"
},
{
"description": "Group Rust toolchain updates into a single PR",
@@ -47,42 +40,20 @@
"matchPackageNames": ["rust", "rustc", "cargo"],
"groupName": "rust-toolchain"
},
{
"description": "Batch minor and patch GitHub Actions updates",
"matchManagers": ["github-actions"],
"matchUpdateTypes": ["minor", "patch"],
"groupName": "github-actions-non-major"
},
{
"description": "Pin forgejo artifact actions to prevent breaking changes",
"matchManagers": ["github-actions"],
"matchPackageNames": ["forgejo/upload-artifact", "forgejo/download-artifact"],
"enabled": false
},
{
"description": "Auto-merge renovatebot docker image updates",
"matchDatasources": ["docker"],
"matchPackageNames": ["ghcr.io/renovatebot/renovate"],
"automerge": true,
"automergeStrategy": "fast-forward"
},
{
"description": "Group lockfile updates into a single PR",
"matchUpdateTypes": ["lockFileMaintenance"],
"groupName": "lockfile-maintenance"
},
{
"description": "Batch patch-level Rust dependency updates",
"matchManagers": ["cargo"],
"matchUpdateTypes": ["patch"],
"groupName": "rust-patch-updates"
},
{
"matchManagers": ["cargo"],
"prConcurrentLimit": 5
}
],
"customManagers": [
{
"customType": "regex",
"description": "Update _VERSION variables in Dockerfiles",
"managerFilePatterns": [
"/(^|/)([Dd]ocker|[Cc]ontainer)file[^/]*$/",
"/(^|/|\\.)([Dd]ocker|[Cc]ontainer)file$/"
],
"matchStrings": [
"# renovate: datasource=(?<datasource>[a-z-.]+?) depName=(?<depName>[^\\s]+?)(?: (lookupName|packageName)=(?<packageName>[^\\s]+?))?(?: versioning=(?<versioning>[^\\s]+?))?(?: extractVersion=(?<extractVersion>[^\\s]+?))?(?: registryUrl=(?<registryUrl>[^\\s]+?))?\\s+(?:ENV|ARG)\\s+[A-Za-z0-9_]+?_VERSION[ =][\"']?(?<currentValue>.+?)[\"']?\\s"
]
}
]
}
+1 -1
View File
@@ -85,7 +85,7 @@ futures.workspace = true
log.workspace = true
ruma.workspace = true
serde_json.workspace = true
serde_yml.workspace = true
serde_yaml.workspace = true
tokio.workspace = true
tracing-subscriber.workspace = true
tracing.workspace = true
+2 -2
View File
@@ -16,7 +16,7 @@ pub(super) async fn register(&self) -> Result {
let range = 1..checked!(body_len - 1)?;
let appservice_config_body = body[range].join("\n");
let parsed_config = serde_yml::from_str(&appservice_config_body);
let parsed_config = serde_yaml::from_str(&appservice_config_body);
match parsed_config {
| Err(e) => return Err!("Could not parse appservice config as YAML: {e}"),
| Ok(registration) => match self
@@ -57,7 +57,7 @@ pub(super) async fn show_appservice_config(&self, appservice_identifier: String)
{
| None => return Err!("Appservice does not exist."),
| Some(config) => {
let config_str = serde_yml::to_string(&config)?;
let config_str = serde_yaml::to_string(&config)?;
write!(self, "Config for {appservice_identifier}:\n\n```yaml\n{config_str}\n```")
},
}
+1 -3
View File
@@ -632,7 +632,6 @@ pub(super) async fn force_set_room_state_from_server(
.add_pdu_outlier(&event_id, &value);
}
info!("Resolving new room state");
let new_room_state = self
.services
.rooms
@@ -640,7 +639,7 @@ pub(super) async fn force_set_room_state_from_server(
.resolve_state(&room_id, &room_version, state)
.await?;
info!("Compressing new room state");
info!("Forcing new room state");
let HashSetCompressStateEvent {
shortstatehash: short_state_hash,
added,
@@ -654,7 +653,6 @@ pub(super) async fn force_set_room_state_from_server(
let state_lock = self.services.rooms.state.mutex.lock(&*room_id).await;
info!("Forcing new room state");
self.services
.rooms
.state
+1 -1
View File
@@ -360,7 +360,7 @@ pub(super) async fn get_remote_thumbnail(
) -> Result {
let mxc: Mxc<'_> = mxc.as_str().try_into()?;
let timeout = Duration::from_millis(timeout.into());
let dim = Dim::new(width, height, None);
let dim = Dim::new(width, height, None, None);
let mut result = self
.services
.media
+7 -25
View File
@@ -179,11 +179,7 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
.await
.is_ok_and(is_equal_to!(1))
{
self.services
.admin
.make_user_admin(&user_id)
.boxed()
.await?;
self.services.admin.make_user_admin(&user_id).await?;
warn!("Granting {user_id} admin privileges as the first user");
}
} else {
@@ -221,9 +217,7 @@ pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) ->
.collect()
.await;
full_user_deactivate(self.services, &user_id, &all_joined_rooms)
.boxed()
.await?;
full_user_deactivate(self.services, &user_id, &all_joined_rooms).await?;
update_displayname(self.services, &user_id, None, &all_joined_rooms).await;
update_avatar_url(self.services, &user_id, None, None, &all_joined_rooms).await;
leave_all_rooms(self.services, &user_id).await;
@@ -382,9 +376,7 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
.collect()
.await;
full_user_deactivate(self.services, &user_id, &all_joined_rooms)
.boxed()
.await?;
full_user_deactivate(self.services, &user_id, &all_joined_rooms).await?;
update_displayname(self.services, &user_id, None, &all_joined_rooms).await;
update_avatar_url(self.services, &user_id, None, None, &all_joined_rooms)
.await;
@@ -764,7 +756,7 @@ pub(super) async fn force_demote(&self, user_id: String, room_id: OwnedRoomOrAli
.build_and_append_pdu(
PduBuilder::state(String::new(), &power_levels_content),
&user_id,
Some(&room_id),
&room_id,
&state_lock,
)
.await?;
@@ -784,11 +776,7 @@ pub(super) async fn make_user_admin(&self, user_id: String) -> Result {
"Parsed user_id must be a local user"
);
self.services
.admin
.make_user_admin(&user_id)
.boxed()
.await?;
self.services.admin.make_user_admin(&user_id).await?;
self.write_str(&format!("{user_id} has been granted admin privileges.",))
.await
@@ -913,13 +901,7 @@ pub(super) async fn redact_event(&self, event_id: OwnedEventId) -> Result {
);
let redaction_event_id = {
let state_lock = self
.services
.rooms
.state
.mutex
.lock(&event.room_id_or_hash())
.await;
let state_lock = self.services.rooms.state.mutex.lock(event.room_id()).await;
self.services
.rooms
@@ -933,7 +915,7 @@ pub(super) async fn redact_event(&self, event_id: OwnedEventId) -> Result {
})
},
event.sender(),
Some(&event.room_id_or_hash()),
event.room_id(),
&state_lock,
)
.await?
+34 -29
View File
@@ -405,36 +405,41 @@ pub(crate) async fn register_route(
)
.await?;
// Generate new device id if the user didn't specify one
let no_device = body.inhibit_login
if (!is_guest && body.inhibit_login)
|| body
.appservice_info
.as_ref()
.is_some_and(|aps| aps.registration.device_management);
let (token, device) = if !no_device {
// Don't create a device for inhibited logins
let device_id = if is_guest { None } else { body.device_id.clone() }
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
.is_some_and(|appservice| appservice.registration.device_management)
{
return Ok(register::v3::Response {
access_token: None,
user_id,
device_id: None,
refresh_token: None,
expires_in: None,
});
}
// Generate new token for the device
let new_token = utils::random_string(TOKEN_LENGTH);
// Generate new device id if the user didn't specify one
let device_id = if is_guest { None } else { body.device_id.clone() }
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
// Create device for this account
services
.users
.create_device(
&user_id,
&device_id,
&new_token,
body.initial_device_display_name.clone(),
Some(client.to_string()),
)
.await?;
debug_info!(%user_id, %device_id, "User account was created");
(Some(new_token), Some(device_id))
} else {
(None, None)
};
// Generate new token for the device
let token = utils::random_string(TOKEN_LENGTH);
// Create device for this account
services
.users
.create_device(
&user_id,
&device_id,
&token,
body.initial_device_display_name.clone(),
Some(client.to_string()),
)
.await?;
debug_info!(%user_id, %device_id, "User account was created");
let device_display_name = body.initial_device_display_name.as_deref().unwrap_or("");
@@ -500,7 +505,7 @@ pub(crate) async fn register_route(
.await
.is_ok_and(is_equal_to!(1))
{
services.admin.make_user_admin(&user_id).boxed().await?;
services.admin.make_user_admin(&user_id).await?;
warn!("Granting {user_id} admin privileges as the first user");
} else if services.config.suspend_on_register {
// This is not an admin, suspend them.
@@ -578,9 +583,9 @@ pub(crate) async fn register_route(
}
Ok(register::v3::Response {
access_token: token,
access_token: Some(token),
user_id,
device_id: device,
device_id: Some(device_id),
refresh_token: None,
expires_in: None,
})
@@ -924,7 +929,7 @@ pub async fn full_user_deactivate(
.build_and_append_pdu(
PduBuilder::state(String::new(), &power_levels_content),
user_id,
Some(room_id),
room_id,
&state_lock,
)
.await
+2 -2
View File
@@ -69,7 +69,7 @@ pub(crate) async fn get_context_route(
let (base_id, base_pdu, visible) = try_join3(base_id, base_pdu, visible).await?;
if base_pdu.room_id_or_hash() != *room_id || base_pdu.event_id != *event_id {
if base_pdu.room_id != *room_id || base_pdu.event_id != *event_id {
return Err!(Request(NotFound("Base event not found.")));
}
@@ -130,7 +130,7 @@ pub(crate) async fn get_context_route(
let state_at = events_after
.last()
.map(ref_at!(1))
.map_or_else(|| body.event_id.as_ref(), |pdu| pdu.event_id.as_ref());
.map_or(body.event_id.as_ref(), |pdu| pdu.event_id.as_ref());
let state_ids = services
.rooms
+1 -1
View File
@@ -99,7 +99,7 @@ pub(crate) async fn get_content_thumbnail_route(
) -> Result<get_content_thumbnail::v1::Response> {
let user = body.sender_user();
let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?;
let dim = Dim::from_ruma(body.width, body.height, body.method.clone(), body.animated)?;
let mxc = Mxc {
server_name: &body.server_name,
media_id: &body.media_id,
+1 -1
View File
@@ -322,7 +322,7 @@ pub(crate) async fn get_content_thumbnail_legacy_route(
media_id: &body.media_id,
};
let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?;
let dim = Dim::from_ruma(body.width, body.height, body.method.clone(), body.animated)?;
match services.media.get_thumbnail(&mxc, &dim).await? {
| Some(FileMeta {
content,
+1 -1
View File
@@ -49,7 +49,7 @@ pub(crate) async fn ban_user_route(
..current_member_content
}),
sender_user,
Some(&body.room_id),
&body.room_id,
&state_lock,
)
.await?;
+29 -42
View File
@@ -4,14 +4,11 @@
Err, Result, debug_error, err, info,
matrix::{event::gen_event_id_canonical_json, pdu::PduBuilder},
};
use futures::FutureExt;
use futures::{FutureExt, join};
use ruma::{
OwnedServerName, RoomId, UserId,
api::{client::membership::invite_user, federation::membership::create_invite},
events::{
invite_permission_config::FilterLevel,
room::member::{MembershipState, RoomMemberEventContent},
},
events::room::member::{MembershipState, RoomMemberEventContent},
};
use service::Services;
@@ -50,21 +47,22 @@ pub(crate) async fn invite_user_route(
.await?;
match &body.recipient {
| invite_user::v3::InvitationRecipient::UserId { user_id: recipient_user } => {
let sender_filter_level = services
.users
.invite_filter_level(recipient_user, sender_user)
.await;
| invite_user::v3::InvitationRecipient::UserId { user_id } => {
let sender_ignored_recipient = services.users.user_is_ignored(sender_user, user_id);
let recipient_ignored_by_sender =
services.users.user_is_ignored(user_id, sender_user);
if !matches!(sender_filter_level, FilterLevel::Allow) {
// drop invites if the sender has the recipient filtered
let (sender_ignored_recipient, recipient_ignored_by_sender) =
join!(sender_ignored_recipient, recipient_ignored_by_sender);
if sender_ignored_recipient {
return Ok(invite_user::v3::Response {});
}
if let Ok(target_user_membership) = services
.rooms
.state_accessor
.get_member(&body.room_id, recipient_user)
.get_member(&body.room_id, user_id)
.await
{
if target_user_membership.membership == MembershipState::Ban {
@@ -72,27 +70,16 @@ pub(crate) async fn invite_user_route(
}
}
// check for blocked invites if the recipient is a local user.
if services.globals.user_is_local(recipient_user) {
let recipient_filter_level = services
.users
.invite_filter_level(sender_user, recipient_user)
.await;
// ignored invites aren't handled here
// since the recipient's membership should still be changed to `invite`.
// they're filtered out in the individual /sync handlers.
if matches!(recipient_filter_level, FilterLevel::Block) {
return Err!(Request(InviteBlocked(
"{recipient_user} has blocked invites from you."
)));
}
if recipient_ignored_by_sender {
// silently drop the invite to the recipient if they've been ignored by the
// sender, pretend it worked
return Ok(invite_user::v3::Response {});
}
invite_helper(
&services,
sender_user,
recipient_user,
user_id,
&body.room_id,
body.reason.clone(),
false,
@@ -111,7 +98,7 @@ pub(crate) async fn invite_user_route(
pub(crate) async fn invite_helper(
services: &Services,
sender_user: &UserId,
recipient_user: &UserId,
user_id: &UserId,
room_id: &RoomId,
reason: Option<String>,
is_direct: bool,
@@ -124,12 +111,12 @@ pub(crate) async fn invite_helper(
return Err!(Request(Forbidden("Invites are not allowed on this server.")));
}
if !services.globals.user_is_local(recipient_user) {
if !services.globals.user_is_local(user_id) {
let (pdu, pdu_json, invite_room_state) = {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let content = RoomMemberEventContent {
avatar_url: services.users.avatar_url(recipient_user).await.ok(),
avatar_url: services.users.avatar_url(user_id).await.ok(),
is_direct: Some(is_direct),
reason,
..RoomMemberEventContent::new(MembershipState::Invite)
@@ -139,14 +126,14 @@ pub(crate) async fn invite_helper(
.rooms
.timeline
.create_hash_and_sign_event(
PduBuilder::state(recipient_user.to_string(), &content),
PduBuilder::state(user_id.to_string(), &content),
sender_user,
Some(room_id),
room_id,
&state_lock,
)
.await?;
let invite_room_state = services.rooms.state.summary_stripped(&pdu, room_id).await;
let invite_room_state = services.rooms.state.summary_stripped(&pdu).await;
drop(state_lock);
@@ -157,7 +144,7 @@ pub(crate) async fn invite_helper(
let response = services
.sending
.send_federation_request(recipient_user.server_name(), create_invite::v2::Request {
.send_federation_request(user_id.server_name(), create_invite::v2::Request {
room_id: room_id.to_owned(),
event_id: (*pdu.event_id).to_owned(),
room_version: room_version_id.clone(),
@@ -186,7 +173,7 @@ pub(crate) async fn invite_helper(
return Err!(Request(BadJson(warn!(
%pdu.event_id, %event_id,
"Server {} sent event with wrong event ID",
recipient_user.server_name()
user_id.server_name()
))));
}
@@ -226,9 +213,9 @@ pub(crate) async fn invite_helper(
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let content = RoomMemberEventContent {
displayname: services.users.displayname(recipient_user).await.ok(),
avatar_url: services.users.avatar_url(recipient_user).await.ok(),
blurhash: services.users.blurhash(recipient_user).await.ok(),
displayname: services.users.displayname(user_id).await.ok(),
avatar_url: services.users.avatar_url(user_id).await.ok(),
blurhash: services.users.blurhash(user_id).await.ok(),
is_direct: Some(is_direct),
reason,
..RoomMemberEventContent::new(MembershipState::Invite)
@@ -238,9 +225,9 @@ pub(crate) async fn invite_helper(
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(recipient_user.to_string(), &content),
PduBuilder::state(user_id.to_string(), &content),
sender_user,
Some(room_id),
room_id,
&state_lock,
)
.await?;
+10 -23
View File
@@ -18,7 +18,7 @@
},
warn,
};
use futures::{FutureExt, StreamExt, TryFutureExt};
use futures::{FutureExt, StreamExt};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId,
RoomVersionId, UserId,
@@ -313,14 +313,11 @@ pub async fn join_room_by_id_helper(
}
}
if !server_in_room && servers.is_empty() {
return Err!(Request(NotFound(
"No servers were provided to assist in joining the room remotely, and we are not \
already participating in the room."
)));
}
let local_join = server_in_room
|| servers.is_empty()
|| (servers.len() == 1 && services.globals.server_is_ours(&servers[0]));
if server_in_room {
if local_join {
join_room_by_id_helper_local(
services,
sender_user,
@@ -559,10 +556,6 @@ async fn join_room_by_id_helper_remote(
services
.server_keys
.validate_and_add_event_id_no_fetch(pdu, &room_version_id)
.inspect_err(|e| {
debug_warn!("Could not validate send_join response room_state event: {e:?}");
})
.inspect(|_| debug!("Completed validating send_join response room_state event"))
})
.ready_filter_map(Result::ok)
.fold(HashMap::new(), |mut state, (event_id, value)| async move {
@@ -573,6 +566,7 @@ async fn join_room_by_id_helper_remote(
return state;
},
};
services.rooms.outlier.add_pdu_outlier(&event_id, &value);
if let Some(state_key) = &pdu.state_key {
let shortstatekey = services
@@ -583,6 +577,7 @@ async fn join_room_by_id_helper_remote(
state.insert(shortstatekey, pdu.event_id.clone());
}
state
})
.await;
@@ -603,7 +598,6 @@ async fn join_room_by_id_helper_remote(
})
.ready_filter_map(Result::ok)
.ready_for_each(|(event_id, value)| {
trace!(%event_id, "Adding PDU as an outlier from send_join auth_chain");
services.rooms.outlier.add_pdu_outlier(&event_id, &value);
})
.await;
@@ -624,9 +618,6 @@ async fn join_room_by_id_helper_remote(
&parsed_join_pdu,
None, // TODO: third party invite
|k, s| state_fetch(k.clone(), s.into()),
&state_fetch(StateEventType::RoomCreate, "".into())
.await
.expect("create event is missing from send_join auth"),
)
.await
.map_err(|e| err!(Request(Forbidden(warn!("Auth check failed: {e:?}")))))?;
@@ -661,7 +652,7 @@ async fn join_room_by_id_helper_remote(
.force_state(room_id, statehash_before_join, added, removed, &state_lock)
.await?;
debug!("Updating joined counts for new room");
info!("Updating joined counts for new room");
services
.rooms
.state_cache
@@ -674,7 +665,7 @@ async fn join_room_by_id_helper_remote(
let statehash_after_join = services
.rooms
.state
.append_to_state(&parsed_join_pdu, room_id)
.append_to_state(&parsed_join_pdu)
.await?;
info!("Appending new room join event");
@@ -686,7 +677,6 @@ async fn join_room_by_id_helper_remote(
join_event,
once(parsed_join_pdu.event_id.borrow()),
&state_lock,
room_id,
)
.await?;
@@ -742,7 +732,6 @@ async fn join_room_by_id_helper_local(
.iter()
.stream()
.any(|restriction_room_id| {
trace!("Checking if {sender_user} is joined to {restriction_room_id}");
services
.rooms
.state_cache
@@ -755,7 +744,6 @@ async fn join_room_by_id_helper_local(
.state_cache
.local_users_in_room(room_id)
.filter(|user| {
trace!("Checking if {user} can invite {sender_user} to {room_id}");
services.rooms.state_accessor.user_can_invite(
room_id,
user,
@@ -768,7 +756,6 @@ async fn join_room_by_id_helper_local(
.await
.map(ToOwned::to_owned)
} else {
trace!("No restriction rooms are joined by {sender_user}");
None
}
};
@@ -789,7 +776,7 @@ async fn join_room_by_id_helper_local(
.build_and_append_pdu(
PduBuilder::state(sender_user.to_string(), &content),
sender_user,
Some(room_id),
room_id,
&state_lock,
)
.await
+1 -1
View File
@@ -54,7 +54,7 @@ pub(crate) async fn kick_user_route(
..event
}),
sender_user,
Some(&body.room_id),
&body.room_id,
&state_lock,
)
.await?;
+2 -4
View File
@@ -373,7 +373,7 @@ async fn knock_room_helper_local(
.build_and_append_pdu(
PduBuilder::state(sender_user.to_string(), &content),
sender_user,
Some(room_id),
room_id,
&state_lock,
)
.await
@@ -502,7 +502,6 @@ async fn knock_room_helper_local(
knock_event,
once(parsed_knock_pdu.event_id.borrow()),
&state_lock,
room_id,
)
.await?;
@@ -673,7 +672,7 @@ async fn knock_room_helper_remote(
let statehash_after_knock = services
.rooms
.state
.append_to_state(&parsed_knock_pdu, room_id)
.append_to_state(&parsed_knock_pdu)
.await?;
info!("Updating membership locally to knock state with provided stripped state events");
@@ -702,7 +701,6 @@ async fn knock_room_helper_remote(
knock_event,
once(parsed_knock_pdu.event_id.borrow()),
&state_lock,
room_id,
)
.await?;
+1 -1
View File
@@ -206,7 +206,7 @@ pub async fn leave_room(
..event
}),
user_id,
Some(room_id),
room_id,
&state_lock,
)
.await?;
+6 -5
View File
@@ -69,11 +69,11 @@ pub(crate) async fn banned_room_check(
}
if let Some(room_id) = room_id {
let room_banned = services.rooms.metadata.is_banned(room_id).await;
let server_banned = room_id.server_name().is_some_and(|server_name| {
services.moderation.is_remote_server_forbidden(server_name)
});
if room_banned || server_banned {
if services.rooms.metadata.is_banned(room_id).await
|| services
.moderation
.is_remote_server_forbidden(room_id.server_name().expect("legacy room mxid"))
{
warn!(
"User {user_id} who is not an admin attempted to send an invite for or \
attempted to join a banned room or banned room server name: {room_id}"
@@ -106,6 +106,7 @@ pub(crate) async fn banned_room_check(
.boxed()
.await?;
}
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
}
} else if let Some(server_name) = server_name {
+1 -1
View File
@@ -47,7 +47,7 @@ pub(crate) async fn unban_user_route(
..current_member_content
}),
sender_user,
Some(&body.room_id),
&body.room_id,
&state_lock,
)
.await?;
+14 -47
View File
@@ -30,7 +30,6 @@
events::{
AnyStateEvent, StateEventType,
TimelineEventType::{self, *},
invite_permission_config::FilterLevel,
},
serde::Raw,
};
@@ -268,7 +267,7 @@ pub(crate) async fn ignored_filter(
pub(crate) async fn is_ignored_pdu<Pdu>(
services: &Services,
event: &Pdu,
recipient_user: &UserId,
user_id: &UserId,
) -> bool
where
Pdu: Event + Send + Sync,
@@ -279,29 +278,20 @@ pub(crate) async fn is_ignored_pdu<Pdu>(
return true;
}
let sender_user = event.sender();
let type_ignored = IGNORED_MESSAGE_TYPES.binary_search(event.kind()).is_ok();
let server_ignored = services
let ignored_type = IGNORED_MESSAGE_TYPES.binary_search(event.kind()).is_ok();
let ignored_server = services
.moderation
.is_remote_server_ignored(sender_user.server_name());
let user_ignored = services
.users
.user_is_ignored(sender_user, recipient_user)
.await;
.is_remote_server_ignored(event.sender().server_name());
if !type_ignored {
// We cannot safely ignore this type
return false;
}
if server_ignored {
// the sender's server is ignored, so ignore this event
return true;
}
if user_ignored && !services.config.send_messages_from_ignored_users_to_client {
// the recipient of this PDU has the sender ignored, and we're not
// configured to send ignored messages to clients
if ignored_type
&& (ignored_server
|| (!services.config.send_messages_from_ignored_users_to_client
&& services
.users
.user_is_ignored(event.sender(), user_id)
.await))
{
return true;
}
@@ -319,7 +309,7 @@ pub(crate) async fn visibility_filter(
services
.rooms
.state_accessor
.user_can_see_event(user_id, &pdu.room_id_or_hash(), pdu.event_id())
.user_can_see_event(user_id, pdu.room_id(), pdu.event_id())
.await
.then_some(item)
}
@@ -330,29 +320,6 @@ pub(crate) fn event_filter(item: PdusIterItem, filter: &RoomEventFilter) -> Opti
filter.matches(pdu).then_some(item)
}
#[inline]
pub(crate) async fn is_ignored_invite(
services: &Services,
recipient_user: &UserId,
room_id: &RoomId,
) -> bool {
let Ok(sender_user) = services
.rooms
.state_cache
.invite_sender(recipient_user, room_id)
.await
else {
// the invite may have been sent before the invite_sender table existed.
// assume it's not ignored
return false;
};
services
.users
.invite_filter_level(&sender_user, recipient_user)
.await == FilterLevel::Ignore
}
#[cfg_attr(debug_assertions, ctor::ctor)]
fn _is_sorted() {
debug_assert!(
+21 -5
View File
@@ -1,3 +1,5 @@
use std::collections::BTreeMap;
use axum::extract::State;
use conduwuit::{
Err, Result,
@@ -224,8 +226,7 @@ pub(crate) async fn get_avatar_url_route(
/// # `GET /_matrix/client/v3/profile/{userId}`
///
/// Returns the displayname, avatar_url, blurhash, and custom profile fields of
/// the user.
/// Returns the displayname, avatar_url, blurhash, and tz of the user.
///
/// - If user is on another server and we do not have a local copy already,
/// fetch profile over federation.
@@ -259,6 +260,9 @@ pub(crate) async fn get_profile_route(
services
.users
.set_blurhash(&body.user_id, response.blurhash.clone());
services
.users
.set_timezone(&body.user_id, response.tz.clone());
for (profile_key, profile_key_value) in &response.custom_profile_fields {
services.users.set_profile_key(
@@ -272,6 +276,7 @@ pub(crate) async fn get_profile_route(
displayname: response.displayname,
avatar_url: response.avatar_url,
blurhash: response.blurhash,
tz: response.tz,
custom_profile_fields: response.custom_profile_fields,
});
}
@@ -283,11 +288,21 @@ pub(crate) async fn get_profile_route(
return Err!(Request(NotFound("Profile was not found.")));
}
let (avatar_url, blurhash, displayname, custom_profile_fields) = join4(
let mut custom_profile_fields: BTreeMap<String, serde_json::Value> = services
.users
.all_profile_keys(&body.user_id)
.collect()
.await;
// services.users.timezone will collect the MSC4175 timezone key if it exists
custom_profile_fields.remove("us.cloke.msc4175.tz");
custom_profile_fields.remove("m.tz");
let (avatar_url, blurhash, displayname, tz) = join4(
services.users.avatar_url(&body.user_id).ok(),
services.users.blurhash(&body.user_id).ok(),
services.users.displayname(&body.user_id).ok(),
services.users.all_profile_keys(&body.user_id).collect(),
services.users.timezone(&body.user_id).ok(),
)
.await;
@@ -295,6 +310,7 @@ pub(crate) async fn get_profile_route(
avatar_url,
blurhash,
displayname,
tz,
custom_profile_fields,
})
}
@@ -407,7 +423,7 @@ pub async fn update_all_rooms(
if let Err(e) = services
.rooms
.timeline
.build_and_append_pdu(pdu_builder, user_id, Some(room_id), &state_lock)
.build_and_append_pdu(pdu_builder, user_id, room_id, &state_lock)
.await
{
warn!(%user_id, %room_id, "Failed to update/send new profile join membership update in room: {e}");
+1 -1
View File
@@ -36,7 +36,7 @@ pub(crate) async fn redact_event_route(
})
},
sender_user,
Some(&body.room_id),
&body.room_id,
&state_lock,
)
.await?;
+1 -1
View File
@@ -222,7 +222,7 @@ async fn visibility_filter<Pdu: Event + Send + Sync>(
services
.rooms
.state_accessor
.user_can_see_event(sender_user, &pdu.room_id_or_hash(), pdu.event_id())
.user_can_see_event(sender_user, pdu.room_id(), pdu.event_id())
.await
.then_some(item)
}
+22 -12
View File
@@ -1,8 +1,8 @@
use std::{fmt::Write as _, time::Duration};
use std::{fmt::Write as _, ops::Mul, time::Duration};
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{Err, Event, Result, debug_info, info, matrix::pdu::PduEvent, utils::ReadyExt};
use conduwuit::{Err, Result, debug_info, info, matrix::pdu::PduEvent, utils::ReadyExt};
use conduwuit_service::Services;
use rand::Rng;
use ruma::{
@@ -12,6 +12,7 @@
room::{report_content, report_room},
},
events::{Mentions, room::message::RoomMessageEventContent},
int,
};
use tokio::time::sleep;
@@ -24,6 +25,7 @@ struct Report {
user_id: Option<OwnedUserId>,
report_type: String,
reason: Option<String>,
score: Option<ruma::Int>,
}
/// # `POST /_matrix/client/v3/rooms/{roomId}/report`
@@ -48,15 +50,6 @@ pub(crate) async fn report_room_route(
delay_response().await;
// We log this early in case the room ID does actually exist, in which case
// admins who scan their logs can see the report and choose to investigate at
// their discretion.
info!(
"Received room report by user {sender_user} for room {} with reason: \"{}\"",
body.room_id,
body.reason.as_deref().unwrap_or("")
);
if !services
.rooms
.state_cache
@@ -67,6 +60,11 @@ pub(crate) async fn report_room_route(
"Room does not exist to us, no local users have joined at all"
)));
}
info!(
"Received room report by user {sender_user} for room {} with reason: \"{}\"",
body.room_id,
body.reason.as_deref().unwrap_or("")
);
let report = Report {
sender: sender_user.to_owned(),
@@ -75,6 +73,7 @@ pub(crate) async fn report_room_route(
user_id: None,
report_type: "room".to_owned(),
reason: body.reason.clone(),
score: None,
};
services.admin.send_message(build_report(report)).await.ok();
@@ -110,6 +109,7 @@ pub(crate) async fn report_event_route(
&body.room_id,
sender_user,
body.reason.as_ref(),
body.score,
&pdu,
)
.await?;
@@ -127,6 +127,7 @@ pub(crate) async fn report_event_route(
user_id: None,
report_type: "event".to_owned(),
reason: body.reason.clone(),
score: body.score,
};
services.admin.send_message(build_report(report)).await.ok();
@@ -165,6 +166,7 @@ pub(crate) async fn report_user_route(
user_id: Some(body.user_id.clone()),
report_type: "user".to_owned(),
reason: body.reason.clone(),
score: None,
};
info!(
@@ -190,6 +192,7 @@ async fn is_event_report_valid(
room_id: &RoomId,
sender_user: &UserId,
reason: Option<&String>,
score: Option<ruma::Int>,
pdu: &PduEvent,
) -> Result<()> {
debug_info!(
@@ -197,10 +200,14 @@ async fn is_event_report_valid(
valid"
);
if room_id != pdu.room_id_or_hash() {
if room_id != pdu.room_id {
return Err!(Request(NotFound("Event ID does not belong to the reported room",)));
}
if score.is_some_and(|s| s > int!(0) || s < int!(-100)) {
return Err!(Request(InvalidParam("Invalid score, must be within 0 to -100",)));
}
if reason.as_ref().is_some_and(|s| s.len() > 750) {
return Err!(Request(
InvalidParam("Reason too long, should be 750 characters or fewer",)
@@ -233,6 +240,9 @@ fn build_report(report: Report) -> RoomMessageEventContent {
if report.event_id.is_some() {
let _ = writeln!(text, "- Reported Event ID: `{}`", report.event_id.unwrap());
}
if let Some(score) = report.score {
let _ = writeln!(text, "- User-supplied offensiveness score: {}%", score.mul(int!(-1)));
}
if let Some(reason) = report.reason {
let _ = writeln!(text, "- Report Reason: {reason}");
}
+83 -176
View File
@@ -1,10 +1,10 @@
use std::collections::{BTreeMap, BTreeSet};
use std::collections::BTreeMap;
use axum::extract::State;
use conduwuit::{
Err, Result, RoomVersion, debug, debug_info, debug_warn, err, info,
Err, Result, debug_info, debug_warn, err, info,
matrix::{StateKey, pdu::PduBuilder},
trace, warn,
warn,
};
use conduwuit_service::{Services, appservice::RegistrationInfo};
use futures::FutureExt;
@@ -13,7 +13,6 @@
api::client::room::{self, create_room},
events::{
TimelineEventType,
invite_permission_config::FilterLevel,
room::{
canonical_alias::RoomCanonicalAliasEventContent,
create::RoomCreateEventContent,
@@ -50,7 +49,6 @@
/// - Send events implied by `name` and `topic`
/// - Send invite events
#[allow(clippy::large_stack_frames)]
#[allow(clippy::cognitive_complexity)]
pub(crate) async fn create_room_route(
State(services): State<crate::State>,
body: Ruma<create_room::v3::Request>,
@@ -70,6 +68,51 @@ pub(crate) async fn create_room_route(
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let room_id: OwnedRoomId = match &body.room_id {
| Some(custom_room_id) => custom_room_id_check(&services, custom_room_id)?,
| _ => RoomId::new(&services.server.name),
};
// check if room ID doesn't already exist instead of erroring on auth check
if services.rooms.short.get_shortroomid(&room_id).await.is_ok() {
return Err!(Request(RoomInUse("Room with that custom room ID already exists",)));
}
if body.visibility == room::Visibility::Public
&& services.server.config.lockdown_public_room_directory
&& !services.users.is_admin(sender_user).await
&& body.appservice_info.is_none()
{
warn!(
"Non-admin user {sender_user} tried to publish {room_id} to the room directory \
while \"lockdown_public_room_directory\" is enabled"
);
if services.server.config.admin_room_notices {
services
.admin
.notice(&format!(
"Non-admin user {sender_user} tried to publish {room_id} to the room \
directory while \"lockdown_public_room_directory\" is enabled"
))
.await;
}
return Err!(Request(Forbidden("Publishing rooms to the room directory is not allowed")));
}
let _short_id = services
.rooms
.short
.get_or_create_shortroomid(&room_id)
.await;
let state_lock = services.rooms.state.mutex.lock(&room_id).await;
let alias: Option<OwnedRoomAliasId> = match body.room_alias_name.as_ref() {
| Some(alias) =>
Some(room_alias_check(&services, alias, body.appservice_info.as_ref()).await?),
| _ => None,
};
let room_version = match body.room_version.clone() {
| Some(room_version) =>
if services.server.supported_room_version(&room_version) {
@@ -81,86 +124,6 @@ pub(crate) async fn create_room_route(
},
| None => services.server.config.default_room_version.clone(),
};
let room_features = RoomVersion::new(&room_version)?;
let room_id: Option<OwnedRoomId> = if !room_features.room_ids_as_hashes {
match &body.room_id {
| Some(custom_room_id) => Some(custom_room_id_check(&services, custom_room_id)?),
| None => Some(RoomId::new(services.globals.server_name())),
}
} else {
None
};
// check if room ID doesn't already exist instead of erroring on auth check
if let Some(ref room_id) = room_id {
if services.rooms.short.get_shortroomid(room_id).await.is_ok() {
return Err!(Request(RoomInUse("Room with that custom room ID already exists",)));
}
}
if body.visibility == room::Visibility::Public
&& services.server.config.lockdown_public_room_directory
&& !services.users.is_admin(sender_user).await
&& body.appservice_info.is_none()
{
warn!(
"Non-admin user {sender_user} tried to publish {room_id:?} to the room directory \
while \"lockdown_public_room_directory\" is enabled"
);
if services.server.config.admin_room_notices {
services
.admin
.notice(&format!(
"Non-admin user {sender_user} tried to publish {room_id:?} to the room \
directory while \"lockdown_public_room_directory\" is enabled"
))
.await;
}
return Err!(Request(Forbidden("Publishing rooms to the room directory is not allowed")));
}
let mut invitees = BTreeSet::new();
for recipient_user in &body.invite {
if !matches!(
services
.users
.invite_filter_level(recipient_user, sender_user)
.await,
FilterLevel::Allow
) {
// drop invites if the creator has them blocked
continue;
}
// if the recipient of the invite is local and has the sender blocked, error
// out. if the recipient is remote we can't tell yet, and if they're local and
// have the sender _ignored_ their invite will be filtered out in
// the handlers for the individual /sync endpoints
if services.globals.user_is_local(recipient_user)
&& matches!(
services
.users
.invite_filter_level(sender_user, recipient_user)
.await,
FilterLevel::Block
) {
return Err!(Request(InviteBlocked(
"{recipient_user} has blocked invites from you."
)));
}
invitees.insert(recipient_user.clone());
}
let alias: Option<OwnedRoomAliasId> = match body.room_alias_name.as_ref() {
| Some(alias) =>
Some(room_alias_check(&services, alias, body.appservice_info.as_ref()).await?),
| _ => None,
};
let create_content = match &body.creation_content {
| Some(content) => {
@@ -201,36 +164,18 @@ pub(crate) async fn create_room_route(
let content = match room_version {
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
RoomCreateEventContent::new_v1(sender_user.to_owned()),
| V11 => RoomCreateEventContent::new_v11(),
| _ => RoomCreateEventContent::new_v12(),
| _ => RoomCreateEventContent::new_v11(),
};
let mut content =
serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())?;
serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())
.unwrap();
content.insert("room_version".into(), json!(room_version.as_str()).try_into()?);
content
},
};
let state_lock = match room_id.clone() {
| Some(room_id) => {
let _short_id = services
.rooms
.short
.get_or_create_shortroomid(&room_id)
.await;
services.rooms.state.mutex.lock(&room_id).await
},
| None => {
let temp_room_id = RoomId::new(services.globals.server_name());
trace!("Locking temporary room state mutex for {temp_room_id}");
services.rooms.state.mutex.lock(&temp_room_id).await
},
};
// 1. The room create event
debug!("Creating room create event for {sender_user} in room {room_id:?}");
let tmp_id = room_id.as_deref();
let create_event_id = services
services
.rooms
.timeline
.build_and_append_pdu(
@@ -241,26 +186,13 @@ pub(crate) async fn create_room_route(
..Default::default()
},
sender_user,
tmp_id,
&room_id,
&state_lock,
)
.boxed()
.await?;
trace!("Created room create event with ID {}", &create_event_id);
let room_id = match room_id.clone() {
| Some(room_id) => room_id,
| None => {
let as_room_id = create_event_id.as_str().replace('$', "!");
trace!("Creating room with v12 room ID {as_room_id}");
RoomId::parse(&as_room_id)?.to_owned()
},
};
drop(state_lock);
debug!("Room created with ID {room_id}");
let state_lock = services.rooms.state.mutex.lock(&room_id).await;
// 2. Let the room creator join
debug_info!("Joining {sender_user} to room {room_id}");
services
.rooms
.timeline
@@ -273,7 +205,7 @@ pub(crate) async fn create_room_route(
..RoomMemberEventContent::new(MembershipState::Join)
}),
sender_user,
Some(&room_id),
&room_id,
&state_lock,
)
.boxed()
@@ -287,45 +219,26 @@ pub(crate) async fn create_room_route(
| _ => RoomPreset::PrivateChat, // Room visibility should not be custom
});
let mut power_levels_to_grant = BTreeMap::from_iter([(sender_user.to_owned(), int!(100))]);
let mut users = BTreeMap::from_iter([(sender_user.to_owned(), int!(100))]);
if preset == RoomPreset::TrustedPrivateChat {
for recipient_user in &invitees {
power_levels_to_grant.insert(recipient_user.clone(), int!(100));
}
}
let mut creators: Vec<OwnedUserId> = vec![sender_user.to_owned()];
// Do we care about additional_creators?
if room_features.explicitly_privilege_room_creators {
// Have they been specified?
if let Some(additional_creators) = create_content.get("additional_creators") {
// Are they a real array?
if let Some(additional_creators) = additional_creators.as_array() {
// Iterate through them
for creator in additional_creators {
// Are they a string?
if let Some(creator) = creator.as_str() {
// Do they parse into a real user ID?
if let Ok(creator) = OwnedUserId::parse(creator) {
// Add them to the power levels and creators
creators.push(creator.clone());
}
}
}
for invite in &body.invite {
if services.users.user_is_ignored(sender_user, invite).await {
continue;
} else if services.users.user_is_ignored(invite, sender_user).await {
// silently drop the invite to the recipient if they've been ignored by the
// sender, pretend it worked
continue;
}
users.insert(invite.clone(), int!(100));
}
} else {
power_levels_to_grant.insert(sender_user.to_owned(), int!(100));
creators.clear(); // If this vec is not empty, default_power_levels_content will
// treat this as a v12 room
}
let power_levels_content = default_power_levels_content(
body.power_level_content_override.as_ref(),
&body.visibility,
power_levels_to_grant,
creators,
users,
)?;
services
@@ -339,7 +252,7 @@ pub(crate) async fn create_room_route(
..Default::default()
},
sender_user,
Some(&room_id),
&room_id,
&state_lock,
)
.boxed()
@@ -356,7 +269,7 @@ pub(crate) async fn create_room_route(
alt_aliases: vec![],
}),
sender_user,
Some(&room_id),
&room_id,
&state_lock,
)
.boxed()
@@ -379,7 +292,7 @@ pub(crate) async fn create_room_route(
}),
),
sender_user,
Some(&room_id),
&room_id,
&state_lock,
)
.boxed()
@@ -395,7 +308,7 @@ pub(crate) async fn create_room_route(
&RoomHistoryVisibilityEventContent::new(HistoryVisibility::Shared),
),
sender_user,
Some(&room_id),
&room_id,
&state_lock,
)
.boxed()
@@ -414,7 +327,7 @@ pub(crate) async fn create_room_route(
}),
),
sender_user,
Some(&room_id),
&room_id,
&state_lock,
)
.boxed()
@@ -450,7 +363,7 @@ pub(crate) async fn create_room_route(
services
.rooms
.timeline
.build_and_append_pdu(pdu_builder, sender_user, Some(&room_id), &state_lock)
.build_and_append_pdu(pdu_builder, sender_user, &room_id, &state_lock)
.boxed()
.await?;
}
@@ -463,7 +376,7 @@ pub(crate) async fn create_room_route(
.build_and_append_pdu(
PduBuilder::state(String::new(), &RoomNameEventContent::new(name.clone())),
sender_user,
Some(&room_id),
&room_id,
&state_lock,
)
.boxed()
@@ -477,7 +390,7 @@ pub(crate) async fn create_room_route(
.build_and_append_pdu(
PduBuilder::state(String::new(), &RoomTopicEventContent { topic: topic.clone() }),
sender_user,
Some(&room_id),
&room_id,
&state_lock,
)
.boxed()
@@ -486,9 +399,17 @@ pub(crate) async fn create_room_route(
// 8. Events implied by invite (and TODO: invite_3pid)
drop(state_lock);
for recipient_user in &invitees {
for user_id in &body.invite {
if services.users.user_is_ignored(sender_user, user_id).await {
continue;
} else if services.users.user_is_ignored(user_id, sender_user).await {
// silently drop the invite to the recipient if they've been ignored by the
// sender, pretend it worked
continue;
}
if let Err(e) =
invite_helper(&services, sender_user, recipient_user, &room_id, None, body.is_direct)
invite_helper(&services, sender_user, user_id, &room_id, None, body.is_direct)
.boxed()
.await
{
@@ -529,7 +450,6 @@ fn default_power_levels_content(
power_level_content_override: Option<&Raw<RoomPowerLevelsEventContent>>,
visibility: &room::Visibility,
users: BTreeMap<OwnedUserId, Int>,
creators: Vec<OwnedUserId>,
) -> Result<serde_json::Value> {
let mut power_levels_content =
serde_json::to_value(RoomPowerLevelsEventContent { users, ..Default::default() })
@@ -579,19 +499,6 @@ fn default_power_levels_content(
}
}
if !creators.is_empty() {
// Raise the default power level of tombstone to 150
power_levels_content["events"]["m.room.tombstone"] =
serde_json::to_value(150).expect("150 is valid Value");
for creator in creators {
// Omit creators from the power level list altogether
power_levels_content["users"]
.as_object_mut()
.expect("users is an object")
.remove(creator.as_str());
}
}
Ok(power_levels_content)
}
+7 -7
View File
@@ -91,7 +91,7 @@ pub(crate) async fn upgrade_room_route(
replacement_room: replacement_room.clone(),
}),
sender_user,
Some(&body.room_id),
&body.room_id,
&state_lock,
)
.await?;
@@ -173,7 +173,7 @@ pub(crate) async fn upgrade_room_route(
timestamp: None,
},
sender_user,
Some(&replacement_room),
&replacement_room,
&state_lock,
)
.boxed()
@@ -204,7 +204,7 @@ pub(crate) async fn upgrade_room_route(
timestamp: None,
},
sender_user,
Some(&replacement_room),
&replacement_room,
&state_lock,
)
.boxed()
@@ -243,7 +243,7 @@ pub(crate) async fn upgrade_room_route(
..Default::default()
},
sender_user,
Some(&replacement_room),
&replacement_room,
&state_lock,
)
.boxed()
@@ -302,7 +302,7 @@ pub(crate) async fn upgrade_room_route(
..power_levels_event_content
}),
sender_user,
Some(&body.room_id),
&body.room_id,
&state_lock,
)
.boxed()
@@ -352,7 +352,7 @@ pub(crate) async fn upgrade_room_route(
..Default::default()
},
sender_user,
Some(space_id),
space_id,
&state_lock,
)
.boxed()
@@ -376,7 +376,7 @@ pub(crate) async fn upgrade_room_route(
..Default::default()
},
sender_user,
Some(space_id),
space_id,
&state_lock,
)
.boxed()
+1 -1
View File
@@ -80,7 +80,7 @@ pub(crate) async fn send_message_event_route(
..Default::default()
},
sender_user,
Some(&body.room_id),
&body.room_id,
&state_lock,
)
.await?;
+2 -2
View File
@@ -145,9 +145,9 @@ pub(super) async fn ldap_login(
let is_conduwuit_admin = services.admin.user_is_admin(lowercased_user_id).await;
if is_ldap_admin && !is_conduwuit_admin {
Box::pin(services.admin.make_user_admin(lowercased_user_id)).await?;
services.admin.make_user_admin(lowercased_user_id).await?;
} else if !is_ldap_admin && is_conduwuit_admin {
Box::pin(services.admin.revoke_admin(lowercased_user_id)).await?;
services.admin.revoke_admin(lowercased_user_id).await?;
}
Ok(user_id)
+1 -1
View File
@@ -201,7 +201,7 @@ async fn send_state_event_for_key_helper(
..Default::default()
},
sender,
Some(room_id),
room_id,
&state_lock,
)
.await?;
+2 -12
View File
@@ -60,10 +60,7 @@
use service::rooms::short::{ShortEventId, ShortStateKey};
use super::{load_timeline, share_encrypted_room};
use crate::{
Ruma, RumaResponse,
client::{ignored_filter, is_ignored_invite},
};
use crate::{Ruma, RumaResponse, client::ignored_filter};
#[derive(Default)]
struct StateChanges {
@@ -241,13 +238,6 @@ pub(crate) async fn build_sync_events(
.rooms
.state_cache
.rooms_invited(sender_user)
.wide_filter_map(async |(room_id, invite_state)| {
if is_ignored_invite(services, sender_user, &room_id).await {
None
} else {
Some((room_id, invite_state))
}
})
.fold_default(|mut invited_rooms: BTreeMap<_, _>, (room_id, invite_state)| async move {
let invite_count = services
.rooms
@@ -467,7 +457,7 @@ async fn handle_left_room(
state_key: Some(sender_user.as_str().into()),
unsigned: None,
// The following keys are dropped on conversion
room_id: Some(room_id.clone()),
room_id: room_id.clone(),
prev_events: vec![],
depth: uint!(1),
auth_events: vec![],
+1 -9
View File
@@ -11,7 +11,6 @@
utils::{
BoolExt, IterStream, ReadyExt, TryFutureExtExt,
math::{ruma_from_usize, usize_from_ruma, usize_from_u64_truncated},
stream::WidebandExt,
},
warn,
};
@@ -40,7 +39,7 @@
use super::{load_timeline, share_encrypted_room};
use crate::{
Ruma,
client::{DEFAULT_BUMP_TYPES, ignored_filter, is_ignored_invite},
client::{DEFAULT_BUMP_TYPES, ignored_filter},
};
type TodoRooms = BTreeMap<OwnedRoomId, (BTreeSet<TypeStateKey>, usize, u64)>;
@@ -103,13 +102,6 @@ pub(crate) async fn sync_events_v4_route(
.rooms
.state_cache
.rooms_invited(sender_user)
.wide_filter_map(async |(room_id, invite_state)| {
if is_ignored_invite(&services, sender_user, &room_id).await {
None
} else {
Some((room_id, invite_state))
}
})
.map(|r| r.0)
.collect()
.await;
+1 -9
View File
@@ -14,7 +14,6 @@
BoolExt, FutureBoolExt, IterStream, ReadyExt, TryFutureExtExt,
future::ReadyEqExt,
math::{ruma_from_usize, usize_from_ruma},
stream::WidebandExt,
},
warn,
};
@@ -39,7 +38,7 @@
use super::share_encrypted_room;
use crate::{
Ruma,
client::{DEFAULT_BUMP_TYPES, ignored_filter, is_ignored_invite, sync::load_timeline},
client::{DEFAULT_BUMP_TYPES, ignored_filter, sync::load_timeline},
};
type SyncInfo<'a> = (&'a UserId, &'a DeviceId, u64, &'a sync_events::v5::Request);
@@ -107,13 +106,6 @@ pub(crate) async fn sync_events_v5_route(
.rooms
.state_cache
.rooms_invited(sender_user)
.wide_filter_map(async |(room_id, invite_state)| {
if is_ignored_invite(services, sender_user, &room_id).await {
None
} else {
Some((room_id, invite_state))
}
})
.map(|r| r.0)
.collect::<Vec<OwnedRoomId>>();
+135 -11
View File
@@ -2,14 +2,18 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{Err, Result};
use conduwuit::{Err, Error, Result};
use futures::StreamExt;
use ruma::{
OwnedRoomId,
api::{
client::{
error::ErrorKind,
membership::mutual_rooms,
profile::{delete_profile_key, get_profile_key, set_profile_key},
profile::{
delete_profile_key, delete_timezone_key, get_profile_key, get_timezone_key,
set_profile_key, set_timezone_key,
},
},
federation,
},
@@ -56,6 +60,62 @@ pub(crate) async fn get_mutual_rooms_route(
})
}
/// # `DELETE /_matrix/client/unstable/uk.tcpip.msc4133/profile/:user_id/us.cloke.msc4175.tz`
///
/// Deletes the `tz` (timezone) of a user, as per MSC4133 and MSC4175.
///
/// - Also makes sure other users receive the update using presence EDUs
pub(crate) async fn delete_timezone_key_route(
State(services): State<crate::State>,
body: Ruma<delete_timezone_key::unstable::Request>,
) -> Result<delete_timezone_key::unstable::Response> {
let sender_user = body.sender_user();
if *sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot update the profile of another user")));
}
services.users.set_timezone(&body.user_id, None);
if services.config.allow_local_presence {
// Presence update
services
.presence
.ping_presence(&body.user_id, &PresenceState::Online)
.await?;
}
Ok(delete_timezone_key::unstable::Response {})
}
/// # `PUT /_matrix/client/unstable/uk.tcpip.msc4133/profile/:user_id/us.cloke.msc4175.tz`
///
/// Updates the `tz` (timezone) of a user, as per MSC4133 and MSC4175.
///
/// - Also makes sure other users receive the update using presence EDUs
pub(crate) async fn set_timezone_key_route(
State(services): State<crate::State>,
body: Ruma<set_timezone_key::unstable::Request>,
) -> Result<set_timezone_key::unstable::Response> {
let sender_user = body.sender_user();
if *sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot update the profile of another user")));
}
services.users.set_timezone(&body.user_id, body.tz.clone());
if services.config.allow_local_presence {
// Presence update
services
.presence
.ping_presence(&body.user_id, &PresenceState::Online)
.await?;
}
Ok(set_timezone_key::unstable::Response {})
}
/// # `PUT /_matrix/client/unstable/uk.tcpip.msc4133/profile/{user_id}/{field}`
///
/// Updates the profile key-value field of a user, as per MSC4133.
@@ -90,14 +150,19 @@ pub(crate) async fn set_profile_key_route(
)));
};
if body
.kv_pair
.keys()
.any(|key| key.starts_with("u.") && !profile_key_value.is_string())
{
return Err!(Request(BadJson("u.* profile key fields must be strings")));
}
if body.kv_pair.keys().any(|key| key.len() > 128) {
return Err!(Request(BadJson("Key names cannot be longer than 128 bytes")));
}
if body.key_name == "displayname" {
let Some(display_name) = profile_key_value.as_str() else {
return Err!(Request(BadJson("displayname must be a string")));
};
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
@@ -109,15 +174,12 @@ pub(crate) async fn set_profile_key_route(
update_displayname(
&services,
&body.user_id,
Some(display_name.to_owned()),
Some(profile_key_value.to_string()),
&all_joined_rooms,
)
.await;
} else if body.key_name == "avatar_url" {
let Some(avatar_url) = profile_key_value.as_str() else {
return Err!(Request(BadJson("avatar_url must be a string")));
};
let mxc = ruma::OwnedMxcUri::from(avatar_url);
let mxc = ruma::OwnedMxcUri::from(profile_key_value.to_string());
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
@@ -206,12 +268,70 @@ pub(crate) async fn delete_profile_key_route(
Ok(delete_profile_key::unstable::Response {})
}
/// # `GET /_matrix/client/unstable/uk.tcpip.msc4133/profile/:user_id/us.cloke.msc4175.tz`
///
/// Returns the `timezone` of the user as per MSC4133 and MSC4175.
///
/// - If user is on another server and we do not have a local copy already fetch
/// `timezone` over federation
pub(crate) async fn get_timezone_key_route(
State(services): State<crate::State>,
body: Ruma<get_timezone_key::unstable::Request>,
) -> Result<get_timezone_key::unstable::Response> {
if !services.globals.user_is_local(&body.user_id) {
// Create and update our local copy of the user
if let Ok(response) = services
.sending
.send_federation_request(
body.user_id.server_name(),
federation::query::get_profile_information::v1::Request {
user_id: body.user_id.clone(),
field: None, // we want the full user's profile to update locally as well
},
)
.await
{
if !services.users.exists(&body.user_id).await {
services.users.create(&body.user_id, None, None).await?;
}
services
.users
.set_displayname(&body.user_id, response.displayname.clone());
services
.users
.set_avatar_url(&body.user_id, response.avatar_url.clone());
services
.users
.set_blurhash(&body.user_id, response.blurhash.clone());
services
.users
.set_timezone(&body.user_id, response.tz.clone());
return Ok(get_timezone_key::unstable::Response { tz: response.tz });
}
}
if !services.users.exists(&body.user_id).await {
// Return 404 if this user doesn't exist and we couldn't fetch it over
// federation
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
}
Ok(get_timezone_key::unstable::Response {
tz: services.users.timezone(&body.user_id).await.ok(),
})
}
/// # `GET /_matrix/client/unstable/uk.tcpip.msc4133/profile/{userId}/{field}}`
///
/// Gets the profile key-value field of a user, as per MSC4133.
///
/// - If user is on another server and we do not have a local copy already fetch
/// the value over federation
/// `timezone` over federation
pub(crate) async fn get_profile_key_route(
State(services): State<crate::State>,
body: Ruma<get_profile_key::unstable::Request>,
@@ -247,6 +367,10 @@ pub(crate) async fn get_profile_key_route(
.users
.set_blurhash(&body.user_id, response.blurhash.clone());
services
.users
.set_timezone(&body.user_id, response.tz.clone());
match response.custom_profile_fields.get(&body.key_name) {
| Some(value) => {
profile_key_value.insert(body.key_name.clone(), value.clone());
-1
View File
@@ -59,7 +59,6 @@ pub(crate) async fn get_supported_versions_route(
("us.cloke.msc4175".to_owned(), true), /* Profile field for user time zone (https://github.com/matrix-org/matrix-spec-proposals/pull/4175) */
("org.matrix.simplified_msc3575".to_owned(), true), /* Simplified Sliding sync (https://github.com/matrix-org/matrix-spec-proposals/pull/4186) */
("uk.timedout.msc4323".to_owned(), true), /* agnostic suspend (https://github.com/matrix-org/matrix-spec-proposals/pull/4323) */
("org.matrix.msc4155".to_owned(), true), /* invite filtering (https://github.com/matrix-org/matrix-spec-proposals/pull/4155) */
]),
};
+3
View File
@@ -22,9 +22,12 @@
pub fn build(router: Router<State>, server: &Server) -> Router<State> {
let config = &server.config;
let mut router = router
.ruma_route(&client::get_timezone_key_route)
.ruma_route(&client::get_profile_key_route)
.ruma_route(&client::set_profile_key_route)
.ruma_route(&client::delete_profile_key_route)
.ruma_route(&client::set_timezone_key_route)
.ruma_route(&client::delete_timezone_key_route)
.ruma_route(&client::appservice_ping)
.ruma_route(&client::get_supported_versions_route)
.ruma_route(&client::get_register_available_route)
+5 -2
View File
@@ -20,7 +20,9 @@
client::{
directory::get_public_rooms,
error::ErrorKind,
profile::{get_avatar_url, get_display_name, get_profile, get_profile_key},
profile::{
get_avatar_url, get_display_name, get_profile, get_profile_key, get_timezone_key,
},
voip::get_turn_server_info,
},
federation::{authentication::XMatrix, openid::get_openid_userinfo},
@@ -87,7 +89,8 @@ pub(super) async fn auth(
| &get_profile::v3::Request::METADATA
| &get_profile_key::unstable::Request::METADATA
| &get_display_name::v3::Request::METADATA
| &get_avatar_url::v3::Request::METADATA => {
| &get_avatar_url::v3::Request::METADATA
| &get_timezone_key::unstable::Request::METADATA => {
if services.server.config.require_auth_for_profile_requests {
match token {
| Token::Appservice(_) | Token::User(_) => {
+2 -2
View File
@@ -2,7 +2,7 @@
use axum::extract::State;
use conduwuit::{
Event, PduCount, Result,
PduCount, Result,
utils::{IterStream, ReadyExt, stream::TryTools},
};
use futures::{FutureExt, StreamExt, TryStreamExt};
@@ -68,7 +68,7 @@ pub(crate) async fn get_backfill_route(
Ok(services
.rooms
.state_accessor
.server_can_see_event(body.origin(), &pdu.room_id_or_hash(), &pdu.event_id)
.server_can_see_event(body.origin(), &pdu.room_id, &pdu.event_id)
.await
.then_some(pdu))
})
+9 -13
View File
@@ -61,16 +61,13 @@ pub(crate) async fn create_invite_route(
let mut signed_event = utils::to_canonical_object(&body.event)
.map_err(|_| err!(Request(InvalidParam("Invite event is invalid."))))?;
let recipient_user: OwnedUserId = signed_event
let invited_user: OwnedUserId = signed_event
.get("state_key")
.try_into()
.map(UserId::to_owned)
.map_err(|e| err!(Request(InvalidParam("Invalid state_key property: {e}"))))?;
if !services
.globals
.server_is_ours(recipient_user.server_name())
{
if !services.globals.server_is_ours(invited_user.server_name()) {
return Err!(Request(InvalidParam("User does not belong to this homeserver.")));
}
@@ -78,7 +75,7 @@ pub(crate) async fn create_invite_route(
services
.rooms
.event_handler
.acl_check(recipient_user.server_name(), &body.room_id)
.acl_check(invited_user.server_name(), &body.room_id)
.await?;
services
@@ -92,19 +89,18 @@ pub(crate) async fn create_invite_route(
// Add event_id back
signed_event.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.to_string()));
let sender_user: &UserId = signed_event
let sender: &UserId = signed_event
.get("sender")
.try_into()
.map_err(|e| err!(Request(InvalidParam("Invalid sender property: {e}"))))?;
if services.rooms.metadata.is_banned(&body.room_id).await
&& !services.users.is_admin(&recipient_user).await
&& !services.users.is_admin(&invited_user).await
{
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
}
if services.config.block_non_admin_invites && !services.users.is_admin(&recipient_user).await
{
if services.config.block_non_admin_invites && !services.users.is_admin(&invited_user).await {
return Err!(Request(Forbidden("This server does not allow room invites.")));
}
@@ -135,9 +131,9 @@ pub(crate) async fn create_invite_route(
.state_cache
.update_membership(
&body.room_id,
&recipient_user,
&invited_user,
RoomMemberEventContent::new(MembershipState::Invite),
sender_user,
sender,
Some(invite_state),
body.via.clone(),
true,
@@ -145,7 +141,7 @@ pub(crate) async fn create_invite_route(
.await?;
for appservice in services.appservice.read().await.values() {
if appservice.is_user_match(&recipient_user) {
if appservice.is_user_match(&invited_user) {
services
.sending
.send_appservice_request(
+1 -1
View File
@@ -122,7 +122,7 @@ pub(crate) async fn create_join_event_template_route(
..RoomMemberEventContent::new(MembershipState::Join)
}),
&body.user_id,
Some(&body.room_id),
&body.room_id,
&state_lock,
)
.await?;
+1 -1
View File
@@ -95,7 +95,7 @@ pub(crate) async fn create_knock_event_template_route(
&RoomMemberEventContent::new(MembershipState::Knock),
),
&body.user_id,
Some(&body.room_id),
&body.room_id,
&state_lock,
)
.await?;
+1 -1
View File
@@ -45,7 +45,7 @@ pub(crate) async fn create_leave_event_template_route(
&RoomMemberEventContent::new(MembershipState::Leave),
),
&body.user_id,
Some(&body.room_id),
&body.room_id,
&state_lock,
)
.await?;
+1 -1
View File
@@ -67,7 +67,7 @@ pub(crate) async fn get_content_thumbnail_route(
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content_thumbnail::v1::Request>,
) -> Result<get_content_thumbnail::v1::Response> {
let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?;
let dim = Dim::from_ruma(body.width, body.height, body.method.clone(), body.animated)?;
let mxc = Mxc {
server_name: services.globals.server_name(),
media_id: &body.media_id,
+7
View File
@@ -83,6 +83,7 @@ pub(crate) async fn get_profile_information_route(
let mut displayname = None;
let mut avatar_url = None;
let mut blurhash = None;
let mut tz = None;
let mut custom_profile_fields = BTreeMap::new();
match &body.field {
@@ -106,6 +107,7 @@ pub(crate) async fn get_profile_information_route(
displayname = services.users.displayname(&body.user_id).await.ok();
avatar_url = services.users.avatar_url(&body.user_id).await.ok();
blurhash = services.users.blurhash(&body.user_id).await.ok();
tz = services.users.timezone(&body.user_id).await.ok();
custom_profile_fields = services
.users
.all_profile_keys(&body.user_id)
@@ -114,10 +116,15 @@ pub(crate) async fn get_profile_information_route(
},
}
// services.users.timezone will collect the MSC4175 timezone key if it exists
custom_profile_fields.remove("us.cloke.msc4175.tz");
custom_profile_fields.remove("m.tz");
Ok(get_profile_information::v1::Response {
displayname,
avatar_url,
blurhash,
tz,
custom_profile_fields,
})
}
+1 -5
View File
@@ -175,11 +175,7 @@ pub(crate) async fn create_knock_event_v1_route(
.send_pdu_room(&body.room_id, &pdu_id)
.await?;
let knock_room_state = services
.rooms
.state
.summary_stripped(&pdu, &body.room_id)
.await;
let knock_room_state = services.rooms.state.summary_stripped(&pdu).await;
Ok(send_knock::v1::Response { knock_room_state })
}
+1 -1
View File
@@ -92,7 +92,7 @@ ruma.workspace = true
sanitize-filename.workspace = true
serde_json.workspace = true
serde_regex.workspace = true
serde_yml.workspace = true
serde_yaml.workspace = true
serde.workspace = true
smallvec.workspace = true
smallstr.workspace = true
+1 -1
View File
@@ -83,7 +83,7 @@ pub enum Error {
#[error(transparent)]
TypedHeader(#[from] axum_extra::typed_header::TypedHeaderRejection),
#[error(transparent)]
Yaml(#[from] serde_yml::Error),
Yaml(#[from] serde_yaml::Error),
// ruma/conduwuit
#[error("Arithmetic operation failed: {0}")]
-1
View File
@@ -73,7 +73,6 @@ pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode {
| ThreepidAuthFailed
| UserDeactivated
| ThreepidDenied
| InviteBlocked
| WrongRoomKeysVersion { .. }
| Forbidden { .. } => StatusCode::FORBIDDEN,
+1 -1
View File
@@ -18,7 +18,7 @@
/// Experimental, partially supported room versions
pub const UNSTABLE_ROOM_VERSIONS: &[RoomVersionId] =
&[RoomVersionId::V3, RoomVersionId::V4, RoomVersionId::V5, RoomVersionId::V12];
&[RoomVersionId::V3, RoomVersionId::V4, RoomVersionId::V5];
type RoomVersion = (RoomVersionId, RoomVersionStability);
+1 -1
View File
@@ -27,5 +27,5 @@ fn init_user_agent() -> String { format!("{}/{}", name(), version()) }
fn init_version() -> String {
conduwuit_build_metadata::version_tag()
.map_or_else(|| SEMANTIC.to_owned(), |extra| format!("{SEMANTIC} ({extra})"))
.map_or(SEMANTIC.to_owned(), |extra| format!("{SEMANTIC} ({extra})"))
}
+2 -7
View File
@@ -10,7 +10,7 @@
use std::fmt::Debug;
use ruma::{
CanonicalJsonObject, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, RoomId,
CanonicalJsonObject, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId,
RoomVersionId, UserId, events::TimelineEventType,
};
use serde::Deserialize;
@@ -168,12 +168,7 @@ fn as_mut_pdu(&mut self) -> &mut Pdu { unimplemented!("not a mutable Pdu") }
fn redacts(&self) -> Option<&EventId>;
/// The `RoomId` of this event.
fn room_id(&self) -> Option<&RoomId>;
/// The `RoomId` or hash of this event.
/// This should only be preferred over room_id() if the event is a v12
/// create event.
fn room_id_or_hash(&self) -> OwnedRoomId;
fn room_id(&self) -> &RoomId;
/// The `UserId` of this event.
fn sender(&self) -> &UserId;
+2 -9
View File
@@ -32,19 +32,12 @@ fn matches(&self, event: &E) -> bool {
}
fn matches_room<E: Event>(event: &E, filter: &RoomEventFilter) -> bool {
if filter
.not_rooms
.iter()
.any(is_equal_to!(event.room_id().expect("event has a room ID")))
{
if filter.not_rooms.iter().any(is_equal_to!(event.room_id())) {
return false;
}
if let Some(rooms) = filter.rooms.as_ref() {
if !rooms
.iter()
.any(is_equal_to!(event.room_id().expect("event has a room ID")))
{
if !rooms.iter().any(is_equal_to!(event.room_id())) {
return false;
}
}
+3 -44
View File
@@ -31,8 +31,7 @@
pub struct Pdu {
pub event_id: OwnedEventId,
#[serde(skip_serializing_if = "Option::is_none")]
pub room_id: Option<OwnedRoomId>,
pub room_id: OwnedRoomId,
pub sender: OwnedUserId,
@@ -111,27 +110,7 @@ fn prev_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Sen
fn redacts(&self) -> Option<&EventId> { self.redacts.as_deref() }
#[inline]
fn room_id(&self) -> Option<&RoomId> { self.room_id.as_deref() }
#[inline]
fn room_id_or_hash(&self) -> OwnedRoomId {
if *self.event_type() != TimelineEventType::RoomCreate {
return self
.room_id()
.expect("Event must have a room ID")
.to_owned();
}
if let Some(room_id) = &self.room_id {
// v1-v11
room_id.clone()
} else {
// v12+
let constructed_hash = self.event_id.as_str().replace('$', "!");
RoomId::parse(&constructed_hash)
.expect("event ID can be parsed")
.to_owned()
}
}
fn room_id(&self) -> &RoomId { &self.room_id }
#[inline]
fn sender(&self) -> &UserId { &self.sender }
@@ -184,27 +163,7 @@ fn prev_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Sen
fn redacts(&self) -> Option<&EventId> { self.redacts.as_deref() }
#[inline]
fn room_id(&self) -> Option<&RoomId> { self.room_id.as_ref().map(AsRef::as_ref) }
#[inline]
fn room_id_or_hash(&self) -> OwnedRoomId {
if *self.event_type() != TimelineEventType::RoomCreate {
return self
.room_id()
.expect("Event must have a room ID")
.to_owned();
}
if let Some(room_id) = &self.room_id {
// v1-v11
room_id.clone()
} else {
// v12+
let constructed_hash = self.event_id.as_str().replace('$', "!");
RoomId::parse(&constructed_hash)
.expect("event ID can be parsed")
.to_owned()
}
}
fn room_id(&self) -> &RoomId { &self.room_id }
#[inline]
fn sender(&self) -> &UserId { &self.sender }
+1 -1
View File
@@ -406,7 +406,7 @@ fn to_pdu_event<S>(
Pdu {
event_id: id.try_into().unwrap(),
room_id: Some(room_id().to_owned()),
room_id: room_id().to_owned(),
sender: sender.to_owned(),
origin_server_ts: ts.try_into().unwrap(),
state_key: state_key.map(Into::into),
+109 -280
View File
@@ -2,7 +2,7 @@
use futures::{
Future,
future::{OptionFuture, join, join3},
future::{OptionFuture, join3},
};
use ruma::{
Int, OwnedUserId, RoomVersionId, UserId,
@@ -44,15 +44,6 @@ struct RoomMemberContentFields {
join_authorised_via_users_server: Option<Raw<OwnedUserId>>,
}
#[derive(Deserialize)]
struct RoomCreateContentFields {
room_version: Option<Raw<RoomVersionId>>,
creator: Option<Raw<IgnoredAny>>,
additional_creators: Option<Vec<Raw<OwnedUserId>>>,
#[serde(rename = "m.federate", default = "ruma::serde::default_true")]
federate: bool,
}
/// For the given event `kind` what are the relevant auth events that are needed
/// to authenticate this `content`.
///
@@ -65,24 +56,16 @@ pub fn auth_types_for_event(
sender: &UserId,
state_key: Option<&str>,
content: &RawJsonValue,
room_version: &RoomVersion,
) -> serde_json::Result<Vec<(StateEventType, StateKey)>> {
if kind == &TimelineEventType::RoomCreate {
return Ok(vec![]);
}
let mut auth_types = if room_version.room_ids_as_hashes {
vec![
(StateEventType::RoomPowerLevels, StateKey::new()),
(StateEventType::RoomMember, sender.as_str().into()),
]
} else {
vec![
(StateEventType::RoomPowerLevels, StateKey::new()),
(StateEventType::RoomMember, sender.as_str().into()),
(StateEventType::RoomCreate, StateKey::new()),
]
};
let mut auth_types = vec![
(StateEventType::RoomPowerLevels, StateKey::new()),
(StateEventType::RoomMember, sender.as_str().into()),
(StateEventType::RoomCreate, StateKey::new()),
];
if kind == &TimelineEventType::RoomMember {
#[derive(Deserialize)]
@@ -153,13 +136,11 @@ struct RoomMemberContentFields {
event_id = incoming_event.event_id().as_str(),
)
)]
#[allow(clippy::suspicious_operation_groupings)]
pub async fn auth_check<E, F, Fut>(
room_version: &RoomVersion,
incoming_event: &E,
current_third_party_invite: Option<&E>,
fetch_state: F,
create_event: &E,
) -> Result<bool, Error>
where
F: Fn(&StateEventType, &str) -> Fut + Send,
@@ -188,6 +169,12 @@ pub async fn auth_check<E, F, Fut>(
//
// 1. If type is m.room.create:
if *incoming_event.event_type() == TimelineEventType::RoomCreate {
#[derive(Deserialize)]
struct RoomCreateContentFields {
room_version: Option<Raw<RoomVersionId>>,
creator: Option<Raw<IgnoredAny>>,
}
debug!("start m.room.create check");
// If it has any previous events, reject
@@ -197,16 +184,14 @@ pub async fn auth_check<E, F, Fut>(
}
// If the domain of the room_id does not match the domain of the sender, reject
if incoming_event.room_id().is_some() {
let Some(room_id_server_name) = incoming_event.room_id().unwrap().server_name()
else {
warn!("room ID has no servername");
return Ok(false);
};
if room_id_server_name != sender.server_name() {
warn!("servername of room ID does not match servername of sender");
return Ok(false);
}
let Some(room_id_server_name) = incoming_event.room_id().server_name() else {
warn!("room ID has no servername");
return Ok(false);
};
if room_id_server_name != sender.server_name() {
warn!("servername of room ID does not match servername of sender");
return Ok(false);
}
// If content.room_version is present and is not a recognized version, reject
@@ -219,14 +204,7 @@ pub async fn auth_check<E, F, Fut>(
return Ok(false);
}
if room_version.room_ids_as_hashes && incoming_event.room_id().is_some() {
warn!("room create event incorrectly claims a room ID");
return Ok(false);
}
if !room_version.use_room_create_sender
&& !room_version.explicitly_privilege_room_creators
{
if !room_version.use_room_create_sender {
// If content has no creator field, reject
if content.creator.is_none() {
warn!("no creator field found in m.room.create content");
@@ -238,8 +216,6 @@ pub async fn auth_check<E, F, Fut>(
return Ok(true);
}
// NOTE(hydra): We always have a room ID from this point forward.
/*
// TODO: In the past this code was commented as it caused problems with Synapse. This is no
// longer the case. This needs to be implemented.
@@ -266,69 +242,54 @@ pub async fn auth_check<E, F, Fut>(
}
*/
let (power_levels_event, sender_member_event) = join(
// fetch_state(&StateEventType::RoomCreate, ""),
let (room_create_event, power_levels_event, sender_member_event) = join3(
fetch_state(&StateEventType::RoomCreate, ""),
fetch_state(&StateEventType::RoomPowerLevels, ""),
fetch_state(&StateEventType::RoomMember, sender.as_str()),
)
.await;
let room_create_event = create_event.clone();
let room_create_event = match room_create_event {
| None => {
warn!("no m.room.create event in auth chain");
return Ok(false);
},
| Some(e) => e,
};
// Get the content of the room create event, used later.
let room_create_content: RoomCreateContentFields =
from_json_str(room_create_event.content().get())?;
if room_create_content
.room_version
.is_some_and(|v| v.deserialize().is_err())
{
warn!("invalid room version found in m.room.create event");
return Ok(false);
}
let expected_room_id = room_create_event.room_id_or_hash();
if incoming_event.room_id().unwrap() != expected_room_id {
warn!(
expected = %expected_room_id,
received = %incoming_event.room_id().unwrap(),
"room_id of incoming event ({}) does not match room_id of m.room.create event ({})",
incoming_event.room_id().unwrap(),
expected_room_id,
);
return Ok(false);
}
// If the create event is referenced in the event's auth events, and this is a
// v12 room, reject
let claims_create_event = incoming_event
.auth_events()
.any(|id| id == room_create_event.event_id());
if room_version.room_ids_as_hashes && claims_create_event {
warn!("m.room.create event incorrectly found in auth events");
return Ok(false);
} else if !room_version.room_ids_as_hashes && !claims_create_event {
// If the create event is not referenced in the event's auth events, and this is
// a v11 room, reject
warn!("no m.room.create event found in auth events");
if incoming_event.room_id() != room_create_event.room_id() {
warn!("room_id of incoming event does not match room_id of m.room.create event");
return Ok(false);
}
if let Some(ref pe) = power_levels_event {
if *pe.room_id().unwrap() != expected_room_id {
warn!(
expected = %expected_room_id,
received = %pe.room_id().unwrap(),
"room_id of power levels event does not match room_id of m.room.create event"
);
if pe.room_id() != room_create_event.room_id() {
warn!("room_id of power levels event does not match room_id of m.room.create event");
return Ok(false);
}
}
// 3. If event does not have m.room.create in auth_events reject
if !incoming_event
.auth_events()
.any(|id| id == room_create_event.event_id())
{
warn!("no m.room.create event in auth events");
return Ok(false);
}
// If the create event content has the field m.federate set to false and the
// sender domain of the event does not match the sender domain of the create
// event, reject.
if !room_version.room_ids_as_hashes
&& !room_create_content.federate
#[derive(Deserialize)]
#[allow(clippy::items_after_statements)]
struct RoomCreateContentFederate {
#[serde(rename = "m.federate", default = "ruma::serde::default_true")]
federate: bool,
}
let room_create_content: RoomCreateContentFederate =
from_json_str(room_create_event.content().get())?;
if !room_create_content.federate
&& room_create_event.sender().server_name() != incoming_event.sender().server_name()
{
warn!(
@@ -360,7 +321,7 @@ pub async fn auth_check<E, F, Fut>(
debug!("starting m.room.member check");
let state_key = match incoming_event.state_key() {
| None => {
warn!("no state key in member event");
warn!("no statekey in member event");
return Ok(false);
},
| Some(s) => s,
@@ -416,7 +377,6 @@ pub async fn auth_check<E, F, Fut>(
&user_for_join_auth_membership,
&room_create_event,
)? {
warn!("membership change not valid for some reason");
return Ok(false);
}
@@ -434,18 +394,8 @@ pub async fn auth_check<E, F, Fut>(
},
};
if sender_member_event
.room_id()
.expect("we have a room ID for non create events")
!= expected_room_id
{
warn!(
"room_id of incoming event ({}) does not match room_id of m.room.create event ({})",
sender_member_event
.room_id()
.expect("event must have a room ID"),
expected_room_id
);
if sender_member_event.room_id() != room_create_event.room_id() {
warn!("room_id of incoming event does not match room_id of m.room.create event");
return Ok(false);
}
@@ -467,7 +417,7 @@ pub async fn auth_check<E, F, Fut>(
}
// If type is m.room.third_party_invite
let mut sender_power_level = match &power_levels_event {
let sender_power_level = match &power_levels_event {
| Some(pl) => {
let content =
deserialize_power_levels_content_fields(pl.content().get(), room_version)?;
@@ -489,24 +439,6 @@ pub async fn auth_check<E, F, Fut>(
if is_creator { int!(100) } else { int!(0) }
},
};
if room_version.explicitly_privilege_room_creators {
// If the user sent the create event, or is listed in additional_creators, just
// give them Int::MAX
if sender == room_create_event.sender()
|| room_create_content
.additional_creators
.as_ref()
.is_some_and(|creators| {
creators
.iter()
.any(|c| c.deserialize().is_ok_and(|c| c == *sender))
}) {
trace!("privileging room creator or additional creator");
// This user is the room creator or an additional creator, give them max power
// level
sender_power_level = Int::MAX;
}
}
// Allow if and only if sender's current power level is greater than
// or equal to the invite level
@@ -587,26 +519,6 @@ pub async fn auth_check<E, F, Fut>(
Ok(true)
}
fn is_creator<EV>(v: &RoomVersion, c: &BTreeSet<OwnedUserId>, ce: &EV, user_id: &UserId) -> bool
where
EV: Event + Send + Sync,
{
if v.explicitly_privilege_room_creators {
c.contains(user_id)
} else if v.use_room_create_sender {
ce.sender() == user_id
} else {
#[allow(deprecated)]
let creator = from_json_str::<RoomCreateEventContent>(ce.content().get())
.unwrap()
.creator
.ok_or_else(|| serde_json::Error::missing_field("creator"))
.unwrap();
creator == user_id
}
}
// TODO deserializing the member, power, join_rules event contents is done in
// conduit just before this is called. Could they be passed in?
/// Does the user who sent this member event have required power levels to do
@@ -642,7 +554,6 @@ fn valid_membership_change<E>(
struct GetThirdPartyInvite {
third_party_invite: Option<Raw<ThirdPartyInvite>>,
}
let create_content = from_json_str::<RoomCreateContentFields>(create_room.content().get())?;
let content = current_event.content();
let target_membership = from_json_str::<GetMembership>(content.get())?.membership;
@@ -665,37 +576,15 @@ struct GetThirdPartyInvite {
| None => RoomPowerLevelsEventContent::default(),
};
let mut sender_power = power_levels
let sender_power = power_levels
.users
.get(sender)
.or_else(|| sender_is_joined.then_some(&power_levels.users_default));
let mut target_power = power_levels.users.get(target_user).or_else(|| {
let target_power = power_levels.users.get(target_user).or_else(|| {
(target_membership == MembershipState::Join).then_some(&power_levels.users_default)
});
let mut creators = BTreeSet::new();
creators.insert(create_room.sender().to_owned());
if room_version.explicitly_privilege_room_creators {
// Explicitly privilege room creators
// If the sender sent the create event, or in additional_creators, give them
// Int::MAX. Same case for target.
if let Some(additional_creators) = &create_content.additional_creators {
for c in additional_creators {
if let Ok(c) = c.deserialize() {
creators.insert(c);
}
}
}
if creators.contains(sender) {
sender_power = Some(&Int::MAX);
}
if creators.contains(target_user) {
target_power = Some(&Int::MAX);
}
}
trace!(?creators, "creators for room");
let mut join_rules = JoinRule::Invite;
if let Some(jr) = &join_rules_event {
join_rules = from_json_str::<RoomJoinRulesEventContent>(jr.content().get())?.join_rule;
@@ -724,29 +613,15 @@ struct GetThirdPartyInvite {
} else {
(int!(0), int!(0))
};
let user_joined = user_for_join_auth_membership == &MembershipState::Join;
let okay_power = is_creator(room_version, &creators, create_room, user_for_join_auth)
|| auth_user_pl >= invite_level;
trace!(
auth_user_pl=?auth_user_pl,
invite_level=?invite_level,
user_joined=?user_joined,
okay_power=?okay_power,
passing=?(user_joined && okay_power),
"user for join auth is valid check details"
);
user_joined && okay_power
(user_for_join_auth_membership == &MembershipState::Join)
&& (auth_user_pl >= invite_level)
} else {
// No auth user was given
trace!("No auth user given for join auth");
false
};
let sender_creator = is_creator(room_version, &creators, create_room, sender);
let target_creator = is_creator(room_version, &creators, create_room, target_user);
Ok(match target_membership {
| MembershipState::Join => {
trace!("starting target_membership=join check");
// 1. If the only previous event is an m.room.create and the state_key is the
// creator,
// allow
@@ -758,25 +633,24 @@ struct GetThirdPartyInvite {
let no_more_prev_events = prev_events.next().is_none();
if prev_event_is_create_event && no_more_prev_events {
trace!(
sender = %sender,
target_user = %target_user,
?sender_creator,
?target_creator,
"checking if sender is a room creator for initial membership event"
);
let is_creator = sender_creator && target_creator;
let is_creator = if room_version.use_room_create_sender {
let creator = create_room.sender();
creator == sender && creator == target_user
} else {
#[allow(deprecated)]
let creator = from_json_str::<RoomCreateEventContent>(create_room.content().get())?
.creator
.ok_or_else(|| serde_json::Error::missing_field("creator"))?;
creator == sender && creator == target_user
};
if is_creator {
debug!("sender is room creator, allowing join");
return Ok(true);
}
trace!("sender is not room creator, proceeding with normal auth checks");
}
let membership_allows_join = matches!(
target_user_current_membership,
MembershipState::Join | MembershipState::Invite
);
if sender != target_user {
// If the sender does not match state_key, reject.
warn!("Can't make other user join");
@@ -785,77 +659,39 @@ struct GetThirdPartyInvite {
// If the sender is banned, reject.
warn!(?target_user_membership_event_id, "Banned user can't join");
false
} else {
match join_rules {
| JoinRule::Invite =>
if !membership_allows_join {
warn!(
membership=?target_user_current_membership,
"Join rule is invite but membership does not allow join"
);
false
} else {
true
},
| JoinRule::Knock if !room_version.allow_knocking => {
warn!("Join rule is knock but room version does not allow knocking");
false
},
| JoinRule::Knock =>
if !membership_allows_join {
warn!(
membership=?target_user_current_membership,
"Join rule is knock but membership does not allow join"
);
false
} else {
true
},
| JoinRule::KnockRestricted(_) if !room_version.knock_restricted_join_rule =>
{
warn!(
"Join rule is knock_restricted but room version does not support it"
);
false
},
| JoinRule::KnockRestricted(_) => {
if membership_allows_join || user_for_join_auth_is_valid {
true
} else {
warn!(
membership=?target_user_current_membership,
"Join rule is a restricted one, but no valid authorising user \
was given and the sender's current membership does not permit \
a join transition"
);
false
}
},
| JoinRule::Restricted(_) =>
if membership_allows_join || user_for_join_auth_is_valid {
true
} else {
warn!(
"Join rule is a restricted one but no valid authorising user \
was given"
);
false
},
| JoinRule::Public => true,
| _ => {
warn!(
join_rule=?join_rules,
membership=?target_user_current_membership,
"Unknown join rule doesn't allow joining, or the rule's conditions were not met"
);
false
},
} else if (join_rules == JoinRule::Invite
|| room_version.allow_knocking && (join_rules == JoinRule::Knock || matches!(join_rules, JoinRule::KnockRestricted(_))))
// If the join_rule is invite then allow if membership state is invite or join
&& (target_user_current_membership == MembershipState::Join
|| target_user_current_membership == MembershipState::Invite)
{
true
} else if room_version.restricted_join_rules
&& matches!(join_rules, JoinRule::Restricted(_))
|| room_version.knock_restricted_join_rule
&& matches!(join_rules, JoinRule::KnockRestricted(_))
{
// If the join_rule is restricted or knock_restricted
if matches!(
target_user_current_membership,
MembershipState::Invite | MembershipState::Join
) {
// If membership state is join or invite, allow.
true
} else {
// If the join_authorised_via_users_server key in content is not a user with
// sufficient permission to invite other users, reject.
// Otherwise, allow.
user_for_join_auth_is_valid
}
} else {
// If the join_rule is public, allow.
// Otherwise, reject.
join_rules == JoinRule::Public
}
},
| MembershipState::Invite => {
// If content has third_party_invite key
trace!("starting target_membership=invite check");
match third_party_invite.and_then(|i| i.deserialize().ok()) {
| Some(tp_id) =>
if target_user_current_membership == MembershipState::Ban {
@@ -886,10 +722,9 @@ struct GetThirdPartyInvite {
);
false
} else {
let allow = sender_creator
|| sender_power
.filter(|&p| p >= &power_levels.invite)
.is_some();
let allow = sender_power
.filter(|&p| p >= &power_levels.invite)
.is_some();
if !allow {
warn!(
?target_user_membership_event_id,
@@ -917,8 +752,7 @@ struct GetThirdPartyInvite {
allow
} else if !sender_is_joined
|| target_user_current_membership == MembershipState::Ban
&& (sender_creator
|| sender_power.filter(|&p| p < &power_levels.ban).is_some())
&& sender_power.filter(|&p| p < &power_levels.ban).is_some()
{
warn!(
?target_user_membership_event_id,
@@ -927,9 +761,8 @@ struct GetThirdPartyInvite {
);
false
} else {
let allow = sender_creator
|| (sender_power.filter(|&p| p >= &power_levels.kick).is_some()
&& target_power < sender_power);
let allow = sender_power.filter(|&p| p >= &power_levels.kick).is_some()
&& target_power < sender_power;
if !allow {
warn!(
?target_user_membership_event_id,
@@ -944,9 +777,8 @@ struct GetThirdPartyInvite {
warn!(?sender_membership_event_id, "Can't ban user if sender is not joined");
false
} else {
let allow = sender_creator
|| (sender_power.filter(|&p| p >= &power_levels.ban).is_some()
&& target_power < sender_power);
let allow = sender_power.filter(|&p| p >= &power_levels.ban).is_some()
&& target_power < sender_power;
if !allow {
warn!(
?target_user_membership_event_id,
@@ -1011,14 +843,12 @@ struct GetThirdPartyInvite {
/// Does the event have the correct userId as its state_key if it's not the ""
/// state_key.
fn can_send_event(event: &impl Event, ple: Option<&impl Event>, user_level: Int) -> bool {
// TODO(hydra): This function does not care about creators!
let event_type_power_level = get_send_level(event.event_type(), event.state_key(), ple);
debug!(
required_level = i64::from(event_type_power_level),
user_level = i64::from(user_level),
state_key = ?event.state_key(),
power_level_event_id = ?ple.map(|e| e.event_id().as_str()),
"permissions factors",
);
@@ -1042,7 +872,6 @@ fn check_power_levels(
previous_power_event: Option<&impl Event>,
user_level: Int,
) -> Option<bool> {
// TODO(hydra): This function does not care about creators!
match power_event.state_key() {
| Some("") => {},
| Some(key) => {
+10 -132
View File
@@ -38,7 +38,6 @@
use crate::{
debug, debug_error,
matrix::{Event, StateKey},
state_res::room_version::StateResolutionVersion,
trace,
utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt, WidebandExt},
warn,
@@ -93,12 +92,7 @@ pub async fn resolve<'a, Pdu, Sets, SetIter, Hasher, Fetch, FetchFut, Exists, Ex
Pdu: Event + Clone + Send + Sync,
for<'b> &'b Pdu: Event + Send,
{
use RoomVersionId::*;
let stateres_version = match room_version {
| V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 | V11 => StateResolutionVersion::V2,
| _ => StateResolutionVersion::V2_1,
};
debug!(version = ?stateres_version, "State resolution starting");
debug!("State resolution starting");
// Split non-conflicting and conflicting state
let (clean, conflicting) = separate(state_sets.into_iter());
@@ -113,27 +107,14 @@ pub async fn resolve<'a, Pdu, Sets, SetIter, Hasher, Fetch, FetchFut, Exists, Ex
debug!(count = conflicting.len(), "conflicting events");
trace!(map = ?conflicting, "conflicting events");
let conflicted_state_subgraph: HashSet<_> = match stateres_version {
| StateResolutionVersion::V2_1 =>
calculate_conflicted_subgraph(&conflicting, event_fetch)
.await
.ok_or_else(|| {
Error::InvalidPdu("Failed to calculate conflicted subgraph".to_owned())
})?,
| _ => HashSet::new(),
};
debug!(count = conflicted_state_subgraph.len(), "conflicted subgraph");
trace!(set = ?conflicted_state_subgraph, "conflicted subgraph");
let conflicting_values = conflicting.into_values().flatten().stream();
// `all_conflicted` contains unique items
// synapse says `full_set = {eid for eid in full_conflicted_set if eid in
// event_map}`
// Hydra: Also consider the conflicted state subgraph
let all_conflicted: HashSet<_> = get_auth_chain_diff(auth_chain_sets)
.chain(conflicting_values)
.chain(conflicted_state_subgraph.into_iter().stream())
.broad_filter_map(async |id| event_exists(id.clone()).await.then_some(id))
.collect()
.await;
@@ -169,7 +150,6 @@ pub async fn resolve<'a, Pdu, Sets, SetIter, Hasher, Fetch, FetchFut, Exists, Ex
// Sequentially auth check each control event.
let resolved_control = iterative_auth_check(
&room_version,
&stateres_version,
sorted_control_levels.iter().stream().map(AsRef::as_ref),
clean.clone(),
&event_fetch,
@@ -183,9 +163,6 @@ pub async fn resolve<'a, Pdu, Sets, SetIter, Hasher, Fetch, FetchFut, Exists, Ex
// sort the remaining events using the mainline of the resolved power level.
let deduped_power_ev: HashSet<_> = sorted_control_levels.into_iter().collect();
debug!(count = deduped_power_ev.len(), "deduped power events");
trace!(set = ?deduped_power_ev, "deduped power events");
// This removes the control events that passed auth and more importantly those
// that failed auth
let events_to_resolve: Vec<_> = all_conflicted
@@ -206,13 +183,12 @@ pub async fn resolve<'a, Pdu, Sets, SetIter, Hasher, Fetch, FetchFut, Exists, Ex
let sorted_left_events =
mainline_sort(&events_to_resolve, power_event.cloned(), &event_fetch).await?;
trace!(list = ?sorted_left_events, "events left, sorted, running iterative auth check");
trace!(list = ?sorted_left_events, "events left, sorted");
let mut resolved_state = iterative_auth_check(
&room_version,
&stateres_version,
sorted_left_events.iter().stream().map(AsRef::as_ref),
resolved_control.clone(), // The control events are added to the final resolved state
resolved_control, // The control events are added to the final resolved state
&event_fetch,
)
.await?;
@@ -220,14 +196,8 @@ pub async fn resolve<'a, Pdu, Sets, SetIter, Hasher, Fetch, FetchFut, Exists, Ex
// Add unconflicted state to the resolved state
// We priorities the unconflicting state
resolved_state.extend(clean);
if stateres_version == StateResolutionVersion::V2_1 {
resolved_state.extend(resolved_control);
// TODO(hydra): this feels disgusting and wrong but it allows
// the state to resolve properly?
}
debug!("state resolution finished");
trace!( map = ?resolved_state, "final resolved state" );
Ok(resolved_state)
}
@@ -280,52 +250,6 @@ fn separate<'a, Id>(
(unconflicted_state, conflicted_state)
}
/// Calculate the conflicted subgraph
async fn calculate_conflicted_subgraph<F, Fut, E>(
conflicted: &StateMap<Vec<OwnedEventId>>,
fetch_event: &F,
) -> Option<HashSet<OwnedEventId>>
where
F: Fn(OwnedEventId) -> Fut + Sync,
Fut: Future<Output = Option<E>> + Send,
E: Event + Send + Sync,
{
let conflicted_events: HashSet<_> = conflicted.values().flatten().cloned().collect();
let mut subgraph: HashSet<OwnedEventId> = HashSet::new();
let mut stack: Vec<Vec<OwnedEventId>> =
vec![conflicted_events.iter().cloned().collect::<Vec<_>>()];
let mut path: Vec<OwnedEventId> = Vec::new();
let mut seen: HashSet<OwnedEventId> = HashSet::new();
let next_event = |stack: &mut Vec<Vec<_>>, path: &mut Vec<_>| {
while stack.last().is_some_and(Vec::is_empty) {
stack.pop();
path.pop();
}
stack.last_mut().and_then(Vec::pop)
};
while let Some(event_id) = next_event(&mut stack, &mut path) {
path.push(event_id.clone());
if subgraph.contains(&event_id) {
if path.len() > 1 {
subgraph.extend(path.iter().cloned());
}
path.pop();
continue;
}
if conflicted_events.contains(&event_id) && path.len() > 1 {
subgraph.extend(path.iter().cloned());
}
if seen.contains(&event_id) {
path.pop();
continue;
}
let evt = fetch_event(event_id.clone()).await?;
stack.push(evt.auth_events().map(ToOwned::to_owned).collect());
seen.insert(event_id);
}
Some(subgraph)
}
/// Returns a Vec of deduped EventIds that appear in some chains but not others.
#[allow(clippy::arithmetic_side_effects)]
fn get_auth_chain_diff<Id, Hasher>(
@@ -589,10 +513,8 @@ async fn get_power_level_for_sender<E, F, Fut>(
/// For each `events_to_check` event we gather the events needed to auth it from
/// the the `fetch_event` closure and verify each event using the
/// `event_auth::auth_check` function.
#[tracing::instrument(level = "trace", skip_all)]
async fn iterative_auth_check<'a, E, F, Fut, S>(
room_version: &RoomVersion,
stateres_version: &StateResolutionVersion,
events_to_check: S,
unconflicted_state: StateMap<OwnedEventId>,
fetch_event: &F,
@@ -616,15 +538,12 @@ async fn iterative_auth_check<'a, E, F, Fut, S>(
.try_collect()
.boxed()
.await?;
trace!(list = ?events_to_check, "events to check");
let auth_event_ids: HashSet<OwnedEventId> = events_to_check
.iter()
.flat_map(|event: &E| event.auth_events().map(ToOwned::to_owned))
.collect();
trace!(set = ?auth_event_ids, "auth event IDs to fetch");
let auth_events: HashMap<OwnedEventId, E> = auth_event_ids
.into_iter()
.stream()
@@ -634,15 +553,9 @@ async fn iterative_auth_check<'a, E, F, Fut, S>(
.boxed()
.await;
trace!(map = ?auth_events.keys().collect::<Vec<_>>(), "fetched auth events");
let auth_events = &auth_events;
let mut resolved_state = match stateres_version {
| StateResolutionVersion::V2_1 => StateMap::new(),
| _ => unconflicted_state,
};
let mut resolved_state = unconflicted_state;
for event in events_to_check {
trace!(event_id = event.event_id().as_str(), "checking event");
let state_key = event
.state_key()
.ok_or_else(|| Error::InvalidPdu("State event had no state key".to_owned()))?;
@@ -652,29 +565,13 @@ async fn iterative_auth_check<'a, E, F, Fut, S>(
event.sender(),
Some(state_key),
event.content(),
room_version,
)?;
trace!(list = ?auth_types, event_id = event.event_id().as_str(), "auth types for event");
let mut auth_state = StateMap::new();
if room_version.room_ids_as_hashes {
trace!("room version uses hashed IDs, manually fetching create event");
let create_event_id_raw = event.room_id_or_hash().as_str().replace('!', "$");
let create_event_id = EventId::parse(&create_event_id_raw).map_err(|e| {
Error::InvalidPdu(format!(
"Failed to parse create event ID from room ID/hash: {e}"
))
})?;
let create_event = fetch_event(create_event_id.into())
.await
.ok_or_else(|| Error::NotFound("Failed to find create event".into()))?;
auth_state.insert(create_event.event_type().with_state_key(""), create_event);
}
for aid in event.auth_events() {
if let Some(ev) = auth_events.get(aid) {
//TODO: synapse checks "rejected_reason" which is most likely related to
// soft-failing
trace!(event_id = aid.as_str(), "found auth event");
auth_state.insert(
ev.event_type()
.with_state_key(ev.state_key().ok_or_else(|| {
@@ -703,9 +600,8 @@ async fn iterative_auth_check<'a, E, F, Fut, S>(
auth_state.insert(key.to_owned(), event);
})
.await;
trace!(map = ?auth_state.keys().collect::<Vec<_>>(), event_id = event.event_id().as_str(), "auth state for event");
debug!(event_id = event.event_id().as_str(), "Running auth checks");
debug!("event to check {:?}", event.event_id());
// The key for this is (eventType + a state_key of the signed token not sender)
// so search for it
@@ -721,29 +617,16 @@ async fn iterative_auth_check<'a, E, F, Fut, S>(
)
};
let auth_result = auth_check(
room_version,
&event,
current_third_party,
fetch_state,
&fetch_state(&StateEventType::RoomCreate, "")
.await
.expect("create event must exist"),
)
.await;
let auth_result =
auth_check(room_version, &event, current_third_party, fetch_state).await;
match auth_result {
| Ok(true) => {
// add event to resolved state map
trace!(
event_id = event.event_id().as_str(),
"event passed the authentication check, adding to resolved state"
);
resolved_state.insert(
event.event_type().with_state_key(state_key),
event.event_id().to_owned(),
);
trace!(map = ?resolved_state, "new resolved state");
},
| Ok(false) => {
// synapse passes here on AuthError. We do not add this event to resolved_state.
@@ -755,8 +638,7 @@ async fn iterative_auth_check<'a, E, F, Fut, S>(
},
}
}
trace!(map = ?resolved_state, "final resolved state from iterative auth check");
debug!("iterative auth check finished");
Ok(resolved_state)
}
@@ -995,7 +877,6 @@ mod tests {
use crate::{
debug,
matrix::{Event, EventTypeExt, Pdu as PduEvent},
state_res::room_version::StateResolutionVersion,
utils::stream::IterStream,
};
@@ -1028,7 +909,6 @@ async fn test_event_sort() {
let resolved_power = super::iterative_auth_check(
&RoomVersion::V6,
&StateResolutionVersion::V2,
sorted_power_events.iter().map(AsRef::as_ref).stream(),
HashMap::new(), // unconflicted events
&fetcher,
@@ -1067,8 +947,7 @@ async fn test_event_sort() {
);
}
// NOTE(2025-09-17): Disabled due to unknown "create event must exist" bug
// #[tokio::test]
#[tokio::test]
async fn test_sort() {
for _ in 0..20 {
// since we shuffle the eventIds before we sort them introducing randomness
@@ -1077,8 +956,7 @@ async fn test_sort() {
}
}
// NOTE(2025-09-17): Disabled due to unknown "create event must exist" bug
//#[tokio::test]
#[tokio::test]
async fn ban_vs_power_level() {
let _ = tracing::subscriber::set_default(
tracing_subscriber::fmt().with_test_writer().finish(),
+6 -25
View File
@@ -22,15 +22,13 @@ pub enum EventFormatVersion {
V3,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub enum StateResolutionVersion {
/// State resolution for rooms at version 1.
V1,
/// State resolution for room at version 2 or later.
V2,
/// State resolution for room at version 12 or later.
V2_1,
}
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
@@ -63,34 +61,25 @@ pub struct RoomVersion {
pub extra_redaction_checks: bool,
/// Allow knocking in event authentication.
///
/// See [room v7 specification](https://spec.matrix.org/latest/rooms/v7/)
/// See [room v7 specification](https://spec.matrix.org/latest/rooms/v7/) for more information.
pub allow_knocking: bool,
/// Adds support for the restricted join rule.
///
/// See: [MSC3289](https://github.com/matrix-org/matrix-spec-proposals/pull/3289)
/// See: [MSC3289](https://github.com/matrix-org/matrix-spec-proposals/pull/3289) for more information.
pub restricted_join_rules: bool,
/// Adds support for the knock_restricted join rule.
///
/// See: [MSC3787](https://github.com/matrix-org/matrix-spec-proposals/pull/3787)
/// See: [MSC3787](https://github.com/matrix-org/matrix-spec-proposals/pull/3787) for more information.
pub knock_restricted_join_rule: bool,
/// Enforces integer power levels.
///
/// See: [MSC3667](https://github.com/matrix-org/matrix-spec-proposals/pull/3667)
/// See: [MSC3667](https://github.com/matrix-org/matrix-spec-proposals/pull/3667) for more information.
pub integer_power_levels: bool,
/// Determine the room creator using the `m.room.create` event's `sender`,
/// instead of the event content's `creator` field.
///
/// See: [MSC2175](https://github.com/matrix-org/matrix-spec-proposals/pull/2175)
/// See: [MSC2175](https://github.com/matrix-org/matrix-spec-proposals/pull/2175) for more information.
pub use_room_create_sender: bool,
/// Whether the room creators are considered superusers.
/// A superuser will always have infinite power levels in the room.
///
/// See: [MSC4289](https://github.com/matrix-org/matrix-spec-proposals/pull/4289)
pub explicitly_privilege_room_creators: bool,
/// Whether the room's m.room.create event ID is itself the room ID.
///
/// See: [MSC4291](https://github.com/matrix-org/matrix-spec-proposals/pull/4291)
pub room_ids_as_hashes: bool,
}
impl RoomVersion {
@@ -108,8 +97,6 @@ impl RoomVersion {
knock_restricted_join_rule: false,
integer_power_levels: false,
use_room_create_sender: false,
explicitly_privilege_room_creators: false,
room_ids_as_hashes: false,
};
pub const V10: Self = Self {
knock_restricted_join_rule: true,
@@ -120,11 +107,6 @@ impl RoomVersion {
use_room_create_sender: true,
..Self::V10
};
pub const V12: Self = Self {
explicitly_privilege_room_creators: true,
room_ids_as_hashes: true,
..Self::V11
};
pub const V2: Self = Self {
state_res: StateResolutionVersion::V2,
..Self::V1
@@ -162,7 +144,6 @@ pub fn new(version: &RoomVersionId) -> Result<Self> {
| RoomVersionId::V9 => Self::V9,
| RoomVersionId::V10 => Self::V10,
| RoomVersionId::V11 => Self::V11,
| RoomVersionId::V12 => Self::V12,
| ver => return Err(Error::Unsupported(format!("found version `{ver}`"))),
})
}
+3 -4
View File
@@ -24,7 +24,7 @@
use super::auth_types_for_event;
use crate::{
Result, RoomVersion, info,
Result, info,
matrix::{Event, EventTypeExt, Pdu, StateMap, pdu::EventHash},
};
@@ -154,7 +154,6 @@ pub(crate) async fn do_check(
fake_event.sender(),
fake_event.state_key(),
fake_event.content(),
&RoomVersion::V6,
)
.unwrap();
@@ -399,7 +398,7 @@ pub(crate) fn to_init_pdu_event(
Pdu {
event_id: id.try_into().unwrap(),
room_id: Some(room_id().to_owned()),
room_id: room_id().to_owned(),
sender: sender.to_owned(),
origin_server_ts: ts.try_into().unwrap(),
state_key: state_key.map(Into::into),
@@ -447,7 +446,7 @@ pub(crate) fn to_pdu_event<S>(
Pdu {
event_id: id.try_into().unwrap(),
room_id: Some(room_id().to_owned()),
room_id: room_id().to_owned(),
sender: sender.to_owned(),
origin_server_ts: ts.try_into().unwrap(),
state_key: state_key.map(Into::into),
+2 -2
View File
@@ -65,7 +65,7 @@ fn expect_false(self, msg: &str) -> Self { (!self).then_some(false).expect(msg)
fn into_option(self) -> Option<()> { self.then_some(()) }
#[inline]
fn into_result(self) -> Result<(), ()> { BoolExt::ok_or(self, ()) }
fn into_result(self) -> Result<(), ()> { self.ok_or(()) }
#[inline]
fn map<T, F: FnOnce(Self) -> T>(self, f: F) -> T
@@ -77,7 +77,7 @@ fn map<T, F: FnOnce(Self) -> T>(self, f: F) -> T
#[inline]
fn map_ok_or<T, E, F: FnOnce() -> T>(self, err: E, f: F) -> Result<T, E> {
BoolExt::ok_or(self, err).map(|()| f())
self.ok_or(err).map(|()| f())
}
#[inline]
+1 -1
View File
@@ -417,7 +417,7 @@ fn deserialize_ignored_any<V: Visitor<'de>>(self, _visitor: V) -> Result<V::Valu
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
debug_assert_eq!(
conduwuit::debug::type_name::<V>(),
"serde_json::value::de::<impl serde_core::de::Deserialize for \
"serde_json::value::de::<impl serde::de::Deserialize for \
serde_json::value::Value>::deserialize::ValueVisitor",
"deserialize_any: type not expected"
);
+1
View File
@@ -38,6 +38,7 @@ pub(crate) fn db_options(config: &Config, env: &Env, row_cache: &Cache) -> Resul
}
if config.rocksdb_optimize_for_spinning_disks {
// speeds up opening DB on hard drives
opts.set_skip_checking_sst_file_sizes_on_db_open(true);
opts.set_skip_stats_update_on_db_open(true);
//opts.set_max_file_opening_threads(threads.try_into().unwrap());
} else {
+1 -1
View File
@@ -227,7 +227,7 @@ pub fn insert_batch<'a, I, K, V>(&'a self, iter: I)
let write_options = &self.write_options;
self.db
.db
.write_opt(&batch, write_options)
.write_opt(batch, write_options)
.or_else(or_else)
.expect("database insert batch error");
-4
View File
@@ -434,8 +434,4 @@ pub(super) fn open_list(db: &Arc<Engine>, maps: &[Descriptor]) -> Result<Maps> {
name: "userroomid_notificationcount",
..descriptor::RANDOM
},
Descriptor {
name: "userroomid_invitesender",
..descriptor::RANDOM_SMALL
},
];
+1 -1
View File
@@ -105,7 +105,7 @@ rustyline-async.workspace = true
rustyline-async.optional = true
serde_json.workspace = true
serde.workspace = true
serde_yml.workspace = true
serde_yaml.workspace = true
sha2.workspace = true
termimad.workspace = true
termimad.optional = true
+13 -16
View File
@@ -1,6 +1,6 @@
use std::collections::BTreeMap;
use conduwuit::{Result, info, pdu::PduBuilder};
use conduwuit::{Result, pdu::PduBuilder};
use futures::FutureExt;
use ruma::{
RoomId, RoomVersionId,
@@ -26,7 +26,7 @@
/// used to issue admin commands by talking to the server user inside it.
pub async fn create_admin_room(services: &Services) -> Result {
let room_id = RoomId::new(services.globals.server_name());
let room_version = &RoomVersionId::V11;
let room_version = &services.config.default_room_version;
let _short_id = services
.rooms
@@ -45,13 +45,10 @@ pub async fn create_admin_room(services: &Services) -> Result {
match room_version {
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
RoomCreateEventContent::new_v1(server_user.into()),
| V11 => RoomCreateEventContent::new_v11(),
| _ => RoomCreateEventContent::new_v12(),
| _ => RoomCreateEventContent::new_v11(),
}
};
info!("Creating admin room {} with version {}", room_id, room_version);
// 1. The room create event
services
.rooms
@@ -64,7 +61,7 @@ pub async fn create_admin_room(services: &Services) -> Result {
..create_content
}),
server_user,
Some(&room_id),
&room_id,
&state_lock,
)
.boxed()
@@ -80,7 +77,7 @@ pub async fn create_admin_room(services: &Services) -> Result {
&RoomMemberEventContent::new(MembershipState::Join),
),
server_user,
Some(&room_id),
&room_id,
&state_lock,
)
.boxed()
@@ -98,7 +95,7 @@ pub async fn create_admin_room(services: &Services) -> Result {
..Default::default()
}),
server_user,
Some(&room_id),
&room_id,
&state_lock,
)
.boxed()
@@ -111,7 +108,7 @@ pub async fn create_admin_room(services: &Services) -> Result {
.build_and_append_pdu(
PduBuilder::state(String::new(), &RoomJoinRulesEventContent::new(JoinRule::Invite)),
server_user,
Some(&room_id),
&room_id,
&state_lock,
)
.boxed()
@@ -127,7 +124,7 @@ pub async fn create_admin_room(services: &Services) -> Result {
&RoomHistoryVisibilityEventContent::new(HistoryVisibility::Shared),
),
server_user,
Some(&room_id),
&room_id,
&state_lock,
)
.boxed()
@@ -143,7 +140,7 @@ pub async fn create_admin_room(services: &Services) -> Result {
&RoomGuestAccessEventContent::new(GuestAccess::Forbidden),
),
server_user,
Some(&room_id),
&room_id,
&state_lock,
)
.boxed()
@@ -157,7 +154,7 @@ pub async fn create_admin_room(services: &Services) -> Result {
.build_and_append_pdu(
PduBuilder::state(String::new(), &RoomNameEventContent::new(room_name)),
server_user,
Some(&room_id),
&room_id,
&state_lock,
)
.boxed()
@@ -171,7 +168,7 @@ pub async fn create_admin_room(services: &Services) -> Result {
topic: format!("Manage {} | Run commands prefixed with `!admin` | Run `!admin -h` for help | Documentation: https://continuwuity.org/", services.config.server_name),
}),
server_user,
Some(&room_id),
&room_id,
&state_lock,
)
.boxed()
@@ -189,7 +186,7 @@ pub async fn create_admin_room(services: &Services) -> Result {
alt_aliases: Vec::new(),
}),
server_user,
Some(&room_id),
&room_id,
&state_lock,
)
.boxed()
@@ -207,7 +204,7 @@ pub async fn create_admin_room(services: &Services) -> Result {
.build_and_append_pdu(
PduBuilder::state(String::new(), &RoomPreviewUrlsEventContent { disabled: true }),
server_user,
Some(&room_id),
&room_id,
&state_lock,
)
.boxed()
+6 -6
View File
@@ -55,7 +55,7 @@ pub async fn make_user_admin(&self, user_id: &UserId) -> Result {
&RoomMemberEventContent::new(MembershipState::Invite),
),
server_user,
Some(&room_id),
&room_id,
&state_lock,
)
.await?;
@@ -69,7 +69,7 @@ pub async fn make_user_admin(&self, user_id: &UserId) -> Result {
&RoomMemberEventContent::new(MembershipState::Join),
),
user_id,
Some(&room_id),
&room_id,
&state_lock,
)
.await?;
@@ -83,7 +83,7 @@ pub async fn make_user_admin(&self, user_id: &UserId) -> Result {
&RoomMemberEventContent::new(MembershipState::Invite),
),
server_user,
Some(&room_id),
&room_id,
&state_lock,
)
.await?;
@@ -111,7 +111,7 @@ pub async fn make_user_admin(&self, user_id: &UserId) -> Result {
.build_and_append_pdu(
PduBuilder::state(String::new(), &room_power_levels),
server_user,
Some(&room_id),
&room_id,
&state_lock,
)
.await?;
@@ -135,7 +135,7 @@ pub async fn make_user_admin(&self, user_id: &UserId) -> Result {
.build_and_append_pdu(
PduBuilder::timeline(&RoomMessageEventContent::text_markdown(welcome_message)),
server_user,
Some(&room_id),
&room_id,
&state_lock,
)
.await?;
@@ -218,7 +218,7 @@ pub async fn revoke_admin(&self, user_id: &UserId) -> Result {
..event
}),
self.services.globals.server_user.as_ref(),
Some(&room_id),
&room_id,
&state_lock,
)
.await
+6 -15
View File
@@ -393,13 +393,13 @@ async fn handle_response(&self, content: RoomMessageEventContent) -> Result<()>
return Ok(());
};
let response_sender = if self.is_admin_room(pdu.room_id().unwrap()).await {
let response_sender = if self.is_admin_room(pdu.room_id()).await {
&self.services.globals.server_user
} else {
pdu.sender()
};
self.respond_to_room(content, pdu.room_id().unwrap(), response_sender)
self.respond_to_room(content, pdu.room_id(), response_sender)
.boxed()
.await
}
@@ -419,13 +419,12 @@ async fn respond_to_room(
.build_and_append_pdu(
PduBuilder::timeline(&self.text_or_file(content).await),
user_id,
Some(room_id),
room_id,
&state_lock,
)
.await
{
self.handle_response_error(e, room_id, user_id, &state_lock)
.boxed()
.await
.unwrap_or_else(default_log);
}
@@ -448,12 +447,7 @@ async fn handle_response_error(
self.services
.timeline
.build_and_append_pdu(
PduBuilder::timeline(&content),
user_id,
Some(room_id),
state_lock,
)
.build_and_append_pdu(PduBuilder::timeline(&content), user_id, room_id, state_lock)
.await?;
Ok(())
@@ -490,10 +484,7 @@ pub async fn is_admin_command<E>(&self, event: &E, body: &str) -> bool
}
// Prevent unescaped !admin from being used outside of the admin room
if event.room_id().is_some()
&& is_public_prefix
&& !self.is_admin_room(event.room_id().unwrap()).await
{
if is_public_prefix && !self.is_admin_room(event.room_id()).await {
return false;
}
@@ -506,7 +497,7 @@ pub async fn is_admin_command<E>(&self, event: &E, body: &str) -> bool
// the administrator can execute commands as the server user
let emergency_password_set = self.services.server.config.emergency_password.is_some();
let from_server = event.sender() == server_user && !emergency_password_set;
if from_server && self.is_admin_room(event.room_id().unwrap()).await {
if from_server && self.is_admin_room(event.room_id()).await {
return false;
}
+1 -1
View File
@@ -271,7 +271,7 @@ pub async fn get_db_registration(&self, id: &str) -> Result<Registration> {
.id_appserviceregistrations
.get(id)
.await
.and_then(|ref bytes| serde_yml::from_slice(bytes).map_err(Into::into))
.and_then(|ref bytes| serde_yaml::from_slice(bytes).map_err(Into::into))
.map_err(|e| err!(Database("Invalid appservice {id:?} registration: {e:?}")))
}
+12 -11
View File
@@ -29,19 +29,20 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
let db = Data::new(&args);
let config = &args.server.config;
let turn_secret = config.turn_secret_file.as_ref().map_or_else(
|| config.turn_secret.clone(),
|path| {
std::fs::read_to_string(path).unwrap_or_else(|e| {
error!("Failed to read the TURN secret file: {e}");
let turn_secret =
config
.turn_secret_file
.as_ref()
.map_or(config.turn_secret.clone(), |path| {
std::fs::read_to_string(path).unwrap_or_else(|e| {
error!("Failed to read the TURN secret file: {e}");
config.turn_secret.clone()
})
},
);
config.turn_secret.clone()
})
});
let registration_token = config.registration_token_file.as_ref().map_or_else(
|| config.registration_token.clone(),
let registration_token = config.registration_token_file.as_ref().map_or(
config.registration_token.clone(),
|path| {
let Ok(token) = std::fs::read_to_string(path).inspect_err(|e| {
error!("Failed to read the registration token file: {e}");
+1 -1
View File
@@ -372,7 +372,7 @@ pub async fn fetch_remote_thumbnail_legacy(
})
.await?;
let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?;
let dim = Dim::from_ruma(body.width, body.height, body.method.clone(), body.animated)?;
self.upload_thumbnail(
&mxc,
None,
+22 -11
View File
@@ -16,12 +16,13 @@
use super::{FileMeta, data::Metadata};
/// Dimension specification for a thumbnail.
/// Dimension and format specification for a thumbnail.
#[derive(Debug)]
pub struct Dim {
pub width: u32,
pub height: u32,
pub method: Method,
pub animated: bool,
}
impl super::Service {
@@ -179,8 +180,14 @@ fn into_filemeta(data: Metadata, content: Vec<u8>) -> FileMeta {
}
impl Dim {
/// Instantiate a Dim from Ruma integers with optional method.
pub fn from_ruma(width: UInt, height: UInt, method: Option<Method>) -> Result<Self> {
/// Instantiate a Dim from Ruma integers with optional method and animation
/// flag.
pub fn from_ruma(
width: UInt,
height: UInt,
method: Option<Method>,
animated: Option<bool>,
) -> Result<Self> {
let width = width
.try_into()
.map_err(|e| err!(Request(InvalidParam("Width is invalid: {e:?}"))))?;
@@ -188,17 +195,19 @@ pub fn from_ruma(width: UInt, height: UInt, method: Option<Method>) -> Result<Se
.try_into()
.map_err(|e| err!(Request(InvalidParam("Height is invalid: {e:?}"))))?;
Ok(Self::new(width, height, method))
Ok(Self::new(width, height, method, animated))
}
/// Instantiate a Dim with optional method
/// Instantiate a Dim with optional method and animation flag.
#[inline]
#[must_use]
pub fn new(width: u32, height: u32, method: Option<Method>) -> Self {
pub fn new(width: u32, height: u32, method: Option<Method>, animated: Option<bool>) -> Self {
Self {
width,
height,
method: method.unwrap_or(Method::Scale),
// "When not provided, the server SHOULD NOT return an animated thumbnail"
animated: animated.unwrap_or(false),
}
}
@@ -229,6 +238,7 @@ pub fn scaled(&self, image: &Self) -> Result<Self> {
width: x,
height: y,
method: Method::Scale,
animated: self.animated,
})
}
@@ -238,11 +248,11 @@ pub fn scaled(&self, image: &Self) -> Result<Self> {
#[must_use]
pub fn normalized(&self) -> Self {
match (self.width, self.height) {
| (0..=32, 0..=32) => Self::new(32, 32, Some(Method::Crop)),
| (0..=96, 0..=96) => Self::new(96, 96, Some(Method::Crop)),
| (0..=320, 0..=240) => Self::new(320, 240, Some(Method::Scale)),
| (0..=640, 0..=480) => Self::new(640, 480, Some(Method::Scale)),
| (0..=800, 0..=600) => Self::new(800, 600, Some(Method::Scale)),
| (0..=32, 0..=32) => Self::new(32, 32, Some(Method::Crop), Some(self.animated)),
| (0..=96, 0..=96) => Self::new(96, 96, Some(Method::Crop), Some(self.animated)),
| (0..=320, 0..=240) => Self::new(320, 240, Some(Method::Scale), Some(self.animated)),
| (0..=640, 0..=480) => Self::new(640, 480, Some(Method::Scale), Some(self.animated)),
| (0..=800, 0..=600) => Self::new(800, 600, Some(Method::Scale), Some(self.animated)),
| _ => Self::default(),
}
}
@@ -260,6 +270,7 @@ fn default() -> Self {
width: 0,
height: 0,
method: Method::Scale,
animated: false,
}
}
}
+2 -66
View File
@@ -9,7 +9,7 @@
},
warn,
};
use futures::{FutureExt, StreamExt, TryStreamExt};
use futures::{FutureExt, StreamExt};
use itertools::Itertools;
use ruma::{
OwnedUserId, RoomId, UserId,
@@ -27,7 +27,7 @@
/// - If database is opened at lesser version we apply migrations up to this.
/// Note that named-feature migrations may also be performed when opening at
/// equal or lesser version. These are expected to be backward-compatible.
pub(crate) const DATABASE_VERSION: u64 = 18;
pub(crate) const DATABASE_VERSION: u64 = 17;
pub(crate) async fn migrations(services: &Services) -> Result<()> {
let users_count = services.users.count().await;
@@ -138,19 +138,6 @@ async fn migrate(services: &Services) -> Result<()> {
info!("Migration: Bumped database version to 17");
}
if db["global"]
.get(FIXED_CORRUPT_MSC4133_FIELDS_MARKER)
.await
.is_not_found()
{
fix_corrupt_msc4133_fields(services).await?;
}
if services.globals.db.database_version().await < 18 {
services.globals.db.bump_database_version(18);
info!("Migration: Bumped database version to 18");
}
assert_eq!(
services.globals.db.database_version().await,
DATABASE_VERSION,
@@ -572,54 +559,3 @@ async fn fix_readreceiptid_readreceipt_duplicates(services: &Services) -> Result
db["global"].insert(b"fix_readreceiptid_readreceipt_duplicates", []);
db.db.sort()
}
const FIXED_CORRUPT_MSC4133_FIELDS_MARKER: &[u8] = b"fix_corrupt_msc4133_fields";
async fn fix_corrupt_msc4133_fields(services: &Services) -> Result {
use serde_json::{Value, from_slice};
type KeyVal<'a> = ((OwnedUserId, String), &'a [u8]);
warn!("Fixing corrupted `us.cloke.msc4175.tz` fields...");
let db = &services.db;
let cork = db.cork_and_sync();
let useridprofilekey_value = db["useridprofilekey_value"].clone();
let (total, fixed) = useridprofilekey_value
.stream()
.try_fold(
(0_usize, 0_usize),
async |(mut total, mut fixed),
((user, key), value): KeyVal<'_>|
-> Result<(usize, usize)> {
if let Err(error) = from_slice::<Value>(value) {
// Due to an old bug, some conduwuit databases have `us.cloke.msc4175.tz` user
// profile fields with raw strings instead of quoted JSON ones.
// This migration fixes that.
let new_value = if key == "us.cloke.msc4175.tz" {
Value::String(String::from_utf8(value.to_vec())?)
} else {
return Err!(
"failed to deserialize msc4133 key {} of user {}: {}",
key,
user,
error
);
};
useridprofilekey_value.put((user, key), new_value);
fixed = fixed.saturating_add(1);
}
total = total.saturating_add(1);
Ok((total, fixed))
},
)
.await?;
drop(cork);
info!(?total, ?fixed, "Fixed corrupted `us.cloke.msc4175.tz` fields.");
db["global"].insert(FIXED_CORRUPT_MSC4133_FIELDS_MARKER, []);
db.db.sort()?;
Ok(())
}
+17 -12
View File
@@ -287,22 +287,18 @@ pub async fn send_push_notice<E>(
{
let mut notify = None;
let mut tweaks = Vec::new();
if event.room_id().is_none() {
// TODO(hydra): does this matter?
return Ok(());
}
let power_levels: RoomPowerLevelsEventContent = self
.services
.state_accessor
.room_state_get(event.room_id().unwrap(), &StateEventType::RoomPowerLevels, "")
.room_state_get(event.room_id(), &StateEventType::RoomPowerLevels, "")
.await
.and_then(|event| event.get_content())
.unwrap_or_default();
let serialized = event.to_format();
for action in self
.get_actions(user, &ruleset, &power_levels, &serialized, event.room_id().unwrap())
.get_actions(user, &ruleset, &power_levels, &serialized, event.room_id())
.await
{
let n = match action {
@@ -430,7 +426,7 @@ async fn send_notice<E>(
let mut notifi = Notification::new(d);
notifi.event_id = Some(event.event_id().to_owned());
notifi.room_id = Some(event.room_id().unwrap().to_owned());
notifi.room_id = Some(event.room_id().to_owned());
if http
.data
.get("org.matrix.msc4076.disable_badge_count")
@@ -443,7 +439,13 @@ async fn send_notice<E>(
notifi.counts = NotificationCounts::default();
}
if !event_id_only {
if event_id_only {
self.send_request(
&http.url,
send_event_notification::v1::Request::new(notifi),
)
.await?;
} else {
if *event.kind() == TimelineEventType::RoomEncrypted
|| tweaks
.iter()
@@ -468,20 +470,23 @@ async fn send_notice<E>(
notifi.room_name = self
.services
.state_accessor
.get_name(event.room_id().unwrap())
.get_name(event.room_id())
.await
.ok();
notifi.room_alias = self
.services
.state_accessor
.get_canonical_alias(event.room_id().unwrap())
.get_canonical_alias(event.room_id())
.await
.ok();
}
self.send_request(&http.url, send_event_notification::v1::Request::new(notifi))
self.send_request(
&http.url,
send_event_notification::v1::Request::new(notifi),
)
.await?;
}
Ok(())
},
+7 -9
View File
@@ -195,15 +195,13 @@ async fn get_auth_chain_inner(
debug_error!(?event_id, ?e, "Could not find pdu mentioned in auth events");
},
| Ok(pdu) => {
if let Some(claimed_room_id) = pdu.room_id.clone() {
if claimed_room_id != *room_id {
return Err!(Request(Forbidden(error!(
?event_id,
?room_id,
wrong_room_id = ?pdu.room_id.unwrap(),
"auth event for incorrect room"
))));
}
if pdu.room_id != room_id {
return Err!(Request(Forbidden(error!(
?event_id,
?room_id,
wrong_room_id = ?pdu.room_id,
"auth event for incorrect room"
))));
}
for auth_event in &pdu.auth_events {
@@ -139,7 +139,6 @@ pub(super) async fn handle_outlier_pdu<'a, Pdu>(
&pdu_event,
None, // TODO: third party invite
state_fetch,
create_event.as_pdu(),
)
.await
.map_err(|e| err!(Request(Forbidden("Auth check failed: {e:?}"))))?;
+1 -4
View File
@@ -99,10 +99,7 @@ async fn event_fetch(&self, event_id: OwnedEventId) -> Option<PduEvent> {
}
fn check_room_id<Pdu: Event>(room_id: &RoomId, pdu: &Pdu) -> Result {
if pdu
.room_id()
.is_some_and(|claimed_room_id| claimed_room_id != room_id)
{
if pdu.room_id() != room_id {
return Err!(Request(InvalidParam(error!(
pdu_event_id = ?pdu.event_id(),
pdu_room_id = ?pdu.room_id(),
@@ -1,8 +1,7 @@
use conduwuit::{
Result, RoomVersion, err, implement, matrix::event::gen_event_id_canonical_json,
result::FlatOk,
Result, err, implement, matrix::event::gen_event_id_canonical_json, result::FlatOk,
};
use ruma::{CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, RoomVersionId};
use ruma::{CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId};
use serde_json::value::RawValue as RawJsonValue;
type Parsed = (OwnedRoomId, OwnedEventId, CanonicalJsonObject);
@@ -12,44 +11,12 @@ pub async fn parse_incoming_pdu(&self, pdu: &RawJsonValue) -> Result<Parsed> {
let value = serde_json::from_str::<CanonicalJsonObject>(pdu.get()).map_err(|e| {
err!(BadServerResponse(debug_warn!("Error parsing incoming event {e:?}")))
})?;
let event_type = value
.get("type")
.and_then(CanonicalJsonValue::as_str)
.ok_or_else(|| err!(Request(InvalidParam("Missing or invalid type in pdu"))))?;
let room_id: OwnedRoomId = if event_type != "m.room.create" {
value
.get("room_id")
.and_then(CanonicalJsonValue::as_str)
.map(OwnedRoomId::parse)
.flat_ok_or(err!(Request(InvalidParam("Invalid room_id in pdu"))))?
} else {
// v12 rooms might have no room_id in the create event. We'll need to check the
// content.room_version
let content = value
.get("content")
.and_then(CanonicalJsonValue::as_object)
.ok_or_else(|| err!(Request(InvalidParam("Missing or invalid content in pdu"))))?;
let room_version = content
.get("room_version")
.and_then(CanonicalJsonValue::as_str)
.unwrap_or("1");
let vi = RoomVersionId::try_from(room_version).unwrap_or(RoomVersionId::V1);
let vf = RoomVersion::new(&vi).expect("supported room version");
if vf.room_ids_as_hashes {
let (event_id, _) = gen_event_id_canonical_json(pdu, &vi).map_err(|e| {
err!(Request(InvalidParam("Could not convert event to canonical json: {e}")))
})?;
OwnedRoomId::parse(event_id.as_str().replace('$', "!")).expect("valid room ID")
} else {
// V11 or below room, room_id must be present
value
.get("room_id")
.and_then(CanonicalJsonValue::as_str)
.map(OwnedRoomId::parse)
.flat_ok_or(err!(Request(InvalidParam("Invalid or missing room_id in pdu"))))?
}
};
let room_id: OwnedRoomId = value
.get("room_id")
.and_then(CanonicalJsonValue::as_str)
.map(OwnedRoomId::parse)
.flat_ok_or(err!(Request(InvalidParam("Invalid room_id in pdu"))))?;
let room_version_id = self
.services
@@ -57,8 +24,10 @@ pub async fn parse_incoming_pdu(&self, pdu: &RawJsonValue) -> Result<Parsed> {
.get_room_version(&room_id)
.await
.map_err(|_| err!("Server is not in room {room_id}"))?;
let (event_id, value) = gen_event_id_canonical_json(pdu, &room_version_id).map_err(|e| {
err!(Request(InvalidParam("Could not convert event to canonical json: {e}")))
})?;
Ok((room_id, event_id, value))
}
@@ -102,7 +102,6 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu<Pdu>(
&incoming_pdu,
None, // TODO: third party invite
|ty, sk| state_fetch(ty.clone(), sk.into()),
create_event.as_pdu(),
)
.await
.map_err(|e| err!(Request(Forbidden("Auth check failed: {e:?}"))))?;
@@ -124,7 +123,6 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu<Pdu>(
incoming_pdu.sender(),
incoming_pdu.state_key(),
incoming_pdu.content(),
&room_version,
)
.await?;
@@ -142,7 +140,6 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu<Pdu>(
&incoming_pdu,
None, // third-party invite
state_fetch,
create_event.as_pdu(),
)
.await
.map_err(|e| err!(Request(Forbidden("Auth check failed: {e:?}"))))?;
@@ -159,7 +156,7 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu<Pdu>(
!self
.services
.state_accessor
.user_can_redact(&redact_id, incoming_pdu.sender(), room_id, true)
.user_can_redact(&redact_id, incoming_pdu.sender(), incoming_pdu.room_id(), true)
.await?,
};
@@ -175,7 +172,7 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu<Pdu>(
// Now we calculate the set of extremities this room has after the incoming
// event has been applied. We start with the previous extremities (aka leaves)
trace!("Calculating extremities");
let mut extremities: Vec<_> = self
let extremities: Vec<_> = self
.services
.state
.get_forward_extremities(room_id)
@@ -195,7 +192,6 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu<Pdu>(
})
.collect()
.await;
extremities.push(incoming_pdu.event_id().to_owned());
debug!(
"Retained {} extremities checked against {} prev_events",
@@ -307,7 +303,6 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu<Pdu>(
);
// assert!(extremities.is_empty(), "soft_fail extremities empty");
let extremities = extremities.iter().map(Borrow::borrow);
debug_assert!(extremities.clone().count() > 0, "extremities not empty");
self.services
.timeline
@@ -318,7 +313,6 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu<Pdu>(
state_ids_compressed,
soft_fail,
&state_lock,
room_id,
)
.await?;
@@ -342,7 +336,6 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu<Pdu>(
.iter()
.map(Borrow::borrow)
.chain(once(incoming_pdu.event_id()));
debug_assert!(extremities.clone().count() > 0, "extremities not empty");
let pdu_id = self
.services
@@ -354,7 +347,6 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu<Pdu>(
state_ids_compressed,
soft_fail,
&state_lock,
room_id,
)
.await?;
+1 -1
View File
@@ -124,7 +124,7 @@ pub async fn search_pdus<'a>(
.wide_filter_map(move |pdu| async move {
self.services
.state_accessor
.user_can_see_event(query.user_id?, pdu.room_id().unwrap(), pdu.event_id())
.user_can_see_event(query.user_id?, pdu.room_id(), pdu.event_id())
.await
.then_some(pdu)
})

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