mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b5e3a2d238 |
@@ -44,7 +44,7 @@ runs:
|
||||
|
||||
- name: Login to builtin registry
|
||||
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
with:
|
||||
registry: ${{ env.BUILTIN_REGISTRY }}
|
||||
username: ${{ inputs.registry_user }}
|
||||
@@ -52,7 +52,7 @@ runs:
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
with:
|
||||
# Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125)
|
||||
driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }}
|
||||
@@ -61,7 +61,7 @@ runs:
|
||||
- name: Extract metadata (tags) for Docker
|
||||
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
|
||||
id: meta
|
||||
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
|
||||
with:
|
||||
flavor: |
|
||||
latest=auto
|
||||
|
||||
@@ -67,7 +67,7 @@ runs:
|
||||
uses: ./.forgejo/actions/rust-toolchain
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
with:
|
||||
# Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125)
|
||||
driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }}
|
||||
@@ -79,7 +79,7 @@ runs:
|
||||
|
||||
- name: Login to builtin registry
|
||||
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
with:
|
||||
registry: ${{ env.BUILTIN_REGISTRY }}
|
||||
username: ${{ inputs.registry_user }}
|
||||
@@ -87,7 +87,7 @@ runs:
|
||||
|
||||
- name: Extract metadata (labels, annotations) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
|
||||
with:
|
||||
images: ${{ inputs.images }}
|
||||
# default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509
|
||||
|
||||
@@ -17,7 +17,7 @@ inputs:
|
||||
llvm-version:
|
||||
description: 'LLVM version to install'
|
||||
required: false
|
||||
default: '21'
|
||||
default: '20'
|
||||
|
||||
outputs:
|
||||
llvm-version:
|
||||
|
||||
@@ -71,7 +71,7 @@ runs:
|
||||
|
||||
- name: Install timelord-cli and git-warp-time
|
||||
if: steps.check-binaries.outputs.need-install == 'true'
|
||||
uses: https://github.com/taiki-e/install-action@920ab1831fbf4fb3ef75c8ead83556c918bb7290 # v2
|
||||
uses: https://github.com/taiki-e/install-action@184183c2401be73c3bf42c2e61268aa5855379c1 # v2
|
||||
with:
|
||||
tool: git-warp-time,timelord-cli@3.0.1
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
const labelsToAdd = new Set();
|
||||
|
||||
for (const file of fileNames) {
|
||||
if (file.startsWith('docs/') || file.startsWith('theme/') || (file.endsWith('.md') && !file.startsWith('changelog.d/')) || file == 'rspress.config.ts') {
|
||||
if (file.startsWith('docs/') || file.startsWith('theme/') || file.endsWith('.md') || file == 'rspress.config.ts') {
|
||||
labelsToAdd.add('Documentation');
|
||||
}
|
||||
if (file.startsWith('.forgejo/')) {
|
||||
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push Docker image by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
with:
|
||||
context: .
|
||||
file: "docker/Dockerfile"
|
||||
@@ -149,7 +149,7 @@ jobs:
|
||||
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@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
|
||||
with:
|
||||
context: .
|
||||
file: "docker/Dockerfile"
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
name: Renovate
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/renovatebot/renovate:43.195.3@sha256:868dffc3d6a46f42dfefe48b6978cc063d8df9c1d58a93a694c8989afa503e34
|
||||
image: ghcr.io/renovatebot/renovate:43.140.0@sha256:61303c28b10a491c559529fb6f41745850e4755a43a54c04c3ae6848d6eaf5cc
|
||||
options: --tmpfs /tmp:exec
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -24,7 +24,7 @@ repos:
|
||||
- id: check-added-large-files
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.46.2
|
||||
rev: v1.46.1
|
||||
hooks:
|
||||
- id: typos
|
||||
- id: typos
|
||||
|
||||
Generated
+140
-205
@@ -71,9 +71,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "annotate-snippets"
|
||||
version = "0.12.16"
|
||||
version = "0.12.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f211a51805bc641f3ad5b7664c77d2547af685cc33b4cd8d31964027a46f13f1"
|
||||
checksum = "92570a3f9c98e7e84df84b71d0965ac99b1871fcd75a3773a3bd1bad13f64cf7"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"memchr",
|
||||
@@ -193,7 +193,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"unicode-ident",
|
||||
"winnow 1.0.3",
|
||||
"winnow 1.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -253,15 +253,15 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.17.0"
|
||||
version = "1.16.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00"
|
||||
checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f"
|
||||
dependencies = [
|
||||
"aws-lc-sys",
|
||||
"zeroize",
|
||||
@@ -269,9 +269,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-sys"
|
||||
version = "0.41.0"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4"
|
||||
checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cmake",
|
||||
@@ -530,15 +530,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "built"
|
||||
version = "0.8.1"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9"
|
||||
checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.3"
|
||||
version = "3.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
@@ -625,9 +625,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.62"
|
||||
version = "1.2.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
|
||||
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -949,7 +949,7 @@ dependencies = [
|
||||
"lock_api",
|
||||
"log",
|
||||
"maplit",
|
||||
"nix",
|
||||
"nix 0.31.3",
|
||||
"num-traits",
|
||||
"parking_lot",
|
||||
"rand 0.10.1",
|
||||
@@ -1088,7 +1088,6 @@ dependencies = [
|
||||
"serde",
|
||||
"serde-saphyr",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sha2 0.11.0",
|
||||
"termimad",
|
||||
"tokio",
|
||||
@@ -1108,29 +1107,18 @@ dependencies = [
|
||||
"axum",
|
||||
"axum-extra",
|
||||
"base64 0.22.1",
|
||||
"conduwuit_api",
|
||||
"conduwuit_build_metadata",
|
||||
"conduwuit_core",
|
||||
"conduwuit_database",
|
||||
"conduwuit_service",
|
||||
"form_urlencoded",
|
||||
"futures",
|
||||
"lettre",
|
||||
"memory-serve",
|
||||
"rand 0.10.1",
|
||||
"recaptcha-verify",
|
||||
"reqwest 0.12.28",
|
||||
"ruma",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"thiserror",
|
||||
"tower-http",
|
||||
"tower-sec-fetch",
|
||||
"tower-sessions",
|
||||
"tower-sessions-core",
|
||||
"tracing",
|
||||
"url",
|
||||
"validator",
|
||||
]
|
||||
|
||||
@@ -1405,18 +1393,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.2.2"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
|
||||
checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "1.0.6"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d765eb1c0bda10d31e0ea185f5ee15da532d60b0912d2bd1441783439e749c5"
|
||||
checksum = "3429e8f8e3ce0ffe475c411850f70468c70d7a87d2ac3d15dd44703fb885aede"
|
||||
dependencies = [
|
||||
"link-section",
|
||||
"linktime-proc-macro",
|
||||
@@ -1538,7 +1526,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1576,12 +1563,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.11.3"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
|
||||
checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c"
|
||||
dependencies = [
|
||||
"block-buffer 0.12.0",
|
||||
"crypto-common 0.2.2",
|
||||
"crypto-common 0.2.1",
|
||||
"ctutils",
|
||||
]
|
||||
|
||||
@@ -1617,9 +1604,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dtor"
|
||||
version = "1.0.3"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2137ce22f50d4c43ce098daf41c904cc700de1ce8bc2daf53ed4e702180a464"
|
||||
checksum = "66261f2a6a4976b45407e398e63bc50a25fccf464d417f7d20f2e2b0974b9888"
|
||||
dependencies = [
|
||||
"linktime-proc-macro",
|
||||
]
|
||||
@@ -1656,9 +1643,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.16.0"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -1722,7 +1709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1939,9 +1926,9 @@ checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "3.0.4"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968"
|
||||
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
@@ -2064,9 +2051,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.14"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733"
|
||||
checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
@@ -2137,9 +2124,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
||||
|
||||
[[package]]
|
||||
name = "hdrhistogram"
|
||||
@@ -2250,7 +2237,7 @@ version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
|
||||
dependencies = [
|
||||
"digest 0.11.3",
|
||||
"digest 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2340,9 +2327,9 @@ checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
|
||||
|
||||
[[package]]
|
||||
name = "hybrid-array"
|
||||
version = "0.4.12"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da"
|
||||
checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
@@ -2570,7 +2557,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.1",
|
||||
"hashbrown 0.17.0",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
@@ -2699,9 +2686,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.99"
|
||||
version = "0.3.97"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
|
||||
checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
@@ -2841,9 +2828,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "link-section"
|
||||
version = "0.17.2"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d1e908a416d6e9f725743b84a36feea40c4c131e805fbc26d61f9f451f36080"
|
||||
checksum = "ea2c24837c4fd5ab6a31d64133eae954f5199247523cf29586117e85245c0dd3"
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
@@ -3018,15 +3005,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "minicbor"
|
||||
version = "2.2.2"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b7a5041e12946f8b7d3f5a9d96383a19d694b9335457c522be7815b9abafb02"
|
||||
checksum = "e70eae6d4f18f7d76877fe7b13f0bc21f7c2b7239d2041c338335f7b388d0dd7"
|
||||
|
||||
[[package]]
|
||||
name = "minicbor-serde"
|
||||
version = "0.7.0"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "293c7245401f035e2dcc4b12ebdb5c9d8847247fc79fe1b5b0a0d58d7275324c"
|
||||
checksum = "80047f75e28e3b38f6ab2ec3c2c7669f6b411fa6f8424e1a90a3fd784b19a3f4"
|
||||
dependencies = [
|
||||
"minicbor",
|
||||
"serde",
|
||||
@@ -3102,6 +3089,18 @@ version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.31.3"
|
||||
@@ -3151,7 +3150,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3189,9 +3188,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.2"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
|
||||
checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
@@ -3429,9 +3428,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry"
|
||||
version = "0.32.0"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0142c63252a9e054e68a4c61a5778f7b14f576274d593f8ce883d191a099682"
|
||||
checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@@ -3443,22 +3442,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-http"
|
||||
version = "0.32.0"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5683015d09e2df236ef005b17f6f196f0d5f6313c4fa43a7b6a53b52776e4331"
|
||||
checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"http",
|
||||
"opentelemetry",
|
||||
"reqwest 0.13.3",
|
||||
"reqwest 0.12.28",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-otlp"
|
||||
version = "0.32.0"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9966929966d17620d7c316c643ba62631826e10021409357772d5eea84f62c35"
|
||||
checksum = "1f69cd6acbb9af919df949cd1ec9e5e7fdc2ef15d234b6b795aaa525cc02f71f"
|
||||
dependencies = [
|
||||
"http",
|
||||
"opentelemetry",
|
||||
@@ -3466,18 +3465,18 @@ dependencies = [
|
||||
"opentelemetry-proto",
|
||||
"opentelemetry_sdk",
|
||||
"prost",
|
||||
"reqwest 0.13.3",
|
||||
"reqwest 0.12.28",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tonic",
|
||||
"tonic-types",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-proto"
|
||||
version = "0.32.0"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56d658ba1faf63f7b9c492cfbe6e0ec365440a16132d3270c1065f7b33f1b638"
|
||||
checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f"
|
||||
dependencies = [
|
||||
"opentelemetry",
|
||||
"opentelemetry_sdk",
|
||||
@@ -3488,16 +3487,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry_sdk"
|
||||
version = "0.32.0"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368afaed344110f40b179bb8fbe54bc52d98f9bd2b281799ef32487c2650c956"
|
||||
checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-executor",
|
||||
"futures-util",
|
||||
"opentelemetry",
|
||||
"percent-encoding",
|
||||
"portable-atomic",
|
||||
"rand 0.9.4",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
@@ -3506,13 +3504,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "os_info"
|
||||
version = "3.15.0"
|
||||
version = "3.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cf20a545b305cf1da722b236b5155c9bb35f1d5ceb28c048bd96ca842f41b5b"
|
||||
checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"log",
|
||||
"nix",
|
||||
"nix 0.30.1",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2-ui-kit",
|
||||
@@ -3647,18 +3645,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.13"
|
||||
version = "1.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
|
||||
checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.13"
|
||||
version = "1.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
|
||||
checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3833,9 +3831,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.13.4"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e"
|
||||
checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"memchr",
|
||||
@@ -4066,7 +4064,9 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
@@ -4164,7 +4164,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma"
|
||||
version = "0.15.1"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=aa266b1e15c782322ffcbd2574264ef74ad2ebe6#aa266b1e15c782322ffcbd2574264ef74ad2ebe6"
|
||||
dependencies = [
|
||||
"assign",
|
||||
"js_int",
|
||||
@@ -4183,7 +4183,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-appservice-api"
|
||||
version = "0.15.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=aa266b1e15c782322ffcbd2574264ef74ad2ebe6#aa266b1e15c782322ffcbd2574264ef74ad2ebe6"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
@@ -4195,7 +4195,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-client-api"
|
||||
version = "0.23.1"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=aa266b1e15c782322ffcbd2574264ef74ad2ebe6#aa266b1e15c782322ffcbd2574264ef74ad2ebe6"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"assign",
|
||||
@@ -4217,7 +4217,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-common"
|
||||
version = "0.18.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=aa266b1e15c782322ffcbd2574264ef74ad2ebe6#aa266b1e15c782322ffcbd2574264ef74ad2ebe6"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"base64 0.22.1",
|
||||
@@ -4250,7 +4250,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-events"
|
||||
version = "0.33.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=aa266b1e15c782322ffcbd2574264ef74ad2ebe6#aa266b1e15c782322ffcbd2574264ef74ad2ebe6"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"indexmap",
|
||||
@@ -4271,7 +4271,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-federation-api"
|
||||
version = "0.14.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=aa266b1e15c782322ffcbd2574264ef74ad2ebe6#aa266b1e15c782322ffcbd2574264ef74ad2ebe6"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"headers",
|
||||
@@ -4294,7 +4294,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-identifiers-validation"
|
||||
version = "0.12.1"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=aa266b1e15c782322ffcbd2574264ef74ad2ebe6#aa266b1e15c782322ffcbd2574264ef74ad2ebe6"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"thiserror",
|
||||
@@ -4303,7 +4303,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-macros"
|
||||
version = "0.18.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=aa266b1e15c782322ffcbd2574264ef74ad2ebe6#aa266b1e15c782322ffcbd2574264ef74ad2ebe6"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"cfg-if",
|
||||
@@ -4319,7 +4319,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-push-gateway-api"
|
||||
version = "0.14.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=aa266b1e15c782322ffcbd2574264ef74ad2ebe6#aa266b1e15c782322ffcbd2574264ef74ad2ebe6"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
@@ -4331,7 +4331,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-signatures"
|
||||
version = "0.20.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=aa266b1e15c782322ffcbd2574264ef74ad2ebe6#aa266b1e15c782322ffcbd2574264ef74ad2ebe6"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"ed25519-dalek",
|
||||
@@ -4347,7 +4347,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-state-res"
|
||||
version = "0.16.0"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
|
||||
source = "git+https://github.com/ruma/ruma.git?rev=aa266b1e15c782322ffcbd2574264ef74ad2ebe6#aa266b1e15c782322ffcbd2574264ef74ad2ebe6"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
@@ -4372,8 +4372,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-librocksdb-sys"
|
||||
version = "0.45.1+11.1.1"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1?rev=0a25ff92f7c09b55eec496b9c192c7d5136ab2b8#0a25ff92f7c09b55eec496b9c192c7d5136ab2b8"
|
||||
version = "0.42.0+10.10.1"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1?rev=31fb8f772c7afcdc0061ab6a40cfa3a1be2fccd9#31fb8f772c7afcdc0061ab6a40cfa3a1be2fccd9"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"bzip2-sys",
|
||||
@@ -4389,8 +4389,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-rocksdb"
|
||||
version = "0.49.1"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1?rev=0a25ff92f7c09b55eec496b9c192c7d5136ab2b8#0a25ff92f7c09b55eec496b9c192c7d5136ab2b8"
|
||||
version = "0.46.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1?rev=31fb8f772c7afcdc0061ab6a40cfa3a1be2fccd9#31fb8f772c7afcdc0061ab6a40cfa3a1be2fccd9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"parking_lot",
|
||||
@@ -4428,7 +4428,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4487,7 +4487,7 @@ dependencies = [
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4760,7 +4760,7 @@ checksum = "dcc7fe48e34d02a97bc8e6253b8b91e5a47fe2c47eaacb5149cefbb69922eaf0"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"annotate-snippets",
|
||||
"base64 0.22.1",
|
||||
"base64 0.21.7",
|
||||
"encoding_rs_io",
|
||||
"getrandom 0.3.4",
|
||||
"granit-parser",
|
||||
@@ -4806,9 +4806,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.150"
|
||||
version = "1.0.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -4887,7 +4887,7 @@ checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.0",
|
||||
"digest 0.11.3",
|
||||
"digest 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4909,7 +4909,7 @@ checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.0",
|
||||
"digest 0.11.3",
|
||||
"digest 0.11.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5004,9 +5004,9 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.3"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
|
||||
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
@@ -5040,7 +5040,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5300,9 +5300,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.52.3"
|
||||
version = "1.52.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
|
||||
checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
@@ -5409,7 +5409,7 @@ dependencies = [
|
||||
"serde_spanned 1.1.1",
|
||||
"toml_datetime 1.1.1+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"winnow 1.0.3",
|
||||
"winnow 1.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5462,7 +5462,7 @@ dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime 1.1.1+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"winnow 1.0.3",
|
||||
"winnow 1.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5471,7 +5471,7 @@ version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
|
||||
dependencies = [
|
||||
"winnow 1.0.3",
|
||||
"winnow 1.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5488,9 +5488,9 @@ checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
|
||||
|
||||
[[package]]
|
||||
name = "tonic"
|
||||
version = "0.14.6"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef"
|
||||
checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
@@ -5517,26 +5517,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tonic-prost"
|
||||
version = "0.14.6"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0"
|
||||
checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost",
|
||||
"tonic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tonic-types"
|
||||
version = "0.14.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73ab1b02061f83d519bba3caa167f88f261ef05720ab8ebc954ade70de3348e8"
|
||||
dependencies = [
|
||||
"prost",
|
||||
"prost-types",
|
||||
"tonic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.3"
|
||||
@@ -5556,27 +5545,11 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-cookies"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "151b5a3e3c45df17466454bb74e9ecedecc955269bdedbf4d150dfa393b55a36"
|
||||
dependencies = [
|
||||
"axum-core",
|
||||
"cookie",
|
||||
"futures-util",
|
||||
"http",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.6.11"
|
||||
version = "0.6.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
|
||||
checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"bitflags",
|
||||
@@ -5620,44 +5593,6 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||
|
||||
[[package]]
|
||||
name = "tower-sessions"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "518dca34b74a17cadfcee06e616a09d2bd0c3984eff1769e1e76d58df978fc78"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"http",
|
||||
"time",
|
||||
"tokio",
|
||||
"tower-cookies",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tower-sessions-core",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-sessions-core"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "568531ec3dfcf3ffe493de1958ae5662a0284ac5d767476ecdb6a34ff8c6b06c"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"base64 0.22.1",
|
||||
"futures",
|
||||
"http",
|
||||
"parking_lot",
|
||||
"rand 0.9.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.44"
|
||||
@@ -5726,9 +5661,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-opentelemetry"
|
||||
version = "0.33.0"
|
||||
version = "0.32.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adbc64cba7137545b8044cb1fe9814f7aacf3c6b5f9b45be8bb5db538befdb26"
|
||||
checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"opentelemetry",
|
||||
@@ -5972,9 +5907,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.122"
|
||||
version = "0.2.120"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
|
||||
checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -5985,9 +5920,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.72"
|
||||
version = "0.4.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f"
|
||||
checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -5995,9 +5930,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.122"
|
||||
version = "0.2.120"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
|
||||
checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -6005,9 +5940,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.122"
|
||||
version = "0.2.120"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
|
||||
checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
@@ -6018,9 +5953,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.122"
|
||||
version = "0.2.120"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
|
||||
checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -6074,9 +6009,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.99"
|
||||
version = "0.3.97"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436"
|
||||
checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -6153,7 +6088,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6364,9 +6299,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.3"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
|
||||
checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -6544,9 +6479,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.8"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
|
||||
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
+9
-12
@@ -39,10 +39,10 @@ features = ["ffi", "std", "union"]
|
||||
version = "1.1.0"
|
||||
|
||||
[workspace.dependencies.ctor]
|
||||
version = "1.0.6"
|
||||
version = "0.13.0"
|
||||
|
||||
[workspace.dependencies.dtor]
|
||||
version = "1.0.0"
|
||||
version = "0.13.0"
|
||||
|
||||
[workspace.dependencies.cargo_toml]
|
||||
version = "0.22"
|
||||
@@ -344,7 +344,7 @@ version = "1.1.1"
|
||||
[workspace.dependencies.ruma]
|
||||
# version = "0.14.1"
|
||||
git = "https://github.com/ruma/ruma.git"
|
||||
rev = "9c9dccc93f054bbd28f23f630223fffa6289ecbc"
|
||||
rev = "aa266b1e15c782322ffcbd2574264ef74ad2ebe6"
|
||||
features = [
|
||||
"appservice-api-c",
|
||||
"client-api",
|
||||
@@ -384,7 +384,7 @@ features = [
|
||||
|
||||
[workspace.dependencies.rust-rocksdb]
|
||||
git = "https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1"
|
||||
rev = "0a25ff92f7c09b55eec496b9c192c7d5136ab2b8"
|
||||
rev = "31fb8f772c7afcdc0061ab6a40cfa3a1be2fccd9"
|
||||
default-features = false
|
||||
features = [
|
||||
"multi-threaded-cf",
|
||||
@@ -404,20 +404,20 @@ default-features = false
|
||||
|
||||
# optional opentelemetry, performance measurements, flamegraphs, etc for performance measurements and monitoring
|
||||
[workspace.dependencies.opentelemetry]
|
||||
version = "0.32.0"
|
||||
version = "0.31.0"
|
||||
|
||||
[workspace.dependencies.tracing-flame]
|
||||
version = "0.2.0"
|
||||
|
||||
[workspace.dependencies.tracing-opentelemetry]
|
||||
version = "0.33.0"
|
||||
version = "0.32.0"
|
||||
|
||||
[workspace.dependencies.opentelemetry_sdk]
|
||||
version = "0.32.0"
|
||||
version = "0.31.0"
|
||||
features = ["rt-tokio"]
|
||||
|
||||
[workspace.dependencies.opentelemetry-otlp]
|
||||
version = "0.32.0"
|
||||
version = "0.31.0"
|
||||
features = ["http", "grpc-tonic", "trace", "logs", "metrics"]
|
||||
|
||||
|
||||
@@ -534,7 +534,7 @@ version = "2.1.1"
|
||||
features = ["std"]
|
||||
|
||||
[workspace.dependencies.minicbor-serde]
|
||||
version = "0.7.0"
|
||||
version = "0.6.0"
|
||||
features = ["std"]
|
||||
|
||||
[workspace.dependencies.maplit]
|
||||
@@ -559,9 +559,6 @@ features = ["std"]
|
||||
[workspace.dependencies.nonzero_ext]
|
||||
version = "0.3.0"
|
||||
|
||||
[workspace.dependencies.serde_urlencoded]
|
||||
version = "0.7.1"
|
||||
|
||||
#
|
||||
# Patches
|
||||
#
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Users may now be forbidden from deactivating their own accounts with the new `allow_deactivation` config option. Contributed by @ginger.
|
||||
@@ -1 +0,0 @@
|
||||
Added support for Matrix 1.16's `state_after` feature, allowing clients which understand it to sync room state changes more reliably. Contributed by @ginger.
|
||||
@@ -1 +0,0 @@
|
||||
Added support for authenticating clients using the new OAuth 2.0 login API. Contributed by @ginger.
|
||||
@@ -1 +0,0 @@
|
||||
The version of Debian that the Docker-based build process uses has been upgraded from Bookworm to Trixie, meaning that standalone binaries now have a minimum glibc of 2.41, and can no longer be used on distro versions from before 2025-01-30
|
||||
@@ -1 +0,0 @@
|
||||
Updated [MSC4284: Policy Servers](https://github.com/matrix-org/matrix-spec-proposals/pull/4284) implementation to support the newly stabilised proposal. Contributed by @nex.
|
||||
@@ -1 +0,0 @@
|
||||
Added config option for default room ACLs. Contributed by @eve.
|
||||
@@ -1,9 +0,0 @@
|
||||
Implemented event rejection, which should resolve and prevent future netsplits of the kinds observed
|
||||
within some Continuwuity rooms.
|
||||
Also resolved several bugs related to both soft-failing events, and event backfilling, which should
|
||||
improve state resolution stability.
|
||||
The `!admin debug get-pdu` command was updated to disambiguate event acceptance status, and
|
||||
`!admin debug show-auth-chain` was added to visually display event auth chains, which may assist
|
||||
developers in debugging strangely complex events.
|
||||
|
||||
Contributed by @nex.
|
||||
@@ -1 +0,0 @@
|
||||
Fixed several bugs in the `POST /_matrix/client/v3/rooms/{roomId}/upgrade` endpoint. Contributed by @nex.
|
||||
@@ -1 +0,0 @@
|
||||
Added full support for [MSC4168: Update `m.space.*` state on room upgrade](https://github.com/matrix-org/matrix-spec-proposals/pull/4168). Contributed by @nex.
|
||||
@@ -1 +0,0 @@
|
||||
Adjusted legacy sync logic to no longer use the `roomsynctoken_shortstatehash` database column. Once this change has been confirmed to be stable and reliable, a future update will remove it entirely, significantly decreasing database sizes. Contributed by @ginger.
|
||||
+37
-79
@@ -372,18 +372,21 @@
|
||||
#
|
||||
#federation_timeout = 60
|
||||
|
||||
# Policy server request timeout (seconds). Generally policy
|
||||
# MSC4284 Policy server request timeout (seconds). Generally policy
|
||||
# servers should respond near instantly, however may slow down under
|
||||
# load. If a policy server doesn't respond in a short amount of time, the
|
||||
# room it is configured in may become unusable if this limit is set too
|
||||
# high. 30 seconds is a good default, however lower values may be
|
||||
# acceptable if temporary send failures are an okay trade-off.
|
||||
# high. 10 seconds is a good default, however dropping this to 3-5 seconds
|
||||
# can be acceptable.
|
||||
#
|
||||
# Please be aware that policy requests are *NOT* currently re-tried, so if
|
||||
# a spam check request fails, the event will be assumed to be not spam,
|
||||
# which in some cases may result in spam being sent to or received from
|
||||
# the room that would typically be prevented.
|
||||
#
|
||||
# About policy servers: https://matrix.org/blog/2025/04/introducing-policy-servers/
|
||||
# (Stabilized in Matrix v1.18)
|
||||
#
|
||||
#policy_server_request_timeout = 30
|
||||
#policy_server_request_timeout = 10
|
||||
|
||||
# Federation client idle connection pool timeout (seconds).
|
||||
#
|
||||
@@ -521,15 +524,17 @@
|
||||
#
|
||||
#recaptcha_private_site_key =
|
||||
|
||||
# Controls whether users are allowed to deactivate their own accounts
|
||||
# through the account management panel or their Matrix clients. Server
|
||||
# admins can always deactivate users using the relevant admin commands.
|
||||
# Policy documents, such as terms and conditions or a privacy policy,
|
||||
# which users must agree to when registering an account.
|
||||
#
|
||||
# Note that, in some jurisdictions, you may be legally required to honor
|
||||
# users who request to deactivate their accounts if you set this option
|
||||
# to `false`.
|
||||
# Example:
|
||||
# ```ignore
|
||||
# [global.registration_terms.privacy_policy]
|
||||
# en = { name = "Privacy Policy", url = "https://homeserver.example/en/privacy_policy.html" }
|
||||
# es = { name = "Política de Privacidad", url = "https://homeserver.example/es/privacy_policy.html" }
|
||||
# ```
|
||||
#
|
||||
#allow_deactivation = true
|
||||
#registration_terms = {}
|
||||
|
||||
# Controls whether encrypted rooms and events are allowed.
|
||||
#
|
||||
@@ -619,30 +624,6 @@
|
||||
#
|
||||
#default_room_version = "12"
|
||||
|
||||
# A default allow value for the Access Control List when creating a room.
|
||||
#
|
||||
# If a list is provided, new rooms will be created with
|
||||
# a m.room.server_acl event. Only servers which match one of the patterns
|
||||
# in the list will be permitted to participate in the room.
|
||||
#
|
||||
# ACLs in existing rooms will not be updated automatically. This is not
|
||||
# a substitute for moderation bots.
|
||||
#
|
||||
#default_room_acl_allow =
|
||||
|
||||
# A default deny value for the Access Control List when creating a room.
|
||||
#
|
||||
# If a list is provided, new rooms will be created with
|
||||
# a m.room.server_acl event. Servers which match one of the patterns
|
||||
# in the list will be NOT permitted to participate in the room.
|
||||
#
|
||||
# This config cannot be used if the default_room_acl_allow config is used.
|
||||
#
|
||||
# ACLs in existing rooms will not be updated automatically. This is not
|
||||
# a substitute for moderation bots.
|
||||
#
|
||||
#default_room_acl_deny =
|
||||
|
||||
# Enable OpenTelemetry OTLP tracing export. This replaces the deprecated
|
||||
# Jaeger exporter. Traces will be sent via OTLP to a collector (such as
|
||||
# Jaeger) that supports the OpenTelemetry Protocol.
|
||||
@@ -1589,6 +1570,19 @@
|
||||
#
|
||||
#block_non_admin_invites = false
|
||||
|
||||
# Enable or disable making requests to MSC4284 Policy Servers.
|
||||
# It is recommended you keep this enabled unless you experience frequent
|
||||
# connectivity issues, such as in a restricted networking environment.
|
||||
#
|
||||
#enable_msc4284_policy_servers = true
|
||||
|
||||
# Enable running locally generated events through configured MSC4284
|
||||
# policy servers. You may wish to disable this if your server is
|
||||
# single-user for a slight speed benefit in some rooms, but otherwise
|
||||
# should leave it enabled.
|
||||
#
|
||||
#policy_server_check_own_events = true
|
||||
|
||||
# Allow admins to enter commands in rooms other than "#admins" (admin
|
||||
# room) by prefixing your message with "\!admin" or "\\!admin" followed up
|
||||
# a normal continuwuity admin command. The reply will be publicly visible
|
||||
@@ -1793,9 +1787,11 @@
|
||||
#stream_amplification = 1024
|
||||
|
||||
# Number of sender task workers; determines sender parallelism. Default is
|
||||
# core count. Override by setting a different value.
|
||||
# '0' which means the value is determined internally, likely matching the
|
||||
# number of tokio worker-threads or number of cores, etc. Override by
|
||||
# setting a non-zero value.
|
||||
#
|
||||
#sender_workers = core count
|
||||
#sender_workers = 0
|
||||
|
||||
# Enables listener sockets; can be set to false to disable listening. This
|
||||
# option is intended for developer/diagnostic purposes only.
|
||||
@@ -1853,11 +1849,6 @@
|
||||
#
|
||||
#support_page =
|
||||
|
||||
# The ed25519 public key for the policy server available at this server's
|
||||
# name. Must be unpadded base64.
|
||||
#
|
||||
#policy_server_public_key =
|
||||
|
||||
# Role string for server support contacts, to be served as part of the
|
||||
# MSC1929 server support endpoint at /.well-known/matrix/support.
|
||||
#
|
||||
@@ -1968,10 +1959,8 @@
|
||||
#
|
||||
#sender =
|
||||
|
||||
# Whether to allow public registration with an email address.
|
||||
#
|
||||
# Note that, if this option is enabled, anyone will be able to register an
|
||||
# account with just an email address.
|
||||
# Whether to require that users provide an email address when they
|
||||
# register.
|
||||
#
|
||||
# If either this option or `require_email_for_token_registration` are set,
|
||||
# users will not be allowed to remove their email address.
|
||||
@@ -1979,37 +1968,6 @@
|
||||
#require_email_for_registration = false
|
||||
|
||||
# Whether to require that users who register with a registration token
|
||||
# provide an email address. This option is independent of
|
||||
# `require_email_for_registration`.
|
||||
# provide an email address.
|
||||
#
|
||||
#require_email_for_token_registration = false
|
||||
|
||||
#[global.registration_terms]
|
||||
|
||||
# The language code to provide to clients along with the policy documents.
|
||||
#
|
||||
#language = "en"
|
||||
|
||||
# Policy documents, such as terms and conditions or a privacy policy,
|
||||
# which users must agree to when registering an account.
|
||||
#
|
||||
# Example:
|
||||
# ```ignore
|
||||
# [global.registration_terms.documents]
|
||||
# privacy_policy = { name = "Privacy Policy", url = "https://homeserver.example/en/privacy_policy.html" }
|
||||
# ```
|
||||
#
|
||||
#documents = {}
|
||||
|
||||
#[global.oauth]
|
||||
|
||||
# The compatibility mode to use for OAuth.
|
||||
#
|
||||
# - "disabled": OAuth will be unavailable. Users will only be able to log
|
||||
# in using legacy authentication.
|
||||
# - "hybrid": OAuth and legacy authentication will both be available. Some
|
||||
# clients may only use one or the other.
|
||||
# - "exclusive": Only OAuth will be available. Clients which require
|
||||
# legacy authentication will be unable to log in.
|
||||
#
|
||||
#compatibility_mode = "hybrid"
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
ARG RUST_VERSION=1
|
||||
ARG DEBIAN_VERSION=trixie
|
||||
ARG DEBIAN_VERSION=bookworm
|
||||
|
||||
FROM --platform=$BUILDPLATFORM docker.io/tonistiigi/xx AS xx
|
||||
FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-slim-${DEBIAN_VERSION} AS base
|
||||
@@ -10,7 +10,7 @@ RUN rm -f /etc/apt/apt.conf.d/docker-clean
|
||||
|
||||
# Match Rustc version as close as possible
|
||||
# rustc -vV
|
||||
ARG LLVM_VERSION=22
|
||||
ARG LLVM_VERSION=21
|
||||
# ENV RUSTUP_TOOLCHAIN=${RUST_VERSION}
|
||||
|
||||
# Install repo tools
|
||||
@@ -22,7 +22,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update && apt-get install -y \
|
||||
pkg-config make jq \
|
||||
wget curl git lsb-release gpg \
|
||||
wget curl git software-properties-common \
|
||||
file
|
||||
# golang cmake
|
||||
|
||||
|
||||
Generated
+21
-21
@@ -3,11 +3,11 @@
|
||||
"advisory-db": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1779575509,
|
||||
"narHash": "sha256-wXKYURZz76ZC5lbuDA1oVQA/MxSB3pSJ1raF1HG0oIc=",
|
||||
"lastModified": 1777645914,
|
||||
"narHash": "sha256-P1T7QVQS13OvkXEuEhI91CLaQfyv6iqV9vW8IBLLDYg=",
|
||||
"owner": "rustsec",
|
||||
"repo": "advisory-db",
|
||||
"rev": "831c50f4a4304068f125e603add6a8839f08b3eb",
|
||||
"rev": "d6ba1f7070ba91f45efe372d68eb648be67d0417",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -18,11 +18,11 @@
|
||||
},
|
||||
"crane": {
|
||||
"locked": {
|
||||
"lastModified": 1779130139,
|
||||
"narHash": "sha256-BLrtr42azquO7MdGFU5a7KiMl3YpFlTeIXqy1fT5GlQ=",
|
||||
"lastModified": 1777335812,
|
||||
"narHash": "sha256-bEg5xoAxAwsyfnGhkEX7RJViTIBIYPd8ISg4O1c0HFc=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "edb38893982a3338972bb4a2ec7ce7c29ba10fd9",
|
||||
"rev": "5e0fb2f64edff2822249f21293b8304dedaaf676",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -39,11 +39,11 @@
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1779612045,
|
||||
"narHash": "sha256-+7lfNVnmXJDkiRYHd5NoNwYoyUcc0LcXPaIJqjO7VWM=",
|
||||
"lastModified": 1777624102,
|
||||
"narHash": "sha256-thSyElkje577x/kAbP72nHlfiFc1a+tCudskLPHXe9s=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "d7be747f0a65af378de515fc3cee131bf99a008f",
|
||||
"rev": "4d81601e0b73f20d81d066754ad0e7d1e7f75a06",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -74,11 +74,11 @@
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1778716662,
|
||||
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
|
||||
"lastModified": 1775087534,
|
||||
"narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
|
||||
"rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -89,11 +89,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1779508470,
|
||||
"narHash": "sha256-Ap9KJX+5xHIn3bPIpfNgT6MEXdAECECwo4/rmlQD74M=",
|
||||
"lastModified": 1777268161,
|
||||
"narHash": "sha256-bxrdOn8SCOv8tN4JbTF/TXq7kjo9ag4M+C8yzzIRYbE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "29916453413845e54a65b8a1cf996842300cd299",
|
||||
"rev": "1c3fe55ad329cbcb28471bb30f05c9827f724c76",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -105,11 +105,11 @@
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1777168982,
|
||||
"narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=",
|
||||
"lastModified": 1774748309,
|
||||
"narHash": "sha256-+U7gF3qxzwD5TZuANzZPeJTZRHS29OFQgkQ2kiTJBIQ=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14",
|
||||
"rev": "333c4e0545a6da976206c74db8773a1645b5870a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -132,11 +132,11 @@
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1779569060,
|
||||
"narHash": "sha256-NSnk5D+3KEfRdbgPijs33N2RAKSG6A74SwfnynLcouo=",
|
||||
"lastModified": 1777583169,
|
||||
"narHash": "sha256-dVJ4+wrRKc8oIgp3rLOFSq1obt/sCKlXy3h47qof/w0=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "987ea33645ab1c709b1df6823038abcb2fe8973e",
|
||||
"rev": "aa64e4828a2bbba44463c1229a81c748d3cce583",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
liburing,
|
||||
craneLib,
|
||||
pkg-config,
|
||||
callPackage,
|
||||
rustPlatform,
|
||||
cargoExtraArgs ? "",
|
||||
rustflags ? "",
|
||||
target_cpu ? null,
|
||||
rocksdb,
|
||||
rocksdb ? callPackage ./rocksdb.nix { },
|
||||
profile ? "release",
|
||||
}:
|
||||
let
|
||||
@@ -39,10 +39,7 @@ let
|
||||
ROCKSDB_LIB_DIR = "${rocksdb}/lib";
|
||||
CARGO_PROFILE = profile;
|
||||
RUSTFLAGS = rustflags;
|
||||
}
|
||||
// (lib.optionalAttrs (target_cpu != null) {
|
||||
TARGET_CPU = target_cpu;
|
||||
});
|
||||
};
|
||||
};
|
||||
in
|
||||
craneLib.buildPackage (
|
||||
@@ -59,7 +56,7 @@ craneLib.buildPackage (
|
||||
]
|
||||
}"
|
||||
|
||||
patchelf --set-rpath "$old_rpath:$extra_rpath" $out/bin/conduwuit
|
||||
patchelf --set-rpath "$old_rpath:$extra_rpath" $out/bin/conduwuit
|
||||
'';
|
||||
|
||||
meta = {
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
rocksdb = pkgs.callPackage ./rocksdb.nix { };
|
||||
default = pkgs.callPackage ./continuwuity.nix {
|
||||
inherit self craneLib;
|
||||
inherit (self'.packages) rocksdb;
|
||||
# extra features via `cargoExtraArgs`
|
||||
cargoExtraArgs = "-F http3";
|
||||
# extra RUSTFLAGS via `rustflags`
|
||||
@@ -23,13 +22,11 @@
|
||||
rustflags = "--cfg reqwest_unstable";
|
||||
};
|
||||
# users may also override this with other cargo profiles to build for other feature sets
|
||||
# for features configuration see `default` package which enables http3 by default
|
||||
|
||||
# example: different compilation profile and different target_cpu
|
||||
max-perf-haswell = self'.packages.default.override {
|
||||
# compiles explicitly for haswell arch cpus
|
||||
target_cpu = "haswell";
|
||||
# compiles slower but with more thorough optimizations
|
||||
#
|
||||
# other examples include:
|
||||
#
|
||||
# - release-high-perf
|
||||
max-perf = self'.packages.default.override {
|
||||
profile = "release-max-perf";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
{
|
||||
# stdenv,
|
||||
# enableJemalloc ? stdenv.hostPlatform.isLinux,
|
||||
enableJemalloc ? false,
|
||||
stdenv,
|
||||
rocksdb,
|
||||
fetchFromGitea,
|
||||
rust-jemalloc-sys-unprefixed,
|
||||
@@ -15,16 +13,16 @@
|
||||
#
|
||||
# [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17
|
||||
jemalloc = rust-jemalloc-sys-unprefixed;
|
||||
inherit enableJemalloc;
|
||||
enableJemalloc = stdenv.hostPlatform.isLinux;
|
||||
}).overrideAttrs
|
||||
({
|
||||
version = "continuwuity-v0.5.0-unstable-2026-05-19";
|
||||
version = "continuwuity-v0.5.0-unstable-2026-03-27";
|
||||
src = fetchFromGitea {
|
||||
domain = "forgejo.ellis.link";
|
||||
owner = "continuwuation";
|
||||
repo = "rocksdb";
|
||||
rev = "3756b2b905e13216d8b56bcc783d814e7b073aff";
|
||||
hash = "sha256-rSv4fr2bf9JJwdodgeuPCuceeh7k97KVxrAOC0wyPQY=";
|
||||
rev = "463f47afceebfe088f6922420265546bd237f249";
|
||||
hash = "sha256-1ef75IDMs5Hba4VWEyXPJb02JyShy5k4gJfzGDhopRk=";
|
||||
};
|
||||
|
||||
# We have this already at https://forgejo.ellis.link/continuwuation/rocksdb/commit/a935c0273e1ba44eacf88ce3685a9b9831486155
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
file = inputs.self + "/rust-toolchain.toml";
|
||||
|
||||
# See also `rust-toolchain.toml`
|
||||
sha256 = "sha256-gh/xTkxKHL4eiRXzWv8KP7vfjSk61Iq48x47BEDFgfk=";
|
||||
sha256 = "sha256-sqSWJDUxc+zaz1nBWMAJKTAGBuGWP25GCftIOlCEAtA=";
|
||||
};
|
||||
in
|
||||
{
|
||||
|
||||
Generated
+133
-133
@@ -125,13 +125,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rsbuild/core": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.0.7.tgz",
|
||||
"integrity": "sha512-LsBONEzsjzOAqO72ot39eI7g53zSfF9QuDXTu4ks8IUX+EZsxRSniQfc+1zVA6a6y3b9KUUtG96avoMLKbWklQ==",
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.0.5.tgz",
|
||||
"integrity": "sha512-KajO50hbXb32S8MsyDh2f+xKcVeRy9Gfzdcy0JjpMLj22djHugly6jrGo7jH7ls9X6/TDcyCTncSuNK4+D2lTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rspack/core": "~2.0.4",
|
||||
"@rspack/core": "~2.0.2",
|
||||
"@swc/helpers": "^0.5.21"
|
||||
},
|
||||
"bin": {
|
||||
@@ -169,28 +169,28 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rspack/binding": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.0.4.tgz",
|
||||
"integrity": "sha512-/QeJDPUw/lWkBJESG264KA9u6/rAjvoJhKncU4rkTi5Ap45kue5HTgOzr0ufxKdd2Xl72fjFBuqlKmtFDD5LiQ==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.0.2.tgz",
|
||||
"integrity": "sha512-0kZPplW9GWx8mfC6DfsaRY3QBIYPuUs42JfmSM6aSb8tMHZAXQeLeMB8M+h8i4SeI+aFtCgO6UuYGtyWf7+L+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optionalDependencies": {
|
||||
"@rspack/binding-darwin-arm64": "2.0.4",
|
||||
"@rspack/binding-darwin-x64": "2.0.4",
|
||||
"@rspack/binding-linux-arm64-gnu": "2.0.4",
|
||||
"@rspack/binding-linux-arm64-musl": "2.0.4",
|
||||
"@rspack/binding-linux-x64-gnu": "2.0.4",
|
||||
"@rspack/binding-linux-x64-musl": "2.0.4",
|
||||
"@rspack/binding-wasm32-wasi": "2.0.4",
|
||||
"@rspack/binding-win32-arm64-msvc": "2.0.4",
|
||||
"@rspack/binding-win32-ia32-msvc": "2.0.4",
|
||||
"@rspack/binding-win32-x64-msvc": "2.0.4"
|
||||
"@rspack/binding-darwin-arm64": "2.0.2",
|
||||
"@rspack/binding-darwin-x64": "2.0.2",
|
||||
"@rspack/binding-linux-arm64-gnu": "2.0.2",
|
||||
"@rspack/binding-linux-arm64-musl": "2.0.2",
|
||||
"@rspack/binding-linux-x64-gnu": "2.0.2",
|
||||
"@rspack/binding-linux-x64-musl": "2.0.2",
|
||||
"@rspack/binding-wasm32-wasi": "2.0.2",
|
||||
"@rspack/binding-win32-arm64-msvc": "2.0.2",
|
||||
"@rspack/binding-win32-ia32-msvc": "2.0.2",
|
||||
"@rspack/binding-win32-x64-msvc": "2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@rspack/binding-darwin-arm64": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.0.4.tgz",
|
||||
"integrity": "sha512-0Q1QXFEsZfDc4opiDnb8q50KlBbC2VovViDaYlMJZBzvjAo325mh3itXPfz7YZ31M+TxRE7TUiJXH3ltiV1Hdg==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.0.2.tgz",
|
||||
"integrity": "sha512-0o7lbgBBsDlICWdjIH0q3e0BsSco4GRiImHWVfZSVEG+q2+ykZJvSvYCVhPM1Co375Z0S3VMPa/8SjcY1FHwlw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -202,9 +202,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rspack/binding-darwin-x64": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.0.4.tgz",
|
||||
"integrity": "sha512-oO5J2QYf7+H+aYRj85EiGrDOoDEE9EDDl7NgXv46iWvIF0wXowEHXqnjMFxHxRq2Vf6scT+0yYQX9blWcvMWAA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.0.2.tgz",
|
||||
"integrity": "sha512-tOwxZpoPlTlRs/w6UyUinXJ4TYRVHMlR7+eQxO1R3muKpixvhXQjtvoaY16HuFyTVky5F0IfOoWr3x9FEsgdLg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -216,9 +216,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rspack/binding-linux-arm64-gnu": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.0.4.tgz",
|
||||
"integrity": "sha512-BEk6mIYBK4BihW9qXXITJORrVXecTlkRjrqhgefili4xjXtLdcUnxAm9sN/2oJ8m378n2h33qDh4gr2orPBFWQ==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.0.2.tgz",
|
||||
"integrity": "sha512-1ZD4YFhG1rmgqj+W8hfwHyKV8xDxGsc/3KgU0FwmiVEX7JfzhCkgBO/xlCG79kRKSrzuVzt4icO/G3cCKn0pag==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -233,9 +233,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rspack/binding-linux-arm64-musl": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.0.4.tgz",
|
||||
"integrity": "sha512-Hyt3z1RwNcSMIoaoWLN4Hb/696/O5JPukf8rXQASvf2UkC+X3ij7tr+8lMSYi3Zysi1QL375CnT4fNoABEW0JA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.0.2.tgz",
|
||||
"integrity": "sha512-/PtTkM/DsDLjeuXTmeJeRfbjCDbcL9jvoVgZrgxYFZ28y2cdLvbChbW9uigOzs5dQEs1CIBQXMTTj7KhdBTuQg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -250,9 +250,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rspack/binding-linux-x64-gnu": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.0.4.tgz",
|
||||
"integrity": "sha512-xHorBPBZAg0Pn9Q0k9dWZ9euowieDxcSOzQ9JhTCmhDY6wZH5M/kCBFlCs/OQeW5/NUArW3x3MwEdO/0QJHMxg==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.0.2.tgz",
|
||||
"integrity": "sha512-bBjsZxMHRaPo6X9SokApm6ucs+UhXtAJFyJJyuk2BH4XJsLeCU9Dz1vMwioeohFbJUUeTASVPm6/BL+RhSaunw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -267,9 +267,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rspack/binding-linux-x64-musl": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.0.4.tgz",
|
||||
"integrity": "sha512-QLxEGUXofF0kVNU12Y2NT3Qi9lGs+WbnYpapVeb+2IXtrAXJfU7Rhy7lAp5GLMzYMQNrKKL9SVnTWKbODbNW9Q==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.0.2.tgz",
|
||||
"integrity": "sha512-HjlpInqzabDNkhVsUJpsHPqa9QYVWBViJoyWNjzXCAW0vKMDvwaphyUvokSinX8FGTlZi/sr5UEaHJo6XtQ35g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -284,9 +284,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rspack/binding-wasm32-wasi": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.0.4.tgz",
|
||||
"integrity": "sha512-YhN8HkiH46ONU9tm5dyoXDImDWGpU7E4SPqGI4OyAVF0445uIChurIUmTIOYcD6cg81GGeIjozWJOcb635Dcqw==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.0.2.tgz",
|
||||
"integrity": "sha512-YaRYNFLJRpkGfYjSWR7n9f+nQKtrlmrrffpAn/blc2geHcRvXoBc5SCs1idPtsLhj7H9qWWhs7ucjyHy4csWFg==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
@@ -300,9 +300,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rspack/binding-win32-arm64-msvc": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.0.4.tgz",
|
||||
"integrity": "sha512-MUlYIz82xQRN0aoiXXyEBrNVUwiOSSFRi7nuCgUKduaSdlbPWzCY31IdtOygZ06LVp5JIGUEtyqSrjQq4FrMRw==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.0.2.tgz",
|
||||
"integrity": "sha512-d/3kTEKq+asLjRFPO96t+wfWiM7DLN76VQEPDD9bc1kdsZXlVJBuvyXfsgK8bbEvKplWXYcSsokhmEnuXrLOpg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -314,9 +314,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rspack/binding-win32-ia32-msvc": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.0.4.tgz",
|
||||
"integrity": "sha512-D7UcIFMzlY2yhhyuW4Ej15gBWmTwUM5DxuObl3Kv31qRv/pmV3MsqUeG5m2dqLbUxzqPH87qnp0cArbkJQ1b+w==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.0.2.tgz",
|
||||
"integrity": "sha512-161cWineq3RW+Jdm1FAfSpXeUtYWvhB3kAbm46vNT9h/YYz+spwsFMvveAZ1nsVSVL0IC5lDBGUte7yUAY8K2g==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -328,9 +328,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rspack/binding-win32-x64-msvc": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.0.4.tgz",
|
||||
"integrity": "sha512-MnYKPfdrAEbtpKg/1SZ6cNtzreIRyQJK4APbxLLPXENdTH5QXQkaTjLMKEeJcJ51FRhI/+yNpOUm2oTHdCQ1Og==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.0.2.tgz",
|
||||
"integrity": "sha512-y7Q0S1FE+OlkL5GMqLG0PwxrPw6E1r892KhGrGKE1Vdufe5YTEx6xTPxzZ+b7N2KPD7s9G1/iJmWHQxb1+Bjkg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -342,13 +342,13 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rspack/core": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.0.4.tgz",
|
||||
"integrity": "sha512-OuxdQeeKWQpNvFBRDOcnoSaQvp6E4APM/6JJMM/k0p6oL1TEFQVGdNu3VGY4mRAsebnNBXapMVMhj+v66Bn2pg==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.0.2.tgz",
|
||||
"integrity": "sha512-VM3UHOo26uC+4QSqY5tU1ybI7KuXY5rTof8nhFOaBY9SYau0Smvr+hMSAPmrmHwknB6dXT8yaNVxrj7I+qxE1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rspack/binding": "2.0.4"
|
||||
"@rspack/binding": "2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
@@ -383,17 +383,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rspress/core": {
|
||||
"version": "2.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.13.tgz",
|
||||
"integrity": "sha512-lbaBA5eqh7wKdH98TUQEI+SfS3Z6YgaNCup3X+ttrYVLOrxN8PJvbedo6fFAcl+wP3XLy6D0pcnnzAgu8y3tdg==",
|
||||
"version": "2.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.11.tgz",
|
||||
"integrity": "sha512-4YBOFmSMFv5GWrCa80qSIW8VxqZQQS/PknVq2r7Hb7kgfB38Fzciopn3hjb3hNwI4TTRbsi/Jev2HyRWD4bYAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@mdx-js/mdx": "^3.1.1",
|
||||
"@mdx-js/react": "^3.1.1",
|
||||
"@rsbuild/core": "^2.0.7",
|
||||
"@rsbuild/core": "^2.0.5",
|
||||
"@rsbuild/plugin-react": "~2.0.0",
|
||||
"@rspress/shared": "2.0.13",
|
||||
"@rspress/shared": "2.0.11",
|
||||
"@shikijs/rehype": "^4.0.2",
|
||||
"@types/unist": "^3.0.3",
|
||||
"@unhead/react": "^2.1.15",
|
||||
@@ -411,8 +411,8 @@
|
||||
"react-dom": "^19.2.6",
|
||||
"react-lazy-with-preload": "^2.2.1",
|
||||
"react-reconciler": "0.33.0",
|
||||
"react-render-to-markdown": "19.1.0",
|
||||
"react-router-dom": "^7.15.1",
|
||||
"react-render-to-markdown": "19.0.1",
|
||||
"react-router-dom": "^7.15.0",
|
||||
"rehype-external-links": "^3.0.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-cjk-friendly": "^2.0.1",
|
||||
@@ -436,9 +436,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rspress/plugin-client-redirects": {
|
||||
"version": "2.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.13.tgz",
|
||||
"integrity": "sha512-dP753ASTvH6eDtSEulcqq2lE/kTSdOWSCw0nzvXG+7atTWTHDp6z47uH3CGD8E78cBuKyEi4OH+U7V0EtCTc0Q==",
|
||||
"version": "2.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.11.tgz",
|
||||
"integrity": "sha512-DI9vod5mGccg57c19CuFpN3mGP1FEEueOUnEUz1UHXSyXg9YTj+ox7Xla4jUUzAzoPVGiWSSsfbtCTwdoxAsbg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -449,9 +449,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rspress/plugin-sitemap": {
|
||||
"version": "2.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.13.tgz",
|
||||
"integrity": "sha512-JtkNlxNuA7BzknKIrLvLQkSk0XVi7OXzrE76ma/cLvleccNWr8LGrHtrac4IrDr+HauK4WKTM2JaHGGHUdOUKw==",
|
||||
"version": "2.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.11.tgz",
|
||||
"integrity": "sha512-046LCHgbJXdaPipWB2SWMjZcAtIrOjXGZOD92xlTjhZ74D7Mk1Nod1MQdtOEoISWedcHdgpUVXMDbB1doKBpPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -462,26 +462,26 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rspress/shared": {
|
||||
"version": "2.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.13.tgz",
|
||||
"integrity": "sha512-LmDfr7+MDNWRBbxcNoWkW68A35oRonpDJq2Jlx3L8GCzG4sAsyd6Yw0DebTWAxx7hVOXGMf37nEf1J4aOLEZfg==",
|
||||
"version": "2.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.11.tgz",
|
||||
"integrity": "sha512-7l5Pso4s597utJyisVEnd7n/40h053nfE8DwGQMeS8RLGtSwVgxFwNHsSrvQEGtFlLrg2aWWSITqnAVO1wfTew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rsbuild/core": "^2.0.7",
|
||||
"@rsbuild/core": "^2.0.5",
|
||||
"@shikijs/rehype": "^4.0.2",
|
||||
"unified": "^11.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/core": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.1.0.tgz",
|
||||
"integrity": "sha512-jLJtSJeuFffqX6/inRE1zqU5aFv2hrszvYgq3OjbAgFRZiWv7abKMDdQzYxuSDfmUPQozZvI/kuy6VMTvnvqTQ==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.0.2.tgz",
|
||||
"integrity": "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/primitive": "4.1.0",
|
||||
"@shikijs/types": "4.1.0",
|
||||
"@shikijs/primitive": "4.0.2",
|
||||
"@shikijs/types": "4.0.2",
|
||||
"@shikijs/vscode-textmate": "^10.0.2",
|
||||
"@types/hast": "^3.0.4",
|
||||
"hast-util-to-html": "^9.0.5"
|
||||
@@ -491,28 +491,28 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/engine-javascript": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.1.0.tgz",
|
||||
"integrity": "sha512-YquhawCUgaBfhsS72e2Y/dI59gCBNPHu3fEO/tvLaXrTssxZrY5ddjtNLTwndrMgPo8b3IscE+xoICDzpTmlFQ==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.0.2.tgz",
|
||||
"integrity": "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "4.1.0",
|
||||
"@shikijs/types": "4.0.2",
|
||||
"@shikijs/vscode-textmate": "^10.0.2",
|
||||
"oniguruma-to-es": "^4.3.6"
|
||||
"oniguruma-to-es": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/engine-oniguruma": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.1.0.tgz",
|
||||
"integrity": "sha512-axLpjVs45YBvvINa+dJF+NPW+KtFkNXsFr4SDw2BMj9GdeMnGxVB9PQb2xXlJYovslt/nz6giedAyOANkfc7hg==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.0.2.tgz",
|
||||
"integrity": "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "4.1.0",
|
||||
"@shikijs/types": "4.0.2",
|
||||
"@shikijs/vscode-textmate": "^10.0.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -520,26 +520,26 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/langs": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.1.0.tgz",
|
||||
"integrity": "sha512-nwOMruEkbgdZfQ/b8CgpNBVOpvG1k0N5tbmgiFeqsan401+x3ILqlzZJowSla4Agmq4hG2Uf2wh5jLTEhR8VSg==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.0.2.tgz",
|
||||
"integrity": "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "4.1.0"
|
||||
"@shikijs/types": "4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/primitive": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.1.0.tgz",
|
||||
"integrity": "sha512-zx2/2Uwj2q9X3KSyYREEhXO23xBw5WUhP4orK2lE4r+t9JGITmEe0JH+wPmJhqHpOT2bRRs6lAL945+LDvOAGw==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.0.2.tgz",
|
||||
"integrity": "sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "4.1.0",
|
||||
"@shikijs/types": "4.0.2",
|
||||
"@shikijs/vscode-textmate": "^10.0.2",
|
||||
"@types/hast": "^3.0.4"
|
||||
},
|
||||
@@ -548,16 +548,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/rehype": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-4.1.0.tgz",
|
||||
"integrity": "sha512-HQwltCcO2/UiFz44/8whyji4rP1VghLu++MgvQn+lQA8/gvuycGkay8DH8o8VAOvLBDKGOkBEw7cC1Cm33GObQ==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-4.0.2.tgz",
|
||||
"integrity": "sha512-cmPlKLD8JeojasNFoY64162ScpEdEdQUMuVodPCrv1nx1z3bjmGwoKWDruQWa/ejSznImlaeB0Ty6Q3zPaVQAA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "4.1.0",
|
||||
"@shikijs/types": "4.0.2",
|
||||
"@types/hast": "^3.0.4",
|
||||
"hast-util-to-string": "^3.0.1",
|
||||
"shiki": "4.1.0",
|
||||
"shiki": "4.0.2",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-visit": "^5.1.0"
|
||||
},
|
||||
@@ -566,22 +566,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/themes": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.1.0.tgz",
|
||||
"integrity": "sha512-emCcTnUM7yO2wltYbaxm+yLvcCI4+h8XBKc4KmJ7EZUXoSGjcCHifkI//R4OFit9ewpg7H2/9tjOuXrT2v/Knw==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.0.2.tgz",
|
||||
"integrity": "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/types": "4.1.0"
|
||||
"@shikijs/types": "4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/@shikijs/types": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.1.0.tgz",
|
||||
"integrity": "sha512-3EQWX54fMpniOrDblzAhiwiJwpiTMW6+B9DWyUd9ska483tbayFYuw47UxwuPknI31bKnySfVQ/QW+jFL4rFdA==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.0.2.tgz",
|
||||
"integrity": "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -631,9 +631,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
|
||||
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -682,9 +682,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz",
|
||||
"integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==",
|
||||
"version": "19.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
@@ -700,9 +700,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@ungap/structured-clone": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz",
|
||||
"integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
|
||||
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -1150,9 +1150,9 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/get-east-asian-width": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
|
||||
"integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz",
|
||||
"integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2809,9 +2809,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-render-to-markdown": {
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-render-to-markdown/-/react-render-to-markdown-19.1.0.tgz",
|
||||
"integrity": "sha512-dF9b3tO41ezqdmHP8X92kbHbMexJ6iC7iHw4ykC8fwiO7DgpFc9PhMoKlI+BcPzRxGcWgQSdrixVB9RykhjJpQ==",
|
||||
"version": "19.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-render-to-markdown/-/react-render-to-markdown-19.0.1.tgz",
|
||||
"integrity": "sha512-BPv48o+ubcu2JyUDIktvJXFqLIZqR7hA4mvGu1eFIofz9fogT2me9UvXwRvqvGs9jEtNaJkxZIUKUX0oiK4hDA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2822,9 +2822,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.15.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.1.tgz",
|
||||
"integrity": "sha512-R8rl9HhgikFYoPJymnUtPXWbnDb3oget6lQnfIoupbt61aT9aOhRkDsY2XRhZRyX1Z/8a5sL74fXmFNm3NRK5A==",
|
||||
"version": "7.15.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.0.tgz",
|
||||
"integrity": "sha512-HW9vYwuM8f4yx66Izy8xfrzCM+SBJluoZcCbww9A1TySax11S5Vgw6fi3ZjMONw9J4gQwngL7PzkyIpJJpJ7RQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2845,13 +2845,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "7.15.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.15.1.tgz",
|
||||
"integrity": "sha512-AzF62gjY6U9rkMq4RfP/r2EVtQ7DMfNMjyOp/flLTCrtRylLiK4wT4pSq6O8rOXZ2eXdZYJPEYe+ifomiv+Igg==",
|
||||
"version": "7.15.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.15.0.tgz",
|
||||
"integrity": "sha512-VcrVg64Fo8nwBvDscajG8gRTLIuTC6N50nb22l2HOOV4PTOHgoGp8mUjy9wLiHYoYTSYI36tUnXZgasSRFZorQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-router": "7.15.1"
|
||||
"react-router": "7.15.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
@@ -3164,18 +3164,18 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shiki": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/shiki/-/shiki-4.1.0.tgz",
|
||||
"integrity": "sha512-l/ABZPUR5v70jI10EzqfMS/I96vjSGv2y0ihUV+WYFzv0EfvW4s54m0Lg8wCrrL+2IkwBzFTuxkZjPf8b2NX9Q==",
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/shiki/-/shiki-4.0.2.tgz",
|
||||
"integrity": "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@shikijs/core": "4.1.0",
|
||||
"@shikijs/engine-javascript": "4.1.0",
|
||||
"@shikijs/engine-oniguruma": "4.1.0",
|
||||
"@shikijs/langs": "4.1.0",
|
||||
"@shikijs/themes": "4.1.0",
|
||||
"@shikijs/types": "4.1.0",
|
||||
"@shikijs/core": "4.0.2",
|
||||
"@shikijs/engine-javascript": "4.0.2",
|
||||
"@shikijs/engine-oniguruma": "4.0.2",
|
||||
"@shikijs/langs": "4.0.2",
|
||||
"@shikijs/themes": "4.0.2",
|
||||
"@shikijs/types": "4.0.2",
|
||||
"@shikijs/vscode-textmate": "^10.0.2",
|
||||
"@types/hast": "^3.0.4"
|
||||
},
|
||||
|
||||
+3
-19
@@ -5,7 +5,7 @@
|
||||
"osvVulnerabilityAlerts": true,
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true,
|
||||
"schedule": ["* * * * 0,6"]
|
||||
"schedule": ["at any time"]
|
||||
},
|
||||
"platformAutomerge": true,
|
||||
"nix": {
|
||||
@@ -66,17 +66,6 @@
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"groupName": "github-actions-non-major"
|
||||
},
|
||||
{
|
||||
"description": "Batch GitHub Actions digest updates",
|
||||
"matchManagers": ["github-actions"],
|
||||
"matchUpdateTypes": ["digest"],
|
||||
"groupName": "github-actions-digest",
|
||||
"automerge": true,
|
||||
"automergeStrategy": "fast-forward",
|
||||
"schedule": [
|
||||
"* 0-7 * * 2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Batch patch-level Node.js dependency updates",
|
||||
"matchManagers": ["npm"],
|
||||
@@ -94,10 +83,7 @@
|
||||
"matchPackageNames": ["crate-ci/typos"],
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"automerge": true,
|
||||
"automergeStrategy": "fast-forward",
|
||||
"schedule": [
|
||||
"* 0-7 * * 3"
|
||||
]
|
||||
"automergeStrategy": "fast-forward"
|
||||
},
|
||||
{
|
||||
"description": "Auto-merge renovatebot docker image updates",
|
||||
@@ -105,9 +91,7 @@
|
||||
"matchPackageNames": ["ghcr.io/renovatebot/renovate"],
|
||||
"automerge": true,
|
||||
"automergeStrategy": "fast-forward",
|
||||
"schedule": [
|
||||
"* 0-7 * * 1"
|
||||
]
|
||||
"extends": ["schedule:earlyMondays"]
|
||||
}
|
||||
],
|
||||
"customManagers": [
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
[toolchain]
|
||||
profile = "minimal"
|
||||
channel = "1.95.0"
|
||||
channel = "1.92.0"
|
||||
components = [
|
||||
# For rust-analyzer
|
||||
"rust-src",
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ use crate::{
|
||||
};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name = conduwuit_core::BRANDING, version = conduwuit_core::version())]
|
||||
#[command(name = conduwuit_core::name(), version = conduwuit_core::version())]
|
||||
pub enum AdminCommand {
|
||||
#[command(subcommand)]
|
||||
/// Commands for managing appservices
|
||||
|
||||
+9
-229
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
collections::HashMap,
|
||||
fmt::Write,
|
||||
iter::once,
|
||||
time::{Instant, SystemTime},
|
||||
@@ -22,7 +22,7 @@ use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||
use lettre::message::Mailbox;
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId,
|
||||
OwnedRoomOrAliasId, OwnedServerName, RoomId, RoomVersionId, UInt,
|
||||
OwnedRoomOrAliasId, OwnedServerName, RoomId, RoomVersionId,
|
||||
api::federation::event::get_room_state, events::AnyStateEvent, serde::Raw,
|
||||
};
|
||||
use service::rooms::{
|
||||
@@ -69,205 +69,6 @@ pub(super) async fn get_auth_chain(&self, event_id: OwnedEventId) -> Result {
|
||||
self.write_str(&out).await
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
enum NodeStatus {
|
||||
Normal(bool),
|
||||
SoftFailed(bool),
|
||||
Rejected(bool),
|
||||
}
|
||||
|
||||
struct AuthChild {
|
||||
node_id: String,
|
||||
event_id: OwnedEventId,
|
||||
depth: UInt,
|
||||
ts: UInt,
|
||||
first_seen: bool,
|
||||
pdu: Option<PduEvent>,
|
||||
}
|
||||
|
||||
fn render_node(
|
||||
graph: &mut String,
|
||||
node_id: &str,
|
||||
event_id: &EventId,
|
||||
name: &str,
|
||||
status: NodeStatus,
|
||||
) -> Result {
|
||||
let evt_str = event_id.to_string();
|
||||
|
||||
let status_label = match status {
|
||||
| NodeStatus::Normal(false) => format!("{evt_str}: {name}"),
|
||||
| NodeStatus::Normal(true) => format!("{evt_str}: {name} (missing locally)"),
|
||||
| NodeStatus::SoftFailed(false) => format!("{evt_str}: {name} (soft-failed)"),
|
||||
| NodeStatus::SoftFailed(true) =>
|
||||
format!("{evt_str}: {name} (soft-failed & missing locally)"),
|
||||
| NodeStatus::Rejected(false) => format!("{evt_str}: {name} (rejected)"),
|
||||
| NodeStatus::Rejected(true) => format!("{evt_str}: {name} (rejected & missing locally)"),
|
||||
};
|
||||
|
||||
writeln!(graph, "{node_id}[\"{}\"]", status_label.as_str())?;
|
||||
|
||||
match status {
|
||||
| NodeStatus::Rejected(_) => writeln!(graph, "class {node_id} rejected;")?,
|
||||
| NodeStatus::SoftFailed(_) => writeln!(graph, "class {node_id} soft_failed;")?,
|
||||
| NodeStatus::Normal(_) => {},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn show_auth_chain(&self, event_id: OwnedEventId) -> Result {
|
||||
let node_status = async |event_id: &EventId, missing: bool| -> NodeStatus {
|
||||
if self
|
||||
.services
|
||||
.rooms
|
||||
.pdu_metadata
|
||||
.is_event_rejected(event_id)
|
||||
.await
|
||||
{
|
||||
NodeStatus::Rejected(missing)
|
||||
} else if self
|
||||
.services
|
||||
.rooms
|
||||
.pdu_metadata
|
||||
.is_event_soft_failed(event_id)
|
||||
.await
|
||||
{
|
||||
NodeStatus::SoftFailed(missing)
|
||||
} else {
|
||||
NodeStatus::Normal(missing)
|
||||
}
|
||||
};
|
||||
|
||||
let Ok(root) = self.services.rooms.timeline.get_pdu(&event_id).await else {
|
||||
return Err!("Event not found.");
|
||||
};
|
||||
|
||||
let mut graph = String::from(
|
||||
"```mermaid\n%% This is a mermaid graph. You can plug this output into\n\
|
||||
%% https://mermaid.live/edit to visualise it on-the-fly.\nflowchart TD\n\
|
||||
classDef rejected fill:#ffe5e5,stroke:#cc0000,stroke-width:2px,color:#000;\n\
|
||||
classDef soft_failed fill:#fff6cc,stroke:#c9a400,stroke-width:2px,color:#000;\n"
|
||||
);
|
||||
|
||||
let mut node_ids: HashMap<OwnedEventId, String> = HashMap::new();
|
||||
let mut cached_events: HashMap<OwnedEventId, PduEvent> =
|
||||
HashMap::from([(event_id.clone(), root.clone())]);
|
||||
let mut scheduled: HashSet<OwnedEventId> = HashSet::from([event_id.clone()]);
|
||||
let mut visited: HashSet<OwnedEventId> = HashSet::new();
|
||||
let mut stack = vec![root];
|
||||
let mut next_node_id = 0_usize;
|
||||
|
||||
let node_id_for = |event_id: &OwnedEventId,
|
||||
node_ids: &mut HashMap<OwnedEventId, String>,
|
||||
next_node_id: &mut usize| {
|
||||
node_ids
|
||||
.entry(event_id.clone())
|
||||
.or_insert_with(|| {
|
||||
let id = format!("n{}", *next_node_id);
|
||||
*next_node_id = next_node_id.saturating_add(1);
|
||||
id
|
||||
})
|
||||
.clone()
|
||||
};
|
||||
let node_name = |e: &PduEvent| {
|
||||
if let Some(state_key) = e.state_key() {
|
||||
format!("{},'{}'", e.event_type(), state_key)
|
||||
} else {
|
||||
format!("{}", e.event_type())
|
||||
}
|
||||
};
|
||||
|
||||
while let Some(event) = stack.pop() {
|
||||
let current_event_id = event.event_id().to_owned();
|
||||
if !visited.insert(current_event_id.clone()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let current_node_id = node_id_for(¤t_event_id, &mut node_ids, &mut next_node_id);
|
||||
let current_status = node_status(¤t_event_id, false).await;
|
||||
|
||||
render_node(
|
||||
&mut graph,
|
||||
¤t_node_id,
|
||||
¤t_event_id,
|
||||
&node_name(&event),
|
||||
current_status,
|
||||
)?;
|
||||
|
||||
let mut children = Vec::with_capacity(event.auth_events.len());
|
||||
for auth_event_id in event.auth_events().rev() {
|
||||
let auth_event_id = auth_event_id.to_owned();
|
||||
let auth_node_id = node_id_for(&auth_event_id, &mut node_ids, &mut next_node_id);
|
||||
writeln!(graph, "{current_node_id} --> {auth_node_id}")?;
|
||||
|
||||
let first_seen = scheduled.insert(auth_event_id.clone());
|
||||
let auth_pdu = if let Some(auth_pdu) = cached_events.get(&auth_event_id) {
|
||||
// NOTE: events might be referenced multiple times (like the create event)
|
||||
// so this saves some cheeky db lookup time
|
||||
Some(auth_pdu.clone())
|
||||
} else if first_seen {
|
||||
match self.services.rooms.timeline.get_pdu(&auth_event_id).await {
|
||||
| Ok(auth_event) => {
|
||||
cached_events.insert(auth_event_id.clone(), auth_event.clone());
|
||||
Some(auth_event)
|
||||
},
|
||||
| Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// NOTE: Depth is used as the primary sorting key here, even though it has no
|
||||
// bearing on state resolution or anything. Timestamp is used as a
|
||||
// tiebreaker, failing back to lexicographical comparison.
|
||||
let (depth, ts) = auth_pdu
|
||||
.as_ref()
|
||||
.map_or((UInt::MAX, UInt::MAX), |pdu| (pdu.depth, pdu.origin_server_ts));
|
||||
|
||||
children.push(AuthChild {
|
||||
node_id: auth_node_id,
|
||||
event_id: auth_event_id,
|
||||
depth,
|
||||
ts,
|
||||
first_seen,
|
||||
pdu: auth_pdu,
|
||||
});
|
||||
}
|
||||
|
||||
children.sort_by(|a, b| {
|
||||
a.depth
|
||||
.cmp(&b.depth)
|
||||
.then(a.ts.cmp(&b.ts))
|
||||
.then(a.event_id.as_str().cmp(b.event_id.as_str()))
|
||||
});
|
||||
|
||||
for child in children.into_iter().rev() {
|
||||
if !child.first_seen {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(child_pdu) = child.pdu {
|
||||
// We have this PDU so will want to traverse it.
|
||||
stack.push(child_pdu);
|
||||
} else {
|
||||
// We don't have this PDU locally so we can't traverse its auth events,
|
||||
// but we can still render it as a node.
|
||||
render_node(
|
||||
&mut graph,
|
||||
&child.node_id,
|
||||
&child.event_id,
|
||||
"",
|
||||
node_status(&child.event_id, true).await,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
graph.push_str("```\n");
|
||||
self.write_str(&graph).await
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn parse_pdu(&self) -> Result {
|
||||
if self.body.len() < 2
|
||||
@@ -310,31 +111,15 @@ pub(super) async fn get_pdu(&self, event_id: OwnedEventId) -> Result {
|
||||
outlier = true;
|
||||
pdu_json = self.services.rooms.timeline.get_pdu_json(&event_id).await;
|
||||
}
|
||||
let rejected = self
|
||||
.services
|
||||
.rooms
|
||||
.pdu_metadata
|
||||
.is_event_rejected(&event_id)
|
||||
.await;
|
||||
let soft_failed = self
|
||||
.services
|
||||
.rooms
|
||||
.pdu_metadata
|
||||
.is_event_soft_failed(&event_id)
|
||||
.await;
|
||||
|
||||
match pdu_json {
|
||||
| Err(_) => return Err!("PDU not found locally."),
|
||||
| Ok(json) => {
|
||||
let text = serde_json::to_string_pretty(&json)?;
|
||||
let msg = if rejected {
|
||||
"Rejected PDU:"
|
||||
} else if soft_failed {
|
||||
"Soft-failed PDU:"
|
||||
} else if outlier {
|
||||
"Outlier PDU:"
|
||||
let msg = if outlier {
|
||||
"Outlier (Rejected / Soft Failed) PDU found in our database"
|
||||
} else {
|
||||
"PDU:"
|
||||
"PDU found in our database"
|
||||
};
|
||||
write!(self, "{msg}\n```json\n{text}\n```")
|
||||
},
|
||||
@@ -829,10 +614,6 @@ pub(super) async fn force_set_room_state_from_server(
|
||||
.await;
|
||||
|
||||
state.insert(shortstatekey, pdu.event_id.clone());
|
||||
self.services
|
||||
.rooms
|
||||
.pdu_metadata
|
||||
.clear_pdu_markers(pdu.event_id());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -850,10 +631,6 @@ pub(super) async fn force_set_room_state_from_server(
|
||||
.rooms
|
||||
.outlier
|
||||
.add_pdu_outlier(&event_id, &value);
|
||||
self.services
|
||||
.rooms
|
||||
.pdu_metadata
|
||||
.clear_pdu_markers(&event_id);
|
||||
}
|
||||
|
||||
info!("Resolving new room state");
|
||||
@@ -885,7 +662,10 @@ pub(super) async fn force_set_room_state_from_server(
|
||||
.force_state(room_id.clone().as_ref(), short_state_hash, added, removed, &state_lock)
|
||||
.await?;
|
||||
|
||||
info!("Updating joined counts for room");
|
||||
info!(
|
||||
"Updating joined counts for room just in case (e.g. we may have found a difference in \
|
||||
the room's m.room.member state"
|
||||
);
|
||||
self.services
|
||||
.rooms
|
||||
.state_cache
|
||||
|
||||
+1
-10
@@ -17,21 +17,12 @@ pub enum DebugCommand {
|
||||
message: Vec<String>,
|
||||
},
|
||||
|
||||
/// Loads the auth_chain of a PDU, reporting how long it took.
|
||||
/// Get the auth_chain of a PDU
|
||||
GetAuthChain {
|
||||
/// An event ID (the $ character followed by the base64 reference hash)
|
||||
event_id: OwnedEventId,
|
||||
},
|
||||
|
||||
/// Walks & displays the auth_chain of a PDU in a mermaid graph format.
|
||||
///
|
||||
/// This is useless to basically anyone but developers, and is also probably
|
||||
/// slow and memory hungry.
|
||||
ShowAuthChain {
|
||||
/// The root event ID to start walking back from.
|
||||
event_id: OwnedEventId,
|
||||
},
|
||||
|
||||
/// Parse and print a PDU from a JSON
|
||||
///
|
||||
/// The PDU event is only checked for validity and is not added to the
|
||||
|
||||
@@ -30,37 +30,14 @@ pub(super) async fn issue_token(&self, expires: super::TokenExpires) -> Result {
|
||||
.issue_token(self.sender_or_service_user().into(), expires);
|
||||
|
||||
self.write_str(&format!(
|
||||
"New registration token issued: `{token}` . {}.",
|
||||
"New registration token issued: `{token}`. {}.",
|
||||
if let Some(expires) = info.expires {
|
||||
format!("{expires}")
|
||||
} else {
|
||||
"Never expires".to_owned()
|
||||
}
|
||||
))
|
||||
.await?;
|
||||
|
||||
if self
|
||||
.services
|
||||
.config
|
||||
.oauth
|
||||
.compatibility_mode
|
||||
.oauth_available()
|
||||
{
|
||||
self.write_str(&format!(
|
||||
"\nInvite link using this token: {}",
|
||||
self.services
|
||||
.config
|
||||
.get_client_domain()
|
||||
.join(&format!(
|
||||
"{}/account/register/?flow=trusted&token={token}",
|
||||
conduwuit::ROUTE_PREFIX
|
||||
))
|
||||
.unwrap()
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
.await
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
|
||||
+148
-17
@@ -1,10 +1,13 @@
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::{
|
||||
collections::{BTreeMap, HashSet},
|
||||
fmt::Write as _,
|
||||
};
|
||||
|
||||
use api::client::{
|
||||
full_user_deactivate, leave_room, recreate_push_rules_and_return, remote_leave_room,
|
||||
};
|
||||
use conduwuit::{
|
||||
Err, Result, debug_warn, info,
|
||||
Err, Result, debug_warn, error, info,
|
||||
matrix::{Event, pdu::PartialPdu},
|
||||
utils::{self, ReadyExt},
|
||||
warn,
|
||||
@@ -50,22 +53,130 @@ pub(super) async fn list_users(&self) -> Result {
|
||||
#[admin_command]
|
||||
pub(super) async fn create_user(&self, username: String, password: Option<String>) -> Result {
|
||||
// Validate user id
|
||||
let user_id = self
|
||||
.services
|
||||
let user_id = parse_local_user_id(self.services, &username)?;
|
||||
|
||||
if let Err(e) = user_id.validate_strict() {
|
||||
if self.services.config.emergency_password.is_none() {
|
||||
return Err!("Username {user_id} contains disallowed characters or spaces: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
if self.services.users.exists(&user_id).await {
|
||||
return Err!("User {user_id} already exists");
|
||||
}
|
||||
|
||||
let password = password.unwrap_or_else(|| utils::random_string(AUTO_GEN_PASSWORD_LENGTH));
|
||||
|
||||
// Create user
|
||||
self.services
|
||||
.users
|
||||
.determine_registration_user_id(Some(username), None, None)
|
||||
.create(&user_id, Some(HashedPassword::new(&password)?))
|
||||
.await?;
|
||||
|
||||
let password = HashedPassword::new(
|
||||
&password.unwrap_or_else(|| utils::random_string(AUTO_GEN_PASSWORD_LENGTH)),
|
||||
)?;
|
||||
// Default to pretty displayname
|
||||
let mut displayname = user_id.localpart().to_owned();
|
||||
|
||||
// If `new_user_displayname_suffix` is set, registration will push whatever
|
||||
// content is set to the user's display name with a space before it
|
||||
if !self
|
||||
.services
|
||||
.server
|
||||
.config
|
||||
.new_user_displayname_suffix
|
||||
.is_empty()
|
||||
{
|
||||
write!(displayname, " {}", self.services.server.config.new_user_displayname_suffix)?;
|
||||
}
|
||||
|
||||
self.services
|
||||
.users
|
||||
.create_local_account(&user_id, password, None)
|
||||
.await;
|
||||
.set_displayname(&user_id, Some(displayname));
|
||||
|
||||
self.write_str(&format!("Created user {user_id}")).await
|
||||
// Initial account data
|
||||
self.services
|
||||
.account_data
|
||||
.update(
|
||||
None,
|
||||
&user_id,
|
||||
ruma::events::GlobalAccountDataEventType::PushRules
|
||||
.to_string()
|
||||
.into(),
|
||||
&serde_json::to_value(ruma::events::push_rules::PushRulesEvent::new(
|
||||
ruma::events::push_rules::PushRulesEventContent::new(
|
||||
ruma::push::Ruleset::server_default(&user_id),
|
||||
),
|
||||
))
|
||||
.unwrap(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if !self.services.server.config.auto_join_rooms.is_empty() {
|
||||
for room in &self.services.server.config.auto_join_rooms {
|
||||
let Ok(room_id) = self.services.rooms.alias.resolve(room).await else {
|
||||
error!(
|
||||
%user_id,
|
||||
"Failed to resolve room alias to room ID when attempting to auto join {room}, skipping"
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(self.services.globals.server_name(), &room_id)
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
"Skipping room {room} to automatically join as we have never joined before."
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(room_server_name) = room.server_name() {
|
||||
match self
|
||||
.services
|
||||
.rooms
|
||||
.membership
|
||||
.join_room(
|
||||
&user_id,
|
||||
&room_id,
|
||||
Some("Automatically joining this room upon registration".to_owned()),
|
||||
&[
|
||||
self.services.globals.server_name().to_owned(),
|
||||
room_server_name.to_owned(),
|
||||
],
|
||||
)
|
||||
.await
|
||||
{
|
||||
| Ok(_response) => {
|
||||
info!("Automatically joined room {room} for user {user_id}");
|
||||
},
|
||||
| Err(e) => {
|
||||
// don't return this error so we don't fail registrations
|
||||
error!(
|
||||
"Failed to automatically join room {room} for user {user_id}: {e}"
|
||||
);
|
||||
self.services
|
||||
.admin
|
||||
.send_text(&format!(
|
||||
"Failed to automatically join room {room} for user {user_id}: \
|
||||
{e}"
|
||||
))
|
||||
.await;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we dont add a device since we're not the user, just the creator
|
||||
|
||||
// Make the first user to register an administrator and disable first-run mode.
|
||||
self.services.firstrun.empower_first_user(&user_id).await?;
|
||||
|
||||
self.write_str(&format!("Created user with user_id: {user_id} and password: `{password}`"))
|
||||
.await
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
@@ -191,6 +302,31 @@ pub(super) async fn reset_password(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn issue_password_reset_link(&self, username: String) -> Result {
|
||||
use conduwuit_service::password_reset::{PASSWORD_RESET_PATH, RESET_TOKEN_QUERY_PARAM};
|
||||
|
||||
self.bail_restricted()?;
|
||||
|
||||
let mut reset_url = self
|
||||
.services
|
||||
.config
|
||||
.get_client_domain()
|
||||
.join(PASSWORD_RESET_PATH)
|
||||
.unwrap();
|
||||
|
||||
let user_id = parse_local_user_id(self.services, &username)?;
|
||||
let token = self.services.password_reset.issue_token(user_id).await?;
|
||||
reset_url
|
||||
.query_pairs_mut()
|
||||
.append_pair(RESET_TOKEN_QUERY_PARAM, &token.token);
|
||||
|
||||
self.write_str(&format!("Password reset link issued for {username}: {reset_url}"))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) -> Result {
|
||||
if self.body.len() < 2
|
||||
@@ -604,19 +740,14 @@ pub(super) async fn force_join_room(
|
||||
&self,
|
||||
user_id: String,
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
via: Option<String>,
|
||||
) -> Result {
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
let (room_id, mut servers) = self
|
||||
let (room_id, servers) = self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_with_servers(&room_id, None)
|
||||
.await?;
|
||||
if let Some(via) = via.map(ServerName::parse).transpose()? {
|
||||
servers.retain(|n| *n != via);
|
||||
servers.insert(0, via);
|
||||
}
|
||||
|
||||
assert!(
|
||||
self.services.globals.user_is_local(&user_id),
|
||||
|
||||
@@ -29,6 +29,12 @@ pub enum UserCommand {
|
||||
password: Option<String>,
|
||||
},
|
||||
|
||||
/// Issue a self-service password reset link for a user.
|
||||
IssuePasswordResetLink {
|
||||
/// Username of the user who may use the link
|
||||
username: String,
|
||||
},
|
||||
|
||||
/// Get a user's associated email address.
|
||||
GetEmail {
|
||||
user_id: String,
|
||||
@@ -173,15 +179,8 @@ pub enum UserCommand {
|
||||
|
||||
/// Manually join a local user to a room.
|
||||
ForceJoinRoom {
|
||||
/// The user to join
|
||||
user_id: String,
|
||||
/// The room to join
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
/// The server name to join via.
|
||||
///
|
||||
/// This server will always be tried first, however if more are
|
||||
/// available, they may be tried after.
|
||||
via: Option<String>,
|
||||
},
|
||||
|
||||
/// Manually leave a local user from a room.
|
||||
|
||||
@@ -24,7 +24,7 @@ use ruma::{
|
||||
power_levels::RoomPowerLevelsEventContent,
|
||||
},
|
||||
};
|
||||
use service::{mailer::messages, uiaa::UiaaInitiator, users::HashedPassword};
|
||||
use service::{mailer::messages, uiaa::Identity, users::HashedPassword};
|
||||
|
||||
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
|
||||
use crate::Ruma;
|
||||
@@ -121,7 +121,7 @@ pub(crate) async fn change_password_route(
|
||||
&body.auth,
|
||||
vec![AuthFlow::new(vec![AuthType::Password])],
|
||||
Box::default(),
|
||||
Some(UiaaInitiator::new(user_id, body.sender_device())),
|
||||
Some(Identity::from_user_id(user_id)),
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
@@ -187,7 +187,7 @@ pub(crate) async fn change_password_route(
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.notice(&format!("User {sender_user} changed their password."))
|
||||
.notice(&format!("User {} changed their password.", &sender_user))
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -270,17 +270,10 @@ pub(crate) async fn deactivate_route(
|
||||
.as_ref()
|
||||
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
|
||||
|
||||
if !services.config.allow_deactivation {
|
||||
return Err!(Request(Unauthorized(
|
||||
"You may not deactivate your own account. Contact your server's administrator for \
|
||||
assistance."
|
||||
)));
|
||||
}
|
||||
|
||||
// Prompt the user to confirm with their password using UIAA
|
||||
let _ = services
|
||||
.uiaa
|
||||
.authenticate_password(&body.auth, sender_user, body.sender_device(), None)
|
||||
.authenticate_password(&body.auth, Some(Identity::from_user_id(sender_user)))
|
||||
.await?;
|
||||
|
||||
// Remove profile pictures and display name
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
use std::collections::HashMap;
|
||||
use std::{collections::HashMap, fmt::Write};
|
||||
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::ClientIp;
|
||||
use conduwuit::{
|
||||
Err, Result, debug_info, info,
|
||||
Err, Result, debug_info, error, info,
|
||||
utils::{self},
|
||||
warn,
|
||||
};
|
||||
use conduwuit_service::Services;
|
||||
use futures::StreamExt;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use lettre::{Address, message::Mailbox};
|
||||
use ruma::{
|
||||
OwnedUserId, UserId,
|
||||
api::client::{
|
||||
account::{
|
||||
register::{self, LoginType, RegistrationKind},
|
||||
@@ -18,6 +20,11 @@ use ruma::{
|
||||
uiaa::{AuthFlow, AuthType},
|
||||
},
|
||||
assign,
|
||||
events::{
|
||||
GlobalAccountDataEventType, push_rules::PushRulesEvent,
|
||||
room::message::RoomMessageEventContent,
|
||||
},
|
||||
push,
|
||||
};
|
||||
use serde_json::value::RawValue;
|
||||
use service::{mailer::messages, users::HashedPassword};
|
||||
@@ -25,6 +32,8 @@ use service::{mailer::messages, users::HashedPassword};
|
||||
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
|
||||
use crate::Ruma;
|
||||
|
||||
const RANDOM_USER_ID_LENGTH: usize = 10;
|
||||
|
||||
/// # `POST /_matrix/client/v3/register`
|
||||
///
|
||||
/// Register an account on this homeserver.
|
||||
@@ -43,6 +52,8 @@ pub(crate) async fn register_route(
|
||||
return Err!(Request(GuestAccessForbidden("Guests may not register on this server.")));
|
||||
}
|
||||
|
||||
let emergency_mode_enabled = services.config.emergency_password.is_some();
|
||||
|
||||
// Allow registration if it's enabled in the config file or if this is the first
|
||||
// run (so the first user account can be created)
|
||||
let allow_registration =
|
||||
@@ -60,59 +71,101 @@ pub(crate) async fn register_route(
|
||||
)));
|
||||
}
|
||||
|
||||
let user_id = if body.body.login_type == Some(LoginType::ApplicationService) {
|
||||
let Some(appservice_info) = &body.appservice_info else {
|
||||
return Err!(Request(Forbidden(
|
||||
"Only appservices can use the appservice login type."
|
||||
)));
|
||||
};
|
||||
|
||||
let user_id = services
|
||||
.users
|
||||
.determine_registration_user_id(body.username.clone(), None, Some(appservice_info))
|
||||
.await?;
|
||||
|
||||
services.users.create(&user_id, None).await?;
|
||||
|
||||
user_id
|
||||
let identity = if body.appservice_info.is_some() {
|
||||
// Appservices can skip auth
|
||||
None
|
||||
} else {
|
||||
// Perform UIAA to determine the user's identity
|
||||
let (flows, params) = create_registration_uiaa_session(&services).await?;
|
||||
|
||||
let identity = services
|
||||
.uiaa
|
||||
.authenticate(&body.auth, flows, params, None)
|
||||
.await?;
|
||||
|
||||
let password = if let Some(password) = &body.password {
|
||||
HashedPassword::new(password)?
|
||||
} else {
|
||||
return Err!(Request(InvalidParam("A password must be provided.")));
|
||||
};
|
||||
|
||||
let user_id = services
|
||||
.users
|
||||
.determine_registration_user_id(body.username.clone(), identity.email.as_ref(), None)
|
||||
.await?;
|
||||
|
||||
services
|
||||
.users
|
||||
.create_local_account(&user_id, password, identity.email)
|
||||
.await;
|
||||
|
||||
user_id
|
||||
Some(
|
||||
services
|
||||
.uiaa
|
||||
.authenticate(&body.auth, flows, params, None)
|
||||
.await?,
|
||||
)
|
||||
};
|
||||
|
||||
let (token, device) = if !body.inhibit_login {
|
||||
// If UIAA is disabled, we can't create a device. In that case only appservices
|
||||
// can reach this point in the first place, so we return an error for them.
|
||||
if !services.config.oauth.compatibility_mode.uiaa_available() {
|
||||
return Err!(Request(AppserviceLoginUnsupported(
|
||||
"User-interactive appservice registration is not available on this server."
|
||||
)));
|
||||
// If the user didn't supply a username but did supply an email, use
|
||||
// the email's user as their initial localpart to avoid falling back to
|
||||
// a randomly generated localpart
|
||||
let supplied_username = body.username.clone().or_else(|| {
|
||||
if let Some(identity) = &identity
|
||||
&& let Some(email) = &identity.email
|
||||
{
|
||||
Some(email.user().to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
// Generate new device id if the user didn't specify one
|
||||
let user_id =
|
||||
determine_registration_user_id(&services, supplied_username, emergency_mode_enabled)
|
||||
.await?;
|
||||
|
||||
if body.body.login_type == Some(LoginType::ApplicationService) {
|
||||
// For appservice logins, make sure that the user ID is in the appservice's
|
||||
// namespace
|
||||
|
||||
match body.appservice_info {
|
||||
| Some(ref info) =>
|
||||
if !info.is_user_match(&user_id) && !emergency_mode_enabled {
|
||||
return Err!(Request(Exclusive(
|
||||
"Username is not in an appservice namespace."
|
||||
)));
|
||||
},
|
||||
| _ => {
|
||||
return Err!(Request(MissingToken("Missing appservice token.")));
|
||||
},
|
||||
}
|
||||
} else if services.appservice.is_exclusive_user_id(&user_id).await && !emergency_mode_enabled
|
||||
{
|
||||
// For non-appservice logins, ban user IDs which are in an appservice's
|
||||
// namespace (unless emergency mode is enabled)
|
||||
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
|
||||
}
|
||||
|
||||
let password = if body.appservice_info.is_some() {
|
||||
None
|
||||
} else if let Some(password) = body.password.as_deref() {
|
||||
Some(HashedPassword::new(password)?)
|
||||
} else {
|
||||
return Err!(Request(InvalidParam("A password must be provided")));
|
||||
};
|
||||
|
||||
// Create user
|
||||
services.users.create(&user_id, password).await?;
|
||||
|
||||
// Set an initial display name
|
||||
let mut displayname = user_id.localpart().to_owned();
|
||||
|
||||
// Apply the new user displayname suffix, if it's set
|
||||
if !services.globals.new_user_displayname_suffix().is_empty()
|
||||
&& body.appservice_info.is_none()
|
||||
{
|
||||
write!(displayname, " {}", services.server.config.new_user_displayname_suffix)?;
|
||||
}
|
||||
|
||||
services
|
||||
.users
|
||||
.set_displayname(&user_id, Some(displayname.clone()));
|
||||
|
||||
// Initial account data
|
||||
services
|
||||
.account_data
|
||||
.update(
|
||||
None,
|
||||
&user_id,
|
||||
GlobalAccountDataEventType::PushRules.to_string().into(),
|
||||
&serde_json::to_value(PushRulesEvent::new(
|
||||
push::Ruleset::server_default(&user_id).into(),
|
||||
))
|
||||
.expect("should be able to serialize push rules"),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Generate new device id if the user didn't specify one
|
||||
let (token, device) = if !body.inhibit_login {
|
||||
let device_id = body
|
||||
.device_id
|
||||
.clone()
|
||||
@@ -128,7 +181,6 @@ pub(crate) async fn register_route(
|
||||
&user_id,
|
||||
&device_id,
|
||||
&new_token,
|
||||
None,
|
||||
body.initial_device_display_name.clone(),
|
||||
Some(client.to_string()),
|
||||
)
|
||||
@@ -139,7 +191,118 @@ pub(crate) async fn register_route(
|
||||
(None, None)
|
||||
};
|
||||
|
||||
debug_info!(%user_id, ?device, "New account created via legacy registration");
|
||||
debug_info!(%user_id, ?device, "User account was created");
|
||||
|
||||
// If the user registered with an email, associate it with their account.
|
||||
if let Some(identity) = identity
|
||||
&& let Some(email) = identity.email
|
||||
{
|
||||
// This may fail if the email is already in use, but we already check for that
|
||||
// in `/requestToken`, so ignoring the error is acceptable here in the rare case
|
||||
// that an email is sniped by another user between the `/requestToken` request
|
||||
// and the `/register` request.
|
||||
let _ = services
|
||||
.threepid
|
||||
.associate_localpart_email(user_id.localpart(), &email)
|
||||
.await;
|
||||
}
|
||||
|
||||
let device_display_name = body.initial_device_display_name.as_deref().unwrap_or("");
|
||||
|
||||
if body.appservice_info.is_none() {
|
||||
if !device_display_name.is_empty() {
|
||||
let notice = format!(
|
||||
"New user \"{user_id}\" registered on this server from IP {client} and device \
|
||||
display name \"{device_display_name}\""
|
||||
);
|
||||
|
||||
info!("{notice}");
|
||||
if services.server.config.admin_room_notices {
|
||||
services.admin.notice(¬ice).await;
|
||||
}
|
||||
} else {
|
||||
let notice = format!("New user \"{user_id}\" registered on this server.");
|
||||
|
||||
info!("{notice}");
|
||||
if services.server.config.admin_room_notices {
|
||||
services.admin.notice(¬ice).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make the first user to register an administrator and disable first-run mode.
|
||||
let was_first_user = services.firstrun.empower_first_user(&user_id).await?;
|
||||
|
||||
// If the registering user was not the first and we're suspending users on
|
||||
// register, suspend them.
|
||||
if !was_first_user && services.config.suspend_on_register {
|
||||
// Note that we can still do auto joins for suspended users
|
||||
services
|
||||
.users
|
||||
.suspend_account(&user_id, &services.globals.server_user)
|
||||
.await;
|
||||
// And send an @room notice to the admin room, to prompt admins to review the
|
||||
// new user and ideally unsuspend them if deemed appropriate.
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_loud_message(RoomMessageEventContent::text_plain(format!(
|
||||
"User {user_id} has been suspended as they are not the first user on this \
|
||||
server. Please review and unsuspend them if appropriate."
|
||||
)))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
if body.appservice_info.is_none() && !services.server.config.auto_join_rooms.is_empty() {
|
||||
for room in &services.server.config.auto_join_rooms {
|
||||
let Ok(room_id) = services.rooms.alias.resolve(room).await else {
|
||||
error!(
|
||||
"Failed to resolve room alias to room ID when attempting to auto join \
|
||||
{room}, skipping"
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
if !services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(services.globals.server_name(), &room_id)
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
"Skipping room {room} to automatically join as we have never joined before."
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(room_server_name) = room.server_name() {
|
||||
match services
|
||||
.rooms
|
||||
.membership
|
||||
.join_room(
|
||||
&user_id,
|
||||
&room_id,
|
||||
Some("Automatically joining this room upon registration".to_owned()),
|
||||
&[services.globals.server_name().to_owned(), room_server_name.to_owned()],
|
||||
)
|
||||
.boxed()
|
||||
.await
|
||||
{
|
||||
| Err(e) => {
|
||||
// don't return this error so we don't fail registrations
|
||||
error!(
|
||||
"Failed to automatically join room {room} for user {user_id}: {e}"
|
||||
);
|
||||
},
|
||||
| _ => {
|
||||
info!("Automatically joined room {room} for user {user_id}");
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(assign!(register::v3::Response::new(user_id), {
|
||||
access_token: token,
|
||||
@@ -211,21 +374,21 @@ async fn create_registration_uiaa_session(
|
||||
|
||||
// Require all users to agree to the terms and conditions, if configured
|
||||
let terms = &services.config.registration_terms;
|
||||
if !terms.documents.is_empty() {
|
||||
let mut terms_map = HashMap::new();
|
||||
if !terms.is_empty() {
|
||||
let mut terms =
|
||||
serde_json::to_value(terms.clone()).expect("failed to serialize terms");
|
||||
|
||||
for (id, document) in &terms.documents {
|
||||
terms_map.insert(id.to_owned(), serde_json::json!({
|
||||
terms.language.clone(): serde_json::to_value(document).expect("should be able to serialize document")
|
||||
}));
|
||||
// Insert a dummy `version` field
|
||||
for (_, documents) in terms.as_object_mut().unwrap() {
|
||||
let documents = documents.as_object_mut().unwrap();
|
||||
|
||||
documents.insert("version".to_owned(), "latest".into());
|
||||
}
|
||||
|
||||
terms_map.insert("version".to_owned(), "latest".into());
|
||||
|
||||
params.insert(
|
||||
AuthType::Terms.as_str().to_owned(),
|
||||
serde_json::json!({
|
||||
"policies": terms_map,
|
||||
"policies": terms,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -258,6 +421,81 @@ async fn create_registration_uiaa_session(
|
||||
Ok((flows, params))
|
||||
}
|
||||
|
||||
async fn determine_registration_user_id(
|
||||
services: &Services,
|
||||
supplied_username: Option<String>,
|
||||
emergency_mode_enabled: bool,
|
||||
) -> Result<OwnedUserId> {
|
||||
if let Some(supplied_username) = supplied_username {
|
||||
// The user gets to pick their username. Do some validation to make sure it's
|
||||
// acceptable.
|
||||
|
||||
// Don't allow registration with forbidden usernames.
|
||||
if services
|
||||
.globals
|
||||
.forbidden_usernames()
|
||||
.is_match(&supplied_username)
|
||||
&& !emergency_mode_enabled
|
||||
{
|
||||
return Err!(Request(Forbidden("Username is forbidden")));
|
||||
}
|
||||
|
||||
// Create and validate the user ID
|
||||
let user_id = match UserId::parse_with_server_name(
|
||||
&supplied_username,
|
||||
services.globals.server_name(),
|
||||
) {
|
||||
| Ok(user_id) => {
|
||||
if let Err(e) = user_id.validate_strict() {
|
||||
// Unless we are in emergency mode, we should follow synapse's behaviour on
|
||||
// not allowing things like spaces and UTF-8 characters in usernames
|
||||
if !emergency_mode_enabled {
|
||||
return Err!(Request(InvalidUsername(debug_warn!(
|
||||
"Username {supplied_username} contains disallowed characters or \
|
||||
spaces: {e}"
|
||||
))));
|
||||
}
|
||||
}
|
||||
|
||||
// Don't allow registration with user IDs that aren't local
|
||||
if !services.globals.user_is_local(&user_id) {
|
||||
return Err!(Request(InvalidUsername(
|
||||
"Username {supplied_username} is not local to this server"
|
||||
)));
|
||||
}
|
||||
|
||||
user_id
|
||||
},
|
||||
| Err(e) => {
|
||||
return Err!(Request(InvalidUsername(debug_warn!(
|
||||
"Username {supplied_username} is not valid: {e}"
|
||||
))));
|
||||
},
|
||||
};
|
||||
|
||||
if services.users.exists(&user_id).await {
|
||||
return Err!(Request(UserInUse("User ID is not available.")));
|
||||
}
|
||||
|
||||
Ok(user_id)
|
||||
} else {
|
||||
// The user didn't specify a username. Generate a username for
|
||||
// them.
|
||||
|
||||
loop {
|
||||
let user_id = UserId::parse_with_server_name(
|
||||
utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(),
|
||||
services.globals.server_name(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if !services.users.exists(&user_id).await {
|
||||
break Ok(user_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/v3/register/email/requestToken`
|
||||
///
|
||||
/// Requests a validation email for the purpose of registering a new account.
|
||||
|
||||
@@ -11,7 +11,7 @@ use ruma::{
|
||||
},
|
||||
thirdparty::{Medium, ThirdPartyIdentifierInit},
|
||||
};
|
||||
use service::mailer::messages;
|
||||
use service::{mailer::messages, uiaa::Identity};
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
@@ -116,15 +116,14 @@ pub(crate) async fn add_3pid_route(
|
||||
// Require password auth to add an email
|
||||
let _ = services
|
||||
.uiaa
|
||||
.authenticate_password(&body.auth, sender_user, body.sender_device(), None)
|
||||
.authenticate_password(&body.auth, Some(Identity::from_user_id(sender_user)))
|
||||
.await?;
|
||||
|
||||
let email = services
|
||||
.threepid
|
||||
.get_valid_session(&body.sid, &body.client_secret)
|
||||
.consume_valid_session(&body.sid, &body.client_secret)
|
||||
.await
|
||||
.map_err(|message| err!(Request(ThreepidAuthFailed("{message}"))))?
|
||||
.consume();
|
||||
.map_err(|message| err!(Request(ThreepidAuthFailed("{message}"))))?;
|
||||
|
||||
services
|
||||
.threepid
|
||||
|
||||
@@ -8,6 +8,7 @@ use ruma::{
|
||||
self, delete_device, delete_devices, get_device, get_devices, update_device,
|
||||
},
|
||||
};
|
||||
use service::uiaa::Identity;
|
||||
|
||||
use crate::{Ruma, client::DEVICE_ID_LENGTH};
|
||||
|
||||
@@ -94,7 +95,6 @@ pub(crate) async fn update_device_route(
|
||||
&device_id,
|
||||
&appservice.registration.as_token,
|
||||
None,
|
||||
None,
|
||||
Some(client.to_string()),
|
||||
)
|
||||
.await?;
|
||||
@@ -126,7 +126,7 @@ pub(crate) async fn delete_device_route(
|
||||
// Prompt the user to confirm with their password using UIAA
|
||||
let _ = services
|
||||
.uiaa
|
||||
.authenticate_password(&body.auth, sender_user, body.sender_device(), None)
|
||||
.authenticate_password(&body.auth, Some(Identity::from_user_id(sender_user)))
|
||||
.await?;
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ pub(crate) async fn delete_devices_route(
|
||||
// Prompt the user to confirm with their password using UIAA
|
||||
let _ = services
|
||||
.uiaa
|
||||
.authenticate_password(&body.auth, sender_user, body.sender_device(), None)
|
||||
.authenticate_password(&body.auth, Some(Identity::from_user_id(sender_user)))
|
||||
.await?;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ use ruma::{
|
||||
serde::Raw,
|
||||
};
|
||||
use serde_json::json;
|
||||
use service::oauth::OAuthTicket;
|
||||
use service::uiaa::Identity;
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
@@ -204,12 +204,7 @@ pub(crate) async fn upload_signing_keys_route(
|
||||
{
|
||||
let _ = services
|
||||
.uiaa
|
||||
.authenticate_password(
|
||||
&body.auth,
|
||||
sender_user,
|
||||
body.sender_device(),
|
||||
Some(OAuthTicket::CrossSigningReset),
|
||||
)
|
||||
.authenticate_password(&body.auth, Some(Identity::from_user_id(sender_user)))
|
||||
.await?;
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +105,11 @@ pub(crate) async fn banned_room_check(
|
||||
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
|
||||
}
|
||||
} else if let Some(server_name) = server_name {
|
||||
if services.moderation.is_remote_server_forbidden(server_name) {
|
||||
if services
|
||||
.config
|
||||
.forbidden_remote_server_names
|
||||
.is_match(server_name.host())
|
||||
{
|
||||
warn!(
|
||||
"User {user_id} who is not an admin tried joining a room which has the server \
|
||||
name {server_name} that is globally forbidden. Rejecting.",
|
||||
|
||||
@@ -16,7 +16,6 @@ pub(super) mod media_legacy;
|
||||
pub(super) mod membership;
|
||||
pub(super) mod message;
|
||||
pub(super) mod mutual_rooms;
|
||||
pub(super) mod oauth;
|
||||
pub(super) mod openid;
|
||||
pub(super) mod presence;
|
||||
pub(super) mod profile;
|
||||
@@ -62,7 +61,6 @@ pub(super) use membership::*;
|
||||
pub use membership::{leave_all_rooms, leave_room, remote_leave_room};
|
||||
pub(super) use message::*;
|
||||
pub(super) use mutual_rooms::*;
|
||||
pub(super) use oauth::*;
|
||||
pub(super) use openid::*;
|
||||
pub(super) use presence::*;
|
||||
pub(super) use profile::*;
|
||||
@@ -75,7 +73,6 @@ pub(super) use report::*;
|
||||
pub(super) use room::*;
|
||||
pub(super) use search::*;
|
||||
pub(super) use send::*;
|
||||
pub use session::handle_login;
|
||||
pub(super) use session::*;
|
||||
pub(super) use space::*;
|
||||
pub(super) use state::*;
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
use axum::{
|
||||
Json, Router,
|
||||
extract::{Request, State},
|
||||
middleware::{self, Next},
|
||||
response::{IntoResponse, Response},
|
||||
routing::method_routing::{get, post},
|
||||
};
|
||||
use const_str::concat;
|
||||
use http::StatusCode;
|
||||
use serde_json::json;
|
||||
pub(crate) use server_metadata::*;
|
||||
|
||||
mod register_client;
|
||||
mod server_metadata;
|
||||
mod token;
|
||||
|
||||
const BASE_PATH: &str = concat!(conduwuit_core::ROUTE_PREFIX, "/oauth2/");
|
||||
const AUTH_CODE_PATH: &str = "grant/authorization_code";
|
||||
const JWKS_URI_PATH: &str = "client/keys.json";
|
||||
const CLIENT_REGISTER_PATH: &str = "client/register";
|
||||
const TOKEN_REVOKE_PATH: &str = "client/revoke";
|
||||
const TOKEN_PATH: &str = "grant/token";
|
||||
const ACCOUNT_MANAGEMENT_PATH: &str = concat!(conduwuit_core::ROUTE_PREFIX, "/account/deeplink");
|
||||
|
||||
pub(crate) fn router(state: crate::State) -> Router<crate::State> {
|
||||
Router::new()
|
||||
.nest(BASE_PATH, oauth_router())
|
||||
.route(
|
||||
"/.well-known/openid-configuration",
|
||||
get(
|
||||
// TODO(unspecced): used by old versions of the matrix-js-sdk
|
||||
async |State(services): State<crate::State>| {
|
||||
Json(authorization_server_metadata(&services).await)
|
||||
},
|
||||
),
|
||||
)
|
||||
.layer(middleware::from_fn_with_state(
|
||||
state,
|
||||
async |State(state): State<crate::State>, request: Request, next: Next| -> Response {
|
||||
if state.config.oauth.compatibility_mode.oauth_available() {
|
||||
next.run(request).await
|
||||
} else {
|
||||
(StatusCode::NOT_FOUND, "OAuth is unavailable on this server").into_response()
|
||||
}
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn oauth_router() -> Router<crate::State> {
|
||||
Router::new()
|
||||
.route(concat!("/", CLIENT_REGISTER_PATH), post(register_client::register_client_route))
|
||||
// TODO(unspecced): used by old versions of the matrix-js-sdk
|
||||
.route(concat!("/", JWKS_URI_PATH), get(async || Json(json!({"keys": []}))))
|
||||
.route(concat!("/", TOKEN_PATH), post(token::token_route))
|
||||
.route(concat!("/", TOKEN_REVOKE_PATH), post(token::revoke_token_route))
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
use axum::{
|
||||
Json,
|
||||
extract::State,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use http::StatusCode;
|
||||
use serde::Serialize;
|
||||
use service::oauth::client_metadata::ClientMetadata;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct RegisteredClient {
|
||||
client_id: String,
|
||||
#[serde(flatten)]
|
||||
metadata: ClientMetadata,
|
||||
}
|
||||
|
||||
pub(crate) async fn register_client_route(
|
||||
State(services): State<crate::State>,
|
||||
Json(metadata): Json<ClientMetadata>,
|
||||
) -> Result<Response, Response> {
|
||||
let client_id = services
|
||||
.oauth
|
||||
.register_client(&metadata)
|
||||
.await
|
||||
.map_err(|err| (StatusCode::BAD_REQUEST, err.to_owned()).into_response())?;
|
||||
|
||||
Ok(Json(RegisteredClient { client_id, metadata }).into_response())
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, Result};
|
||||
use ruma::{
|
||||
api::client::discovery::get_authorization_server_metadata::{
|
||||
self, v1::AccountManagementAction,
|
||||
},
|
||||
serde::Raw,
|
||||
};
|
||||
use serde_json::{Value, json};
|
||||
use service::Services;
|
||||
|
||||
use crate::{
|
||||
Ruma,
|
||||
client::oauth::{
|
||||
ACCOUNT_MANAGEMENT_PATH, AUTH_CODE_PATH, CLIENT_REGISTER_PATH, JWKS_URI_PATH, TOKEN_PATH,
|
||||
TOKEN_REVOKE_PATH,
|
||||
},
|
||||
};
|
||||
|
||||
pub(crate) async fn get_authorization_server_metadata_route(
|
||||
State(services): State<crate::State>,
|
||||
_body: Ruma<get_authorization_server_metadata::v1::Request>,
|
||||
) -> Result<get_authorization_server_metadata::v1::Response> {
|
||||
if !services.config.oauth.compatibility_mode.oauth_available() {
|
||||
return Err!(Request(Unrecognized("OAuth is unavailable on this server")));
|
||||
}
|
||||
|
||||
let metadata = Raw::new(&authorization_server_metadata(&services).await).unwrap();
|
||||
|
||||
Ok(get_authorization_server_metadata::v1::Response::new(metadata.cast_unchecked()))
|
||||
}
|
||||
|
||||
pub(crate) async fn authorization_server_metadata(services: &Services) -> Value {
|
||||
let endpoint_base = services
|
||||
.config
|
||||
.get_client_domain()
|
||||
.join(super::BASE_PATH)
|
||||
.unwrap();
|
||||
|
||||
json!({
|
||||
"account_management_uri": endpoint_base.join(ACCOUNT_MANAGEMENT_PATH).unwrap(),
|
||||
"account_management_actions_supported": [
|
||||
AccountManagementAction::AccountDeactivate,
|
||||
AccountManagementAction::CrossSigningReset,
|
||||
AccountManagementAction::DeviceDelete,
|
||||
AccountManagementAction::DeviceView,
|
||||
AccountManagementAction::DevicesList,
|
||||
AccountManagementAction::Profile,
|
||||
],
|
||||
"authorization_endpoint": endpoint_base.join(AUTH_CODE_PATH).unwrap(),
|
||||
"code_challenge_methods_supported": ["S256"],
|
||||
"grant_types_supported": ["authorization_code", "refresh_token"],
|
||||
"issuer": services.config.get_client_domain(),
|
||||
"jwks_uri": endpoint_base.join(JWKS_URI_PATH).unwrap(),
|
||||
"prompt_values_supported": ["create"],
|
||||
"registration_endpoint": endpoint_base.join(CLIENT_REGISTER_PATH).unwrap(),
|
||||
"response_modes_supported": ["query", "fragment"],
|
||||
"response_types_supported": ["code"],
|
||||
"revocation_endpoint": endpoint_base.join(TOKEN_REVOKE_PATH).unwrap(),
|
||||
"token_endpoint": endpoint_base.join(TOKEN_PATH).unwrap(),
|
||||
})
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
use axum::{Form, Json, extract::State, response::IntoResponse};
|
||||
use http::StatusCode;
|
||||
use service::oauth::grant::{RevokeTokenRequest, TokenRequest};
|
||||
|
||||
pub(crate) async fn token_route(
|
||||
State(services): State<crate::State>,
|
||||
Form(request): Form<TokenRequest>,
|
||||
) -> impl IntoResponse {
|
||||
match services.oauth.issue_token(request).await {
|
||||
| Ok(response) => Ok(Json(response)),
|
||||
| Err(err) => Err((StatusCode::BAD_REQUEST, err.message())),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn revoke_token_route(
|
||||
State(services): State<crate::State>,
|
||||
Form(request): Form<RevokeTokenRequest>,
|
||||
) -> impl IntoResponse {
|
||||
match services.oauth.revoke_token(request.token).await {
|
||||
| Ok(()) => Ok(StatusCode::OK),
|
||||
| Err(err) => Err((StatusCode::BAD_REQUEST, err.message())),
|
||||
}
|
||||
}
|
||||
@@ -329,7 +329,7 @@ async fn set_profile_field(
|
||||
// If the user is local and changed their displayname or avatar_url, update it
|
||||
// in all their joined rooms
|
||||
if matches!(field_name, ProfileFieldName::AvatarUrl | ProfileFieldName::DisplayName)
|
||||
&& services.globals.user_is_local(user_id)
|
||||
&& services.users.is_active_local(user_id).await
|
||||
{
|
||||
let displayname = services.users.displayname(user_id).await.ok();
|
||||
let avatar_url = services.users.avatar_url(user_id).await.ok();
|
||||
|
||||
@@ -219,14 +219,14 @@ async fn is_event_report_valid(
|
||||
fn build_report(report: Report) -> RoomMessageEventContent {
|
||||
let mut text =
|
||||
format!("@room New {} report received from {}:\n\n", report.report_type, report.sender);
|
||||
if let Some(user_id) = report.user_id {
|
||||
let _ = writeln!(text, "- Reported User ID: `{user_id}`");
|
||||
if report.user_id.is_some() {
|
||||
let _ = writeln!(text, "- Reported User ID: `{}`", report.user_id.unwrap());
|
||||
}
|
||||
if let Some(room_id) = report.room_id {
|
||||
let _ = writeln!(text, "- Reported Room ID: `{room_id}`");
|
||||
if report.room_id.is_some() {
|
||||
let _ = writeln!(text, "- Reported Room ID: `{}`", report.room_id.unwrap());
|
||||
}
|
||||
if let Some(event_id) = report.event_id {
|
||||
let _ = writeln!(text, "- Reported Event ID: `{event_id}`");
|
||||
if report.event_id.is_some() {
|
||||
let _ = writeln!(text, "- Reported Event ID: `{}`", report.event_id.unwrap());
|
||||
}
|
||||
let _ = writeln!(text, "- Report Reason: {}", report.reason);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ use conduwuit_service::{Services, appservice::RegistrationInfo};
|
||||
use futures::FutureExt;
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue, Int, MilliSecondsSinceUnixEpoch, OwnedRoomAliasId,
|
||||
OwnedUserId, RoomAliasId, RoomId, RoomVersionId, UserId,
|
||||
OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, RoomVersionId, UserId,
|
||||
api::client::room::{self, create_room},
|
||||
assign,
|
||||
events::{
|
||||
@@ -24,7 +24,6 @@ use ruma::{
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
name::RoomNameEventContent,
|
||||
power_levels::RoomPowerLevelsEventContent,
|
||||
server_acl::RoomServerAclEventContent,
|
||||
topic::RoomTopicEventContent,
|
||||
},
|
||||
},
|
||||
@@ -87,41 +86,25 @@ pub(crate) async fn create_room_route(
|
||||
};
|
||||
let room_version_rules = room_version.rules().unwrap();
|
||||
|
||||
// For custom room IDs, if the user is creating a room with a v1 room ID format,
|
||||
// we can just use that ID directly. However, if it's a custom *v2* room ID, we
|
||||
// need to make sure that we don't generate one, which would in turn trick us
|
||||
// into generating invalid v2 room events.
|
||||
//
|
||||
// expect_room_id is the custom room ID that the user is expecting - for v2
|
||||
// formatted rooms, we check that the m.room.create event's generated room ID
|
||||
// exactly matches this, and abort if it doesn't. Otherwise, we use it as the
|
||||
// room ID itself.
|
||||
let expect_room_id = {
|
||||
let body_ref = body.json_body.as_ref().unwrap();
|
||||
if let Some(CanonicalJsonValue::String(room_id)) = body_ref
|
||||
.get("fi.mau.room_id")
|
||||
.or_else(|| body_ref.get("room_id"))
|
||||
{
|
||||
Some(
|
||||
RoomId::parse(room_id)
|
||||
.map_err(|e| err!(Request(BadJson("Malformed custom room ID: {e}"))))?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let room_id = match room_version_rules.room_id_format {
|
||||
| RoomIdFormatVersion::V1 => Some(
|
||||
expect_room_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| RoomId::new_v1(services.globals.server_name())),
|
||||
),
|
||||
let room_id: Option<OwnedRoomId> = match room_version_rules.room_id_format {
|
||||
| RoomIdFormatVersion::V1 => {
|
||||
// Check for custom room ID field
|
||||
if let Some(CanonicalJsonValue::String(room_id)) =
|
||||
body.json_body.as_ref().unwrap().get("room_id")
|
||||
{
|
||||
Some(
|
||||
RoomId::parse(room_id)
|
||||
.map_err(|_| err!(Request(BadJson("Malformed custom room ID"))))?,
|
||||
)
|
||||
} else {
|
||||
Some(RoomId::new_v1(services.globals.server_name()))
|
||||
}
|
||||
},
|
||||
| _ => None,
|
||||
};
|
||||
|
||||
// check if room ID doesn't already exist instead of erroring on auth check
|
||||
if let Some(room_id) = room_id.as_ref().or(expect_room_id.as_ref()) {
|
||||
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",)));
|
||||
}
|
||||
@@ -260,16 +243,15 @@ pub(crate) async fn create_room_route(
|
||||
|
||||
// Allow requesters to override the `origin_server_ts` to customize room ids
|
||||
// from v12 onwards
|
||||
let custom_origin_server_ts = {
|
||||
let body_ref = body.json_body.as_ref().unwrap();
|
||||
body_ref
|
||||
.get("origin_server_ts")
|
||||
.or_else(|| body_ref.get("fi.mau.origin_server_ts"))
|
||||
.and_then(CanonicalJsonValue::as_integer)
|
||||
.map(Into::into)
|
||||
.and_then(|value: i64| value.try_into().ok())
|
||||
.map(MilliSecondsSinceUnixEpoch)
|
||||
};
|
||||
let custom_origin_server_ts = body
|
||||
.json_body
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.get("origin_server_ts")
|
||||
.and_then(CanonicalJsonValue::as_integer)
|
||||
.map(Into::into)
|
||||
.and_then(|value: i64| value.try_into().ok())
|
||||
.map(MilliSecondsSinceUnixEpoch);
|
||||
|
||||
let create_event_id = services
|
||||
.rooms
|
||||
@@ -299,13 +281,6 @@ pub(crate) async fn create_room_route(
|
||||
};
|
||||
drop(state_lock);
|
||||
debug!("Room created with ID {room_id}");
|
||||
if let Some(expected_room_id) = expect_room_id
|
||||
&& expected_room_id != room_id
|
||||
{
|
||||
return Err!(BadServerResponse(
|
||||
"Room's final room ID was {room_id}, but expected {expected_room_id}"
|
||||
));
|
||||
}
|
||||
let state_lock = services.rooms.state.mutex.lock(room_id.as_str()).await;
|
||||
|
||||
// 2. Let the room creator join
|
||||
@@ -478,32 +453,7 @@ pub(crate) async fn create_room_route(
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
// 6. Initial state events provided by the homeserver
|
||||
|
||||
let mut server_initial_state: Vec<PartialPdu> = Vec::new();
|
||||
|
||||
if let Some(allow_list) = services.server.config.default_room_acl_allow.clone() {
|
||||
server_initial_state.push(PartialPdu::state(
|
||||
String::new(),
|
||||
&RoomServerAclEventContent::new(true, allow_list, vec![]),
|
||||
));
|
||||
} else if let Some(deny_list) = services.server.config.default_room_acl_deny.clone() {
|
||||
server_initial_state.push(PartialPdu::state(
|
||||
String::new(),
|
||||
&RoomServerAclEventContent::new(true, vec!["*".to_owned()], deny_list),
|
||||
));
|
||||
}
|
||||
|
||||
for pdu in server_initial_state {
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(pdu, sender_user, Some(&room_id), &state_lock)
|
||||
.boxed()
|
||||
.await?;
|
||||
}
|
||||
|
||||
// 7. Events listed in initial_state
|
||||
// 6. Events listed in initial_state
|
||||
for event in &body.initial_state {
|
||||
let mut partial_pdu = event
|
||||
.deserialize_as_unchecked::<PartialPdu>()
|
||||
@@ -531,7 +481,7 @@ pub(crate) async fn create_room_route(
|
||||
.await?;
|
||||
}
|
||||
|
||||
// 8. Events implied by name and topic
|
||||
// 7. Events implied by name and topic
|
||||
if let Some(name) = &body.name {
|
||||
services
|
||||
.rooms
|
||||
@@ -560,7 +510,7 @@ pub(crate) async fn create_room_route(
|
||||
.await?;
|
||||
}
|
||||
|
||||
// 9. Events implied by invite (and TODO: invite_3pid)
|
||||
// 8. Events implied by invite (and TODO: invite_3pid)
|
||||
drop(state_lock);
|
||||
for recipient_user in &invitees {
|
||||
if let Err(e) =
|
||||
@@ -586,7 +536,10 @@ pub(crate) async fn create_room_route(
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_text(&format!("{sender_user} made {room_id} public to the room directory"))
|
||||
.send_text(&format!(
|
||||
"{sender_user} made {} public to the room directory",
|
||||
&room_id
|
||||
))
|
||||
.await;
|
||||
}
|
||||
info!("{sender_user} made {0} public to the room directory", &room_id);
|
||||
|
||||
+272
-420
@@ -2,267 +2,47 @@ use std::cmp::max;
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{
|
||||
Err, Error, Event, Result, debug,
|
||||
debug::DebugInspect,
|
||||
err, error,
|
||||
info::room_version::UNSTABLE_ROOM_VERSIONS,
|
||||
Err, Error, Event, Result, debug, err, info,
|
||||
matrix::{StateKey, pdu::PartialPdu},
|
||||
};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ruma::{
|
||||
OwnedEventId, OwnedRoomId, RoomId, UserId,
|
||||
CanonicalJsonObject, RoomId, RoomVersionId,
|
||||
api::{client::room::upgrade_room, error::ErrorKind},
|
||||
assign,
|
||||
events::{
|
||||
StateEventType,
|
||||
StateEventType, TimelineEventType,
|
||||
room::{
|
||||
create::{PreviousRoom, RoomCreateEventContent},
|
||||
create::PreviousRoom,
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
power_levels::RoomPowerLevelsEventContent,
|
||||
tombstone::RoomTombstoneEventContent,
|
||||
},
|
||||
space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
|
||||
space::child::{RedactedSpaceChildEventContent, SpaceChildEventContent},
|
||||
},
|
||||
int,
|
||||
room_version_rules::RoomIdFormatVersion,
|
||||
};
|
||||
use serde_json::value::to_raw_value;
|
||||
use serde_json::{json, value::to_raw_value};
|
||||
|
||||
use crate::router::Ruma;
|
||||
|
||||
/// Recommended transferable state events list from the spec
|
||||
const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 11] = &[
|
||||
StateEventType::RoomServerAcl,
|
||||
StateEventType::RoomEncryption,
|
||||
StateEventType::RoomName,
|
||||
StateEventType::RoomAvatar,
|
||||
StateEventType::RoomTopic,
|
||||
StateEventType::RoomEncryption,
|
||||
StateEventType::RoomGuestAccess,
|
||||
StateEventType::RoomHistoryVisibility,
|
||||
StateEventType::RoomJoinRules,
|
||||
StateEventType::RoomName,
|
||||
StateEventType::RoomPowerLevels,
|
||||
StateEventType::SpaceParent,
|
||||
StateEventType::RoomServerAcl,
|
||||
StateEventType::RoomTopic,
|
||||
// Not explicitly recommended in spec, but very useful.
|
||||
StateEventType::SpaceChild,
|
||||
StateEventType::SpaceParent, // TODO: m.room.policy?
|
||||
];
|
||||
|
||||
/// Updates spaces that are marked as parents of old_room_id to instead point to
|
||||
/// the new room ID.
|
||||
///
|
||||
/// See: https://github.com/matrix-org/matrix-spec-proposals/pull/4168
|
||||
async fn update_parents(
|
||||
services: &crate::State,
|
||||
sender: &UserId,
|
||||
old_room_id: &RoomId,
|
||||
new_room_id: &RoomId,
|
||||
) -> Result {
|
||||
// Fetch the spaces which this room claims are its parents.
|
||||
|
||||
// In rooms that reference the old room via m.space.child events...
|
||||
let parents = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_keys(old_room_id, &StateEventType::SpaceParent)
|
||||
.await
|
||||
.debug_inspect(|k| debug!(?old_room_id, "Parents: {k:?}"))?;
|
||||
|
||||
for raw_parent_id in parents {
|
||||
let parent_id = RoomId::parse(&raw_parent_id)?;
|
||||
if !services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(sender, &parent_id)
|
||||
.await
|
||||
{
|
||||
debug!(%parent_id, "Skipping space as sender is not joined");
|
||||
continue; // Skip updating rooms the sender isn't in.
|
||||
}
|
||||
let state_lock = services.rooms.state.mutex.lock(parent_id.as_str()).await;
|
||||
// We're now fetching state from the *space* that has the old room as a *child*.
|
||||
// Follow along. This will be on the test.
|
||||
let Ok(child) = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get_content::<SpaceChildEventContent>(
|
||||
&parent_id,
|
||||
&StateEventType::SpaceChild,
|
||||
old_room_id.as_str(),
|
||||
)
|
||||
.await
|
||||
.debug_inspect_err(|e| {
|
||||
error!(
|
||||
?parent_id,
|
||||
old_room_id=?old_room_id,
|
||||
new_room_id=?new_room_id,
|
||||
%e,
|
||||
"failed to fetch m.space.child from parent"
|
||||
);
|
||||
})
|
||||
else {
|
||||
// If the space does not have a child event for this room, we can skip it
|
||||
continue;
|
||||
};
|
||||
|
||||
// ...the upgrading server SHOULD send a new m.space.child event with state_key
|
||||
// set to the new room's ID, copying the order and suggested fields from the
|
||||
// content of the m.space.child with state_key of the previous room ID.
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PartialPdu::state(
|
||||
new_room_id.as_str(),
|
||||
&assign!(
|
||||
SpaceChildEventContent::new(vec![sender.server_name().to_owned()]),
|
||||
{
|
||||
order: child.order,
|
||||
suggested: child.suggested,
|
||||
}
|
||||
),
|
||||
),
|
||||
sender,
|
||||
Some(&parent_id),
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await
|
||||
.debug_inspect_err(|e| {
|
||||
error!(
|
||||
?parent_id,
|
||||
old_room_id=?old_room_id,
|
||||
new_room_id=?new_room_id,
|
||||
%e,
|
||||
"failed to send m.space.child to parent during room upgrade"
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
drop(state_lock);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// If the room being upgraded is a space, replace all m.space.parent references
|
||||
/// in its children to point at the newly upgraded room ID, so that they point
|
||||
/// at the new space.
|
||||
///
|
||||
/// See: https://github.com/matrix-org/matrix-spec-proposals/pull/4168
|
||||
async fn update_children(
|
||||
services: &crate::State,
|
||||
sender: &UserId,
|
||||
old_room_id: &RoomId,
|
||||
new_room_id: &RoomId,
|
||||
) -> Result {
|
||||
// Fetch the children of this space.
|
||||
// Note that this might not actually be a space, but just a room that has
|
||||
// children.
|
||||
|
||||
// In rooms that reference the old room via m.space.parent events...
|
||||
// NOTE: Doing that would be expensive. We'll instead fetch rooms which the
|
||||
// space claims are children.
|
||||
let parents = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_keys(old_room_id, &StateEventType::SpaceChild)
|
||||
.await
|
||||
.debug_inspect(|k| debug!(?old_room_id, "Children: {k:?}"))?;
|
||||
|
||||
for raw_child_id in parents {
|
||||
let child_id = RoomId::parse(&raw_child_id)?;
|
||||
if !services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(sender, &child_id)
|
||||
.await
|
||||
{
|
||||
debug!(%child_id, "Skipping child room as sender is not joined");
|
||||
continue;
|
||||
}
|
||||
let state_lock = services.rooms.state.mutex.lock(child_id.as_str()).await;
|
||||
// We're now fetching state from the *child* that has the old space as a
|
||||
// *parent*. Follow along. This will also be on the test.
|
||||
let Ok(ref parent) = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get_content::<SpaceParentEventContent>(
|
||||
&child_id,
|
||||
&StateEventType::SpaceParent,
|
||||
old_room_id.as_str(),
|
||||
)
|
||||
.await
|
||||
.debug_inspect_err(|e| {
|
||||
error!(
|
||||
?child_id,
|
||||
old_room_id=?old_room_id,
|
||||
new_room_id=?new_room_id,
|
||||
%e,
|
||||
"failed to fetch m.space.parent from child"
|
||||
);
|
||||
})
|
||||
else {
|
||||
// If the child does not have a parent event for this room, we can skip it.
|
||||
continue;
|
||||
};
|
||||
|
||||
// ... the upgrading server SHOULD send a new m.space.parent event with
|
||||
// state_key set to the new room's ID.
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PartialPdu::state(
|
||||
new_room_id.as_str(),
|
||||
&assign!(SpaceParentEventContent::new(vec![sender.server_name().to_owned()]), { canonical: parent.canonical }),
|
||||
),
|
||||
sender,
|
||||
Some(&child_id),
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await
|
||||
.debug_inspect_err(|e| error!(
|
||||
child_id=?child_id,
|
||||
old_room_id=?old_room_id,
|
||||
new_room_id=?new_room_id,
|
||||
%e,
|
||||
"failed to send updated m.space.parent to child during room upgrade"
|
||||
))
|
||||
.ok();
|
||||
|
||||
// If the previous m.space.parent event has canonical set to true in content,
|
||||
// homeservers SHOULD update the old state event to set canonical to false,
|
||||
// while setting it to true in the newly-sent m.space.parent event.
|
||||
if parent.canonical {
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PartialPdu::state(
|
||||
old_room_id.as_str(),
|
||||
&assign!(parent.clone(), {canonical: false}),
|
||||
),
|
||||
sender,
|
||||
Some(&child_id),
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await
|
||||
.debug_inspect_err(|e| {
|
||||
error!(
|
||||
child_id=?child_id,
|
||||
old_room_id=?old_room_id,
|
||||
new_room_id=?new_room_id,
|
||||
%e,
|
||||
"failed to send non-canonical m.space.parent to child room"
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
drop(state_lock);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/r0/rooms/{roomId}/upgrade`
|
||||
///
|
||||
/// Upgrades the room.
|
||||
@@ -277,14 +57,10 @@ pub(crate) async fn upgrade_room_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<upgrade_room::v3::Request>,
|
||||
) -> Result<upgrade_room::v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
// TODO[v12]: Handle additional creators
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let (supported, forbid_unstable, is_unstable) = (
|
||||
services.server.supported_room_version(&body.new_version),
|
||||
!services.config.allow_unstable_room_versions,
|
||||
UNSTABLE_ROOM_VERSIONS.contains(&body.new_version),
|
||||
);
|
||||
if !supported || (forbid_unstable && is_unstable) {
|
||||
if !services.server.supported_room_version(&body.new_version) {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::UnsupportedRoomVersion,
|
||||
"This server does not support that room version.",
|
||||
@@ -301,15 +77,17 @@ pub(crate) async fn upgrade_room_route(
|
||||
return Err!(Request(Forbidden("Upgrading the admin room this way is not allowed.")));
|
||||
}
|
||||
|
||||
// 1. Check that the user has permission to send m.room.tombstone events in the
|
||||
// room.
|
||||
// First, check if the user has permission to upgrade the room (send tombstone
|
||||
// event)
|
||||
let old_room_state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await;
|
||||
|
||||
// Check tombstone permission by attempting to create (but not send) the event.
|
||||
services
|
||||
// Check tombstone permission by attempting to create (but not send) the event
|
||||
// Note that this does internally call the policy server with a fake room ID,
|
||||
// which may not be good?
|
||||
let tombstone_test_result = services
|
||||
.rooms
|
||||
.timeline
|
||||
.create_event(
|
||||
.create_hash_and_sign_event(
|
||||
PartialPdu::state(
|
||||
StateKey::new(),
|
||||
&RoomTombstoneEventContent::new(
|
||||
@@ -321,104 +99,157 @@ pub(crate) async fn upgrade_room_route(
|
||||
Some(&body.room_id),
|
||||
&old_room_state_lock,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| {
|
||||
err!(Request(Forbidden("You do not have permission to upgrade this room.")))
|
||||
})?;
|
||||
.await;
|
||||
|
||||
if let Err(_e) = tombstone_test_result {
|
||||
return Err!(Request(Forbidden("User does not have permission to upgrade this room.")));
|
||||
}
|
||||
|
||||
drop(old_room_state_lock);
|
||||
|
||||
// Create a replacement room
|
||||
let new_version_rules = body
|
||||
let room_version_rules = body
|
||||
.new_version
|
||||
.rules()
|
||||
.expect("new room version should have defined rules");
|
||||
|
||||
let last_event = if new_version_rules
|
||||
.authorization
|
||||
.room_create_event_id_as_room_id
|
||||
{
|
||||
None
|
||||
let replacement_room_owned = if room_version_rules.room_id_format == RoomIdFormatVersion::V2 {
|
||||
Some(RoomId::new_v1(services.globals.server_name()))
|
||||
} else {
|
||||
Some(
|
||||
services
|
||||
.rooms
|
||||
.state
|
||||
.get_forward_extremities(&body.room_id)
|
||||
.collect::<Vec<OwnedEventId>>()
|
||||
.await[0]
|
||||
.clone(),
|
||||
)
|
||||
None
|
||||
};
|
||||
let old_create_event: RoomCreateEventContent = services
|
||||
let replacement_room: Option<&RoomId> = replacement_room_owned.as_ref().map(AsRef::as_ref);
|
||||
let replacement_room_tmp = match replacement_room {
|
||||
| Some(v) => v,
|
||||
| None => &RoomId::new_v1(services.globals.server_name()),
|
||||
};
|
||||
|
||||
let _short_id = services
|
||||
.rooms
|
||||
.short
|
||||
.get_or_create_shortroomid(replacement_room_tmp)
|
||||
.await;
|
||||
|
||||
// For pre-v12 rooms, send tombstone before creating replacement room
|
||||
let tombstone_event_id = if room_version_rules.room_id_format != RoomIdFormatVersion::V2 {
|
||||
let state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await;
|
||||
// Send a m.room.tombstone event to the old room to indicate that it is not
|
||||
// intended to be used any further
|
||||
let tombstone_event_id = services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PartialPdu::state(
|
||||
StateKey::new(),
|
||||
&RoomTombstoneEventContent::new(
|
||||
"This room has been replaced".to_owned(),
|
||||
replacement_room.unwrap().to_owned(),
|
||||
),
|
||||
),
|
||||
sender_user,
|
||||
Some(&body.room_id),
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
// Change lock to replacement room
|
||||
drop(state_lock);
|
||||
Some(tombstone_event_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let state_lock = services
|
||||
.rooms
|
||||
.state
|
||||
.mutex
|
||||
.lock(replacement_room_tmp.as_str())
|
||||
.await;
|
||||
|
||||
// Get the old room creation event
|
||||
let mut create_event_content: CanonicalJsonObject = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get_content(&body.room_id, &StateEventType::RoomCreate, "")
|
||||
.await
|
||||
.map_err(|_| err!(Database("Found room without m.room.create event.")))?;
|
||||
let create_event_content = if new_version_rules.authorization.use_room_create_sender {
|
||||
RoomCreateEventContent::new_v1(sender_user.to_owned())
|
||||
} else {
|
||||
RoomCreateEventContent::new_v11()
|
||||
};
|
||||
#[allow(deprecated)]
|
||||
let create_event_content = {
|
||||
assign!(
|
||||
create_event_content,
|
||||
{
|
||||
additional_creators: if new_version_rules.authorization.additional_room_creators {
|
||||
body.additional_creators.clone()
|
||||
} else { Vec::new() },
|
||||
creator: if new_version_rules.authorization.use_room_create_sender {
|
||||
None
|
||||
} else { Some(sender_user.to_owned()) },
|
||||
predecessor: Some(assign!(PreviousRoom::new(body.room_id.clone()), {
|
||||
event_id: last_event,
|
||||
})),
|
||||
room_type: old_create_event.room_type.clone(),
|
||||
room_version: body.new_version.clone(),
|
||||
}
|
||||
)
|
||||
|
||||
// Use the m.room.tombstone event as the predecessor
|
||||
|
||||
let predecessor = {
|
||||
#[allow(deprecated, reason = "Clients still use event_id even though it's deprecated")]
|
||||
Some(assign!(PreviousRoom::new(body.room_id.clone()), {
|
||||
event_id: tombstone_event_id,
|
||||
}))
|
||||
};
|
||||
|
||||
let replacement_room_id: Option<OwnedRoomId> =
|
||||
if new_version_rules.room_id_format == RoomIdFormatVersion::V2 {
|
||||
None
|
||||
} else {
|
||||
Some(RoomId::new_v1(services.globals.server_name()))
|
||||
};
|
||||
// Send a m.room.create event containing a predecessor field and the applicable
|
||||
// room_version
|
||||
{
|
||||
use RoomVersionId::*;
|
||||
match body.new_version {
|
||||
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
|
||||
create_event_content.insert(
|
||||
"creator".into(),
|
||||
json!(&sender_user).try_into().map_err(|e| {
|
||||
info!("Error forming creation event: {e}");
|
||||
Error::BadRequest(ErrorKind::BadJson, "Error forming creation event")
|
||||
})?,
|
||||
);
|
||||
},
|
||||
| _ => {
|
||||
// "creator" key no longer exists in V11 rooms
|
||||
create_event_content.remove("creator");
|
||||
},
|
||||
// TODO(hydra): additional_creators
|
||||
}
|
||||
}
|
||||
|
||||
create_event_content.insert(
|
||||
"room_version".into(),
|
||||
json!(&body.new_version)
|
||||
.try_into()
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?,
|
||||
);
|
||||
create_event_content.insert(
|
||||
"predecessor".into(),
|
||||
json!(predecessor)
|
||||
.try_into()
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?,
|
||||
);
|
||||
|
||||
// Validate creation event content
|
||||
if serde_json::from_str::<CanonicalJsonObject>(
|
||||
to_raw_value(&create_event_content)
|
||||
.expect("Error forming creation event")
|
||||
.get(),
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
return Err(Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"));
|
||||
}
|
||||
|
||||
let new_room_state_lock = if let Some(new_room_id) = replacement_room_id.as_ref() {
|
||||
services.rooms.state.mutex.lock(new_room_id.as_str()).await
|
||||
} else {
|
||||
// NOTE: Using a hardcoded room ID for the temporary mutex means only one room
|
||||
// can be created at a time. This is actually beneficial, as it reduces the
|
||||
// risk of concurrent in-flight collisions.
|
||||
services.rooms.state.mutex.lock("!new-room").await
|
||||
};
|
||||
debug!("Upgrading {} to room version {}", &body.room_id, &body.new_version);
|
||||
let create_event_id = services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PartialPdu::state(StateKey::new(), &create_event_content),
|
||||
PartialPdu {
|
||||
event_type: TimelineEventType::RoomCreate,
|
||||
content: to_raw_value(&create_event_content)
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(StateKey::new()),
|
||||
redacts: None,
|
||||
timestamp: None,
|
||||
},
|
||||
sender_user,
|
||||
replacement_room_id.as_deref(),
|
||||
&new_room_state_lock,
|
||||
replacement_room,
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
drop(new_room_state_lock);
|
||||
// re-acquire a new lock with the new room ID.
|
||||
// We don't actually need a state lock for sending the m.room.create event, but
|
||||
// we get one anyway because the function requires it and I can't be bothered
|
||||
// refactoring it.
|
||||
let (replacement_room_id, new_room_state_lock) =
|
||||
if new_version_rules.room_id_format == RoomIdFormatVersion::V2 {
|
||||
let parsed_room_id = RoomId::new_v2(
|
||||
create_event_id
|
||||
.as_str()
|
||||
.strip_prefix("$")
|
||||
.expect("event ID must start with $ sigil"),
|
||||
)?;
|
||||
let create_id = create_event_id.as_str().replace('$', "!");
|
||||
let (replacement_room, state_lock) =
|
||||
if room_version_rules.room_id_format == RoomIdFormatVersion::V2 {
|
||||
let parsed_room_id = RoomId::parse(&create_id)?;
|
||||
let lock = services
|
||||
.rooms
|
||||
.state
|
||||
@@ -427,13 +258,9 @@ pub(crate) async fn upgrade_room_route(
|
||||
.await;
|
||||
(Some(parsed_room_id), lock)
|
||||
} else {
|
||||
let new_room_id =
|
||||
replacement_room_id.expect("replacement room id should be known by now");
|
||||
let lock = services.rooms.state.mutex.lock(new_room_id.as_str()).await;
|
||||
(Some(new_room_id), lock)
|
||||
(replacement_room.map(ToOwned::to_owned), state_lock)
|
||||
};
|
||||
|
||||
debug!("Upgraded {} to {}", &body.room_id, replacement_room_id.as_deref().unwrap());
|
||||
// Join the new room
|
||||
services
|
||||
.rooms
|
||||
@@ -447,13 +274,13 @@ pub(crate) async fn upgrade_room_route(
|
||||
}),
|
||||
),
|
||||
sender_user,
|
||||
replacement_room_id.as_deref(),
|
||||
&new_room_state_lock,
|
||||
replacement_room.as_deref(),
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
// 3. Replicate transferable state events to the new room
|
||||
// Replicate transferable state events to the new room
|
||||
for event_type in TRANSFERABLE_STATE_EVENTS {
|
||||
let state_keys = services
|
||||
.rooms
|
||||
@@ -470,45 +297,26 @@ pub(crate) async fn upgrade_room_route(
|
||||
| Ok(v) => v.content().to_owned(),
|
||||
| Err(_) => continue, // Skipping missing events.
|
||||
};
|
||||
if event_content.get() == "{}" {
|
||||
// If the event content is empty, we skip it
|
||||
continue;
|
||||
}
|
||||
// If this is a power levels event, and the new room version has creators,
|
||||
// we need to make sure they dont appear in the users block of power levels.
|
||||
if *event_type == StateEventType::RoomPowerLevels {
|
||||
let creators = body
|
||||
.additional_creators
|
||||
.clone()
|
||||
.iter()
|
||||
.chain(std::iter::once(&sender_user.to_owned()))
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<_>>();
|
||||
// TODO(v12): additional creators
|
||||
let creators = vec![sender_user];
|
||||
let mut power_levels_event_content: RoomPowerLevelsEventContent =
|
||||
serde_json::from_str(event_content.get()).map_err(|_| {
|
||||
err!(Request(BadJson("Power levels event content is not valid")))
|
||||
})?;
|
||||
for creator in creators {
|
||||
if new_version_rules
|
||||
.authorization
|
||||
.explicitly_privilege_room_creators
|
||||
{
|
||||
power_levels_event_content.users.remove(&creator);
|
||||
} else {
|
||||
power_levels_event_content.users.insert(
|
||||
creator.clone(),
|
||||
max(
|
||||
int!(100),
|
||||
power_levels_event_content
|
||||
.users
|
||||
.get(&creator)
|
||||
.copied()
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
);
|
||||
}
|
||||
power_levels_event_content.users.remove(creator);
|
||||
}
|
||||
event_content = to_raw_value(&power_levels_event_content)
|
||||
.expect("event is valid, we just deserialized and modified it");
|
||||
}
|
||||
|
||||
debug!(%event_type, ?state_key, "Transferring state event to new room");
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
@@ -520,15 +328,15 @@ pub(crate) async fn upgrade_room_route(
|
||||
..Default::default()
|
||||
},
|
||||
sender_user,
|
||||
replacement_room_id.as_deref(),
|
||||
&new_room_state_lock,
|
||||
replacement_room.as_deref(),
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Move any local aliases to the new room
|
||||
// Moves any local aliases to the new room
|
||||
let mut local_aliases = services
|
||||
.rooms
|
||||
.alias
|
||||
@@ -536,7 +344,6 @@ pub(crate) async fn upgrade_room_route(
|
||||
.boxed();
|
||||
|
||||
while let Some(alias) = local_aliases.next().await {
|
||||
debug!(?alias, "Migrating alias");
|
||||
services
|
||||
.rooms
|
||||
.alias
|
||||
@@ -545,31 +352,11 @@ pub(crate) async fn upgrade_room_route(
|
||||
|
||||
services.rooms.alias.set_alias(
|
||||
&alias,
|
||||
replacement_room_id.as_deref().unwrap(),
|
||||
replacement_room.as_ref().unwrap(),
|
||||
sender_user,
|
||||
)?;
|
||||
}
|
||||
|
||||
// 5. Send a `m.room.tombstone` event to the old room to indicate that it is not
|
||||
// intended to be used any further.
|
||||
debug!(target=?body.room_id, "Sending tombstone to old room");
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PartialPdu::state(
|
||||
StateKey::new(),
|
||||
&RoomTombstoneEventContent::new(
|
||||
"This room has been replaced".to_owned(),
|
||||
replacement_room_id.clone().unwrap(),
|
||||
),
|
||||
),
|
||||
sender_user,
|
||||
Some(&body.room_id),
|
||||
&old_room_state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Get the old room power levels
|
||||
let mut power_levels = services
|
||||
.rooms
|
||||
@@ -591,10 +378,8 @@ pub(crate) async fn upgrade_room_route(
|
||||
power_levels.events_default = new_level;
|
||||
power_levels.invite = new_level;
|
||||
|
||||
// 6. Modify the power levels in the old room to prevent sending of events and
|
||||
// Modify the power levels in the old room to prevent sending of events and
|
||||
// inviting new users
|
||||
// Spec dictates that this is allowed to fail.
|
||||
debug!(target=?body.room_id, ?new_level, "Raising power level in old room to lock it");
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
@@ -605,50 +390,117 @@ pub(crate) async fn upgrade_room_route(
|
||||
),
|
||||
sender_user,
|
||||
Some(&body.room_id),
|
||||
&old_room_state_lock,
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await
|
||||
.ok();
|
||||
.await?;
|
||||
|
||||
// MSC4168: Update spaces that reference this room to point at the new room.
|
||||
debug!("Updating parent spaces");
|
||||
update_parents(
|
||||
&services,
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
replacement_room_id.as_deref().unwrap(),
|
||||
)
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
error!(
|
||||
old_room_id=?body.room_id,
|
||||
new_room_id=?replacement_room_id.as_deref().unwrap(),
|
||||
%e,
|
||||
"failed to update parent spaces during room upgrade"
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
drop(state_lock);
|
||||
|
||||
// MSC4168: Update child rooms to point at the new space, where possible
|
||||
debug!("Updating space children");
|
||||
update_children(
|
||||
&services,
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
replacement_room_id.as_deref().unwrap(),
|
||||
)
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
error!(
|
||||
old_room_id=?body.room_id,
|
||||
new_room_id=?replacement_room_id.as_deref().unwrap(),
|
||||
%e,
|
||||
"failed to update space children during room upgrade"
|
||||
// For v12 rooms, send tombstone AFTER creating replacement room
|
||||
if room_version_rules.room_id_format == RoomIdFormatVersion::V2 {
|
||||
let old_room_state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await;
|
||||
// For v12 rooms, no event reference in predecessor due to cyclic dependency -
|
||||
// could best effort one maybe?
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PartialPdu::state(
|
||||
StateKey::new(),
|
||||
&RoomTombstoneEventContent::new(
|
||||
"This room has been replaced".to_owned(),
|
||||
replacement_room.as_ref().unwrap().to_owned(),
|
||||
),
|
||||
),
|
||||
sender_user,
|
||||
Some(&body.room_id),
|
||||
&old_room_state_lock,
|
||||
)
|
||||
.await?;
|
||||
drop(old_room_state_lock);
|
||||
}
|
||||
|
||||
// Check if the old room has a space parent, and if so, whether we should update
|
||||
// it (m.space.parent, room_id)
|
||||
let parents = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_keys(&body.room_id, &StateEventType::SpaceParent)
|
||||
.await?;
|
||||
|
||||
for raw_space_id in parents {
|
||||
let space_id = RoomId::parse(&raw_space_id)?;
|
||||
let Ok(child) = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get_content::<SpaceChildEventContent>(
|
||||
&space_id,
|
||||
&StateEventType::SpaceChild,
|
||||
body.room_id.as_str(),
|
||||
)
|
||||
.await
|
||||
else {
|
||||
// If the space does not have a child event for this room, we can skip it
|
||||
continue;
|
||||
};
|
||||
debug!(
|
||||
"Updating space {space_id} child event for room {} to {}",
|
||||
&body.room_id,
|
||||
replacement_room.as_ref().unwrap()
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
// First, drop the space's child event
|
||||
let state_lock = services.rooms.state.mutex.lock(space_id.as_str()).await;
|
||||
debug!("Removing space child event for room {} in space {space_id}", &body.room_id);
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PartialPdu {
|
||||
event_type: StateEventType::SpaceChild.into(),
|
||||
content: to_raw_value(&RedactedSpaceChildEventContent::new())
|
||||
.expect("event is valid, we just created it"),
|
||||
state_key: Some(body.room_id.clone().as_str().into()),
|
||||
..Default::default()
|
||||
},
|
||||
sender_user,
|
||||
Some(&space_id),
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await
|
||||
.ok();
|
||||
// Now, add a new child event for the replacement room
|
||||
debug!(
|
||||
"Adding space child event for room {} in space {space_id}",
|
||||
replacement_room.as_ref().unwrap()
|
||||
);
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PartialPdu::state(
|
||||
replacement_room.as_ref().unwrap().as_str(),
|
||||
&assign!(SpaceChildEventContent::new(vec![sender_user.server_name().to_owned()]), {
|
||||
order: child.order,
|
||||
suggested: child.suggested,
|
||||
}),
|
||||
),
|
||||
sender_user,
|
||||
Some(&space_id),
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await
|
||||
.ok();
|
||||
debug!(
|
||||
"Finished updating space {space_id} child event for room {} to {}",
|
||||
&body.room_id,
|
||||
replacement_room.as_ref().unwrap()
|
||||
);
|
||||
drop(state_lock);
|
||||
}
|
||||
|
||||
// Return the replacement room id
|
||||
Ok(upgrade_room::v3::Response::new(replacement_room_id.unwrap()))
|
||||
Ok(upgrade_room::v3::Response::new(replacement_room.as_ref().unwrap().to_owned()))
|
||||
}
|
||||
|
||||
+20
-31
@@ -21,7 +21,7 @@ use ruma::{
|
||||
},
|
||||
login::{
|
||||
self,
|
||||
v3::{DiscoveryInfo, HomeserverInfo, LoginInfo},
|
||||
v3::{DiscoveryInfo, HomeserverInfo},
|
||||
},
|
||||
logout, logout_all,
|
||||
},
|
||||
@@ -29,6 +29,7 @@ use ruma::{
|
||||
},
|
||||
assign,
|
||||
};
|
||||
use service::uiaa::Identity;
|
||||
|
||||
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
|
||||
use crate::Ruma;
|
||||
@@ -43,12 +44,6 @@ pub(crate) async fn get_login_types_route(
|
||||
ClientIp(client): ClientIp,
|
||||
_body: Ruma<get_login_types::v3::Request>,
|
||||
) -> Result<get_login_types::v3::Response> {
|
||||
if !services.config.oauth.compatibility_mode.uiaa_available() {
|
||||
return Err!(Request(Unrecognized(
|
||||
"User-interactive authentication is not available on this server."
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(get_login_types::v3::Response::new(vec![
|
||||
get_login_types::v3::LoginType::Password(PasswordLoginType::default()),
|
||||
get_login_types::v3::LoginType::ApplicationService(ApplicationServiceLoginType::default()),
|
||||
@@ -58,7 +53,7 @@ pub(crate) async fn get_login_types_route(
|
||||
]))
|
||||
}
|
||||
|
||||
pub async fn handle_login(
|
||||
pub(crate) async fn handle_login(
|
||||
services: &Services,
|
||||
identifier: Option<&UserIdentifier>,
|
||||
password: &str,
|
||||
@@ -92,6 +87,10 @@ pub async fn handle_login(
|
||||
return Err!(Request(InvalidParam("User ID does not belong to this homeserver")));
|
||||
}
|
||||
|
||||
if services.users.is_locked(&user_id).await? {
|
||||
return Err!(Request(UserLocked("This account has been locked.")));
|
||||
}
|
||||
|
||||
if services.users.is_login_disabled(&user_id).await {
|
||||
warn!(%user_id, "user attempted to log in with a login-disabled account");
|
||||
return Err!(Request(Forbidden("This account is not permitted to log in.")));
|
||||
@@ -120,29 +119,19 @@ pub(crate) async fn login_route(
|
||||
ClientIp(client): ClientIp,
|
||||
body: Ruma<login::v3::Request>,
|
||||
) -> Result<login::v3::Response> {
|
||||
if !services.config.oauth.compatibility_mode.uiaa_available() {
|
||||
return match body.login_info {
|
||||
| LoginInfo::ApplicationService(_) => {
|
||||
Err!(Request(AppserviceLoginUnsupported(
|
||||
"User-interactive appservice login is not available on this server."
|
||||
)))
|
||||
},
|
||||
| _ => {
|
||||
Err!(Request(Unrecognized(
|
||||
"User-interactive authentication is not available on this server."
|
||||
)))
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
let emergency_mode_enabled = services.config.emergency_password.is_some();
|
||||
|
||||
// Validate login method
|
||||
// TODO: Other login methods
|
||||
let user_id = match &body.login_info {
|
||||
#[allow(deprecated)]
|
||||
| LoginInfo::Password(login::v3::Password { identifier, password, user, .. }) =>
|
||||
handle_login(&services, identifier.as_ref(), password, user.as_ref()).await?,
|
||||
| LoginInfo::Token(login::v3::Token { token, .. }) => {
|
||||
| login::v3::LoginInfo::Password(login::v3::Password {
|
||||
identifier,
|
||||
password,
|
||||
user,
|
||||
..
|
||||
}) => handle_login(&services, identifier.as_ref(), password, user.as_ref()).await?,
|
||||
| login::v3::LoginInfo::Token(login::v3::Token { token, .. }) => {
|
||||
debug!("Got token login type");
|
||||
if !services.server.config.login_via_existing_session {
|
||||
return Err!(Request(Unknown("Token login is not enabled.")));
|
||||
@@ -150,7 +139,7 @@ pub(crate) async fn login_route(
|
||||
services.users.find_from_login_token(token).await?
|
||||
},
|
||||
#[allow(deprecated)]
|
||||
| LoginInfo::ApplicationService(login::v3::ApplicationService {
|
||||
| login::v3::LoginInfo::ApplicationService(login::v3::ApplicationService {
|
||||
identifier,
|
||||
user,
|
||||
..
|
||||
@@ -184,6 +173,7 @@ pub(crate) async fn login_route(
|
||||
user_id
|
||||
},
|
||||
| _ => {
|
||||
debug!("/login json_body: {:?}", &body.json_body);
|
||||
return Err!(Request(Unknown(
|
||||
debug_warn!(?body.login_info, "Invalid or unsupported login type")
|
||||
)));
|
||||
@@ -213,7 +203,7 @@ pub(crate) async fn login_route(
|
||||
if device_exists {
|
||||
services
|
||||
.users
|
||||
.set_token(&user_id, &device_id, &token, None)
|
||||
.set_token(&user_id, &device_id, &token)
|
||||
.await?;
|
||||
} else {
|
||||
services
|
||||
@@ -222,7 +212,6 @@ pub(crate) async fn login_route(
|
||||
&user_id,
|
||||
&device_id,
|
||||
&token,
|
||||
None,
|
||||
body.initial_device_display_name.clone(),
|
||||
Some(client.to_string()),
|
||||
)
|
||||
@@ -261,7 +250,7 @@ pub(crate) async fn login_token_route(
|
||||
ClientIp(client): ClientIp,
|
||||
body: Ruma<get_login_token::v1::Request>,
|
||||
) -> Result<get_login_token::v1::Response> {
|
||||
if !services.config.login_via_existing_session {
|
||||
if !services.server.config.login_via_existing_session {
|
||||
return Err!(Request(Forbidden("Login via an existing session is not enabled")));
|
||||
}
|
||||
|
||||
@@ -270,7 +259,7 @@ pub(crate) async fn login_token_route(
|
||||
// Prompt the user to confirm with their password using UIAA
|
||||
let _ = services
|
||||
.uiaa
|
||||
.authenticate_password(&body.auth, sender_user, body.sender_device(), None)
|
||||
.authenticate_password(&body.auth, Some(Identity::from_user_id(sender_user)))
|
||||
.await?;
|
||||
|
||||
let login_token = utils::random_string(TOKEN_LENGTH);
|
||||
|
||||
@@ -48,13 +48,6 @@ async fn load_timeline(
|
||||
ending_count: Option<PduCount>,
|
||||
limit: usize,
|
||||
) -> Result<TimelinePdus> {
|
||||
if let (Some(starting_count), Some(ending_count)) = (starting_count, ending_count) {
|
||||
debug_assert!(
|
||||
starting_count <= ending_count,
|
||||
"starting count {starting_count} > ending count {ending_count}"
|
||||
);
|
||||
}
|
||||
|
||||
let mut pdu_stream = match starting_count {
|
||||
| Some(starting_count) => {
|
||||
let last_timeline_count = services
|
||||
|
||||
@@ -38,7 +38,6 @@ use ruma::{
|
||||
uint,
|
||||
};
|
||||
use service::{account_data::AnyRawAccountDataEvent, rooms::short::ShortStateHash};
|
||||
use tokio::pin;
|
||||
|
||||
use super::{load_timeline, share_encrypted_room};
|
||||
use crate::client::{
|
||||
@@ -97,19 +96,12 @@ pub(super) async fn load_joined_room(
|
||||
);
|
||||
}
|
||||
|
||||
let state_events =
|
||||
StateEvents::with_events(state_events.into_iter().map(Event::into_format).collect());
|
||||
|
||||
let joined_room = assign!(JoinedRoom::new(), {
|
||||
account_data,
|
||||
summary: summary.unwrap_or_default(),
|
||||
unread_notifications: notification_counts.unwrap_or_default(),
|
||||
timeline,
|
||||
state: if sync_context.use_state_after {
|
||||
RoomState::After(state_events)
|
||||
} else {
|
||||
RoomState::Before(state_events)
|
||||
},
|
||||
state: RoomState::Before(StateEvents::with_events(state_events.into_iter().map(Event::into_format).collect())),
|
||||
ephemeral,
|
||||
unread_thread_notifications: BTreeMap::new(),
|
||||
});
|
||||
@@ -352,7 +344,7 @@ struct ShortStateHashes {
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn fetch_shortstatehashes(
|
||||
services: &Services,
|
||||
SyncContext { last_sync_end_count, .. }: SyncContext<'_>,
|
||||
SyncContext { last_sync_end_count, current_count, .. }: SyncContext<'_>,
|
||||
room_id: &RoomId,
|
||||
) -> Result<ShortStateHashes> {
|
||||
// the room state currently.
|
||||
@@ -362,41 +354,46 @@ async fn fetch_shortstatehashes(
|
||||
.rooms
|
||||
.state
|
||||
.get_room_shortstatehash(room_id)
|
||||
.map_err(|_| err!(Database(error!("Room {room_id} has no state"))))
|
||||
.await?;
|
||||
.map_err(|_| err!(Database(error!("Room {room_id} has no state"))));
|
||||
|
||||
// The room state as of the end of the last sync.
|
||||
// This will be None if we are doing an initial sync.
|
||||
// the room state as of the end of the last sync.
|
||||
// this will be None if we are doing an initial sync or if we just joined this
|
||||
// room.
|
||||
let last_sync_end_shortstatehash =
|
||||
OptionFuture::from(last_sync_end_count.map(async |last_sync_end_count| {
|
||||
pin! {
|
||||
let pdus = services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus(room_id, Some(PduCount::Normal(last_sync_end_count)))
|
||||
.ignore_err();
|
||||
}
|
||||
|
||||
match pdus.next().await {
|
||||
| Some((_, pdu_after_last_sync_end)) => {
|
||||
trace!(?pdu_after_last_sync_end.event_id, "pdu at last sync end");
|
||||
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.pdu_shortstatehash(&pdu_after_last_sync_end.event_id)
|
||||
.await
|
||||
.map_err(|err| err!("Last sync end PDU has no shortstatehash: {err}"))
|
||||
},
|
||||
| None => {
|
||||
// No events have been sent since the last sync, or we just joined this room,
|
||||
// so the state then is the same as the state now
|
||||
Ok(current_shortstatehash)
|
||||
},
|
||||
}
|
||||
OptionFuture::from(last_sync_end_count.map(|last_sync_end_count| {
|
||||
// look up the shortstatehash saved by the last sync's call to
|
||||
// `associate_token_shortstatehash`
|
||||
services
|
||||
.rooms
|
||||
.user
|
||||
.get_token_shortstatehash(room_id, last_sync_end_count)
|
||||
.inspect_err(move |_| {
|
||||
debug_warn!(
|
||||
token = last_sync_end_count,
|
||||
"Room has no shortstatehash for this token"
|
||||
);
|
||||
})
|
||||
.ok()
|
||||
}))
|
||||
.await
|
||||
.transpose()?;
|
||||
.map(Option::flatten)
|
||||
.map(Ok);
|
||||
|
||||
let (current_shortstatehash, last_sync_end_shortstatehash) =
|
||||
try_join(current_shortstatehash, last_sync_end_shortstatehash).await?;
|
||||
|
||||
/*
|
||||
associate the `current_count` with the `current_shortstatehash`, so we can
|
||||
use it on the next sync as the `last_sync_end_shortstatehash`.
|
||||
|
||||
TODO: the table written to by this call grows extremely fast, gaining one new entry for each
|
||||
joined room on _every single sync request_. we need to find a better way to remember the shortstatehash
|
||||
between syncs.
|
||||
*/
|
||||
services
|
||||
.rooms
|
||||
.user
|
||||
.associate_token_shortstatehash(room_id, current_count, current_shortstatehash)
|
||||
.await;
|
||||
|
||||
Ok(ShortStateHashes {
|
||||
current_shortstatehash,
|
||||
@@ -455,7 +452,6 @@ async fn build_state_events(
|
||||
syncing_user,
|
||||
last_sync_end_count,
|
||||
full_state,
|
||||
use_state_after,
|
||||
..
|
||||
} = sync_context;
|
||||
|
||||
@@ -464,28 +460,32 @@ async fn build_state_events(
|
||||
last_sync_end_shortstatehash,
|
||||
} = shortstatehashes;
|
||||
|
||||
let timeline_start_shortstatehash = if let Some((count, pdu)) = timeline.pdus.front() {
|
||||
if matches!(count, PduCount::Backfilled(_)) {
|
||||
// We don't have shortstatehashes for backfilled PDUs, the best we can
|
||||
// do is to use the current state
|
||||
current_shortstatehash
|
||||
} else {
|
||||
services
|
||||
// the spec states that the `state` property only includes state events up to
|
||||
// the beginning of the timeline, so we determine the state of the syncing room
|
||||
// as of the first timeline event. NOTE: this explanation is not entirely
|
||||
// accurate; see the implementation of `build_state_incremental`.
|
||||
let timeline_start_shortstatehash = async {
|
||||
if let Some((_, pdu)) = timeline.pdus.front() {
|
||||
if let Ok(shortstatehash) = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.pdu_shortstatehash(&pdu.event_id)
|
||||
.await
|
||||
.map_err(|err| err!("Timeline start has no shortstatehash: {err}"))?
|
||||
{
|
||||
return shortstatehash;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if the timeline is empty there can't possibly be any changes to the state
|
||||
return Ok(vec![]);
|
||||
|
||||
current_shortstatehash
|
||||
};
|
||||
|
||||
// the user IDs of members whose membership needs to be sent to the client, if
|
||||
// lazy-loading is enabled.
|
||||
let lazily_loaded_members =
|
||||
prepare_lazily_loaded_members(services, sync_context, room_id, timeline.senders()).await;
|
||||
prepare_lazily_loaded_members(services, sync_context, room_id, timeline.senders());
|
||||
|
||||
let (timeline_start_shortstatehash, lazily_loaded_members) =
|
||||
join(timeline_start_shortstatehash, lazily_loaded_members).await;
|
||||
|
||||
// compute the state delta between the previous sync and this sync.
|
||||
match (last_sync_end_count, last_sync_end_shortstatehash) {
|
||||
@@ -494,15 +494,16 @@ async fn build_state_events(
|
||||
is Some (meaning the syncing user didn't just join this room for the first time ever), and `full_state` is false,
|
||||
then use `build_state_incremental`.
|
||||
*/
|
||||
| (Some(_), Some(last_sync_end_shortstatehash)) if !full_state =>
|
||||
| (Some(last_sync_end_count), Some(last_sync_end_shortstatehash)) if !full_state =>
|
||||
build_state_incremental(
|
||||
services,
|
||||
syncing_user,
|
||||
room_id,
|
||||
PduCount::Normal(last_sync_end_count),
|
||||
last_sync_end_shortstatehash,
|
||||
timeline_start_shortstatehash,
|
||||
current_shortstatehash,
|
||||
timeline,
|
||||
use_state_after,
|
||||
lazily_loaded_members.as_ref(),
|
||||
)
|
||||
.boxed()
|
||||
@@ -517,8 +518,6 @@ async fn build_state_events(
|
||||
services,
|
||||
syncing_user,
|
||||
timeline_start_shortstatehash,
|
||||
current_shortstatehash,
|
||||
use_state_after,
|
||||
lazily_loaded_members.as_ref(),
|
||||
)
|
||||
.boxed()
|
||||
@@ -599,25 +598,23 @@ async fn check_joined_since_last_sync(
|
||||
ShortStateHashes { last_sync_end_shortstatehash, .. }: ShortStateHashes,
|
||||
SyncContext { syncing_user, .. }: SyncContext<'_>,
|
||||
) -> Result<bool> {
|
||||
let Some(last_sync_end_shortstatehash) = last_sync_end_shortstatehash else {
|
||||
// For initial syncs always return false, since there's no "last sync" for the
|
||||
// user to have joined since.
|
||||
return Ok(false);
|
||||
// fetch the syncing user's membership event during the last sync.
|
||||
// this will be None if `previous_sync_end_shortstatehash` is None.
|
||||
let membership_during_previous_sync = match last_sync_end_shortstatehash {
|
||||
| Some(last_sync_end_shortstatehash) => services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.state_get_content(
|
||||
last_sync_end_shortstatehash,
|
||||
&StateEventType::RoomMember,
|
||||
syncing_user.as_str(),
|
||||
)
|
||||
.await
|
||||
.inspect_err(|_| debug_warn!("User has no previous membership"))
|
||||
.ok(),
|
||||
| None => None,
|
||||
};
|
||||
|
||||
// Fetch the syncing user's membership event during the last sync.
|
||||
let membership_during_previous_sync = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.state_get_content(
|
||||
last_sync_end_shortstatehash,
|
||||
&StateEventType::RoomMember,
|
||||
syncing_user.as_str(),
|
||||
)
|
||||
.await
|
||||
.inspect_err(|_| debug_warn!("User has no previous membership"))
|
||||
.ok();
|
||||
|
||||
// TODO: If the requesting user got state-reset out of the room, this
|
||||
// will be `true` when it shouldn't be. this function should never be called
|
||||
// in that situation, but it may be if the membership cache didn't get updated.
|
||||
|
||||
@@ -181,9 +181,6 @@ pub(super) async fn load_left_room(
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
|
||||
let state_events =
|
||||
StateEvents::with_events(state_events.into_iter().map(Event::into_format).collect());
|
||||
|
||||
Ok(Some(assign!(LeftRoom::new(), {
|
||||
account_data: RoomAccountData::new(),
|
||||
timeline: assign!(Timeline::new(), {
|
||||
@@ -191,11 +188,7 @@ pub(super) async fn load_left_room(
|
||||
prev_batch: Some(current_count.to_string()),
|
||||
events: raw_timeline_pdus,
|
||||
}),
|
||||
state: if sync_context.use_state_after {
|
||||
State::After(state_events)
|
||||
} else {
|
||||
State::Before(state_events)
|
||||
},
|
||||
state: State::Before(StateEvents::with_events(state_events.into_iter().map(Event::into_format).collect())),
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -271,8 +264,6 @@ async fn build_left_state_and_timeline(
|
||||
services,
|
||||
syncing_user,
|
||||
timeline_start_shortstatehash,
|
||||
leave_shortstatehash,
|
||||
sync_context.use_state_after,
|
||||
lazily_loaded_members.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -11,11 +11,12 @@ use std::{
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::ClientIp;
|
||||
use conduwuit::{
|
||||
Err, Result, at, error, extract_variant,
|
||||
Err, Result, at, extract_variant,
|
||||
utils::{
|
||||
ReadyExt, TryFutureExtExt,
|
||||
stream::{BroadbandExt, Tools, WidebandExt},
|
||||
},
|
||||
warn,
|
||||
};
|
||||
use conduwuit_service::Services;
|
||||
use futures::{FutureExt, StreamExt, TryFutureExt, future::OptionFuture};
|
||||
@@ -109,9 +110,6 @@ struct SyncContext<'a> {
|
||||
/// The sync filter, which the client uses to specify what data should be
|
||||
/// included in the sync response.
|
||||
filter: &'a FilterDefinition,
|
||||
/// Whether the state at the end of the timeline should be used when
|
||||
/// calculating state diffs for sync.
|
||||
use_state_after: bool,
|
||||
}
|
||||
|
||||
impl<'a> SyncContext<'a> {
|
||||
@@ -263,7 +261,6 @@ pub(crate) async fn build_sync_events(
|
||||
current_count,
|
||||
full_state,
|
||||
filter: &filter,
|
||||
use_state_after: body.use_state_after,
|
||||
};
|
||||
|
||||
let joined_rooms = services
|
||||
@@ -276,7 +273,7 @@ pub(crate) async fn build_sync_events(
|
||||
match joined_room {
|
||||
| Ok((room, updates)) => Some((room_id, room, updates)),
|
||||
| Err(err) => {
|
||||
error!(?err, %room_id, "error loading joined room");
|
||||
warn!(?err, %room_id, "error loading joined room");
|
||||
None
|
||||
},
|
||||
}
|
||||
@@ -305,7 +302,7 @@ pub(crate) async fn build_sync_events(
|
||||
| Ok(Some(left_room)) => Some((room_id, left_room)),
|
||||
| Ok(None) => None,
|
||||
| Err(err) => {
|
||||
error!(?err, %room_id, "error loading joined room");
|
||||
warn!(?err, %room_id, "error loading joined room");
|
||||
None
|
||||
},
|
||||
}
|
||||
|
||||
+143
-60
@@ -1,8 +1,11 @@
|
||||
use std::collections::HashSet;
|
||||
use std::{collections::BTreeSet, ops::ControlFlow};
|
||||
|
||||
use conduwuit::{
|
||||
Result, at,
|
||||
matrix::{Event, pdu::PduEvent},
|
||||
Result, at, is_equal_to,
|
||||
matrix::{
|
||||
Event,
|
||||
pdu::{PduCount, PduEvent},
|
||||
},
|
||||
utils::{
|
||||
BoolExt, IterStream, ReadyExt, TryFutureExtExt,
|
||||
stream::{BroadbandExt, TryIgnore},
|
||||
@@ -13,7 +16,9 @@ use conduwuit_service::{
|
||||
rooms::{lazy_loading::MemberSet, short::ShortStateHash},
|
||||
};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ruma::{OwnedEventId, UserId, events::StateEventType};
|
||||
use itertools::Itertools;
|
||||
use ruma::{OwnedEventId, RoomId, UserId, events::StateEventType};
|
||||
use service::rooms::short::ShortEventId;
|
||||
use tracing::trace;
|
||||
|
||||
use crate::client::TimelinePdus;
|
||||
@@ -34,19 +39,13 @@ pub(super) async fn build_state_initial(
|
||||
services: &Services,
|
||||
sender_user: &UserId,
|
||||
timeline_start_shortstatehash: ShortStateHash,
|
||||
timeline_end_shortstatehash: ShortStateHash,
|
||||
use_state_after: bool,
|
||||
lazily_loaded_members: Option<&MemberSet>,
|
||||
) -> Result<Vec<PduEvent>> {
|
||||
// load the keys and event IDs of the state events at the start of the timeline
|
||||
let (shortstatekeys, event_ids): (Vec<_>, Vec<_>) = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.state_full_ids(if use_state_after {
|
||||
timeline_end_shortstatehash
|
||||
} else {
|
||||
timeline_start_shortstatehash
|
||||
})
|
||||
.state_full_ids(timeline_start_shortstatehash)
|
||||
.unzip()
|
||||
.await;
|
||||
|
||||
@@ -93,34 +92,82 @@ pub(super) async fn build_state_initial(
|
||||
pub(super) async fn build_state_incremental<'a>(
|
||||
services: &Services,
|
||||
sender_user: &'a UserId,
|
||||
room_id: &RoomId,
|
||||
last_sync_end_count: PduCount,
|
||||
last_sync_end_shortstatehash: ShortStateHash,
|
||||
timeline_start_shortstatehash: ShortStateHash,
|
||||
timeline_end_shortstatehash: ShortStateHash,
|
||||
timeline: &TimelinePdus,
|
||||
use_state_after: bool,
|
||||
lazily_loaded_members: Option<&'a MemberSet>,
|
||||
) -> Result<Vec<PduEvent>> {
|
||||
let mut state_event_ids: HashSet<OwnedEventId> = HashSet::new();
|
||||
/*
|
||||
NB: a limited sync is one where `timeline.limited == true`. Synapse calls this a "gappy" sync internally.
|
||||
|
||||
trace!(
|
||||
%use_state_after,
|
||||
%last_sync_end_shortstatehash,
|
||||
%timeline_start_shortstatehash,
|
||||
%timeline_end_shortstatehash,
|
||||
"computing state for incremental sync"
|
||||
);
|
||||
The algorithm implemented in this function is, currently, quite different from the algorithm vaguely described
|
||||
by the Matrix specification. This is because the specification's description of the `state` property does not accurately
|
||||
reflect how Synapse behaves, and therefore how client SDKs behave. Notable differences include:
|
||||
1. We do not compute the delta using the naive approach of "every state event from the end of the last sync
|
||||
up to the start of this sync's timeline". see below for details.
|
||||
2. If lazy-loading is enabled, we include lazily-loaded membership events. The specific users to include are determined
|
||||
elsewhere and supplied to this function in the `lazily_loaded_members` parameter.
|
||||
*/
|
||||
|
||||
// Fetch lazy-loaded membership events if lazy-loading is enabled
|
||||
if let Some(lazily_loaded_members) = lazily_loaded_members
|
||||
&& !lazily_loaded_members.is_empty()
|
||||
{
|
||||
trace!("including lazy membership events for members: {:?}", lazily_loaded_members);
|
||||
/*
|
||||
the `state` property of an incremental sync which isn't limited are _usually_ empty.
|
||||
(note: the specification says that the `state` property is _always_ empty for limited syncs, which is incorrect.)
|
||||
however, if an event in the timeline (`timeline.pdus`) merges a split in the room's DAG (i.e. has multiple `prev_events`),
|
||||
the state at the _end_ of the timeline may include state events which were merged in and don't exist in the state
|
||||
at the _start_ of the timeline. because this is uncommon, we check here to see if any events in the timeline
|
||||
merged a split in the DAG.
|
||||
|
||||
services
|
||||
see: https://github.com/element-hq/synapse/issues/16941
|
||||
*/
|
||||
|
||||
let timeline_is_linear = timeline.pdus.is_empty() || {
|
||||
let last_pdu_of_last_sync = services
|
||||
.rooms
|
||||
.short
|
||||
.multi_get_eventid_from_short::<'_, OwnedEventId, _>(
|
||||
lazily_loaded_members
|
||||
.timeline
|
||||
.pdus_rev(room_id, Some(last_sync_end_count.saturating_add(1)))
|
||||
.boxed()
|
||||
.next()
|
||||
.await
|
||||
.transpose()
|
||||
.expect("last sync should have had some PDUs")
|
||||
.map(at!(1));
|
||||
|
||||
// make sure the prev_events of each pdu in the timeline refer only to the
|
||||
// previous pdu
|
||||
timeline
|
||||
.pdus
|
||||
.iter()
|
||||
.try_fold(last_pdu_of_last_sync.map(|pdu| pdu.event_id), |prev_event_id, (_, pdu)| {
|
||||
if let Ok(pdu_prev_event_id) = pdu.prev_events.iter().exactly_one() {
|
||||
if prev_event_id
|
||||
.as_ref()
|
||||
.is_none_or(is_equal_to!(pdu_prev_event_id))
|
||||
{
|
||||
return ControlFlow::Continue(Some(pdu_prev_event_id.to_owned()));
|
||||
}
|
||||
}
|
||||
|
||||
trace!(
|
||||
"pdu {:?} has split prev_events (expected {:?}): {:?}",
|
||||
pdu.event_id, prev_event_id, pdu.prev_events
|
||||
);
|
||||
ControlFlow::Break(())
|
||||
})
|
||||
.is_continue()
|
||||
};
|
||||
|
||||
if timeline_is_linear && !timeline.limited {
|
||||
// if there are no splits in the DAG and the timeline isn't limited, then
|
||||
// `state` will always be empty unless lazy loading is enabled.
|
||||
|
||||
if let Some(lazily_loaded_members) = lazily_loaded_members {
|
||||
if !timeline.pdus.is_empty() {
|
||||
// lazy loading is enabled, so we return the membership events which were
|
||||
// requested by the caller.
|
||||
let lazy_membership_events: Vec<_> = lazily_loaded_members
|
||||
.iter()
|
||||
.stream()
|
||||
.broad_filter_map(|user_id| async move {
|
||||
@@ -131,24 +178,71 @@ pub(super) async fn build_state_incremental<'a>(
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.state_get_shortid(
|
||||
.state_get(
|
||||
timeline_start_shortstatehash,
|
||||
&StateEventType::RoomMember,
|
||||
user_id.as_str(),
|
||||
)
|
||||
.ok()
|
||||
.await
|
||||
}),
|
||||
)
|
||||
.ignore_err()
|
||||
.ready_for_each(|event_id| {
|
||||
state_event_ids.insert(event_id);
|
||||
})
|
||||
.await;
|
||||
})
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
if !lazy_membership_events.is_empty() {
|
||||
trace!(
|
||||
"syncing lazy membership events for members: {:?}",
|
||||
lazy_membership_events
|
||||
.iter()
|
||||
.map(|pdu| pdu.state_key().unwrap())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
return Ok(lazy_membership_events);
|
||||
}
|
||||
}
|
||||
|
||||
// lazy loading is disabled, `state` is empty.
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
// Fetch the state events added since the last sync.
|
||||
services
|
||||
/*
|
||||
at this point, either the timeline is `limited` or the DAG has a split in it. this necessitates
|
||||
computing the incremental state (which may be empty).
|
||||
|
||||
NOTE: this code path does not use the `lazy_membership_events` parameter. any changes to membership will be included
|
||||
in the incremental state. therefore, the incremental state may include "redundant" membership events,
|
||||
which we do not filter out because A. the spec forbids lazy-load filtering if the timeline is `limited`,
|
||||
and B. DAG splits which require sending extra membership state events are (probably) uncommon enough that
|
||||
the performance penalty is acceptable.
|
||||
*/
|
||||
|
||||
trace!(%timeline_is_linear, %timeline.limited, "computing state for incremental sync");
|
||||
|
||||
// fetch the shorteventids of state events in the timeline
|
||||
let state_events_in_timeline: BTreeSet<ShortEventId> = services
|
||||
.rooms
|
||||
.short
|
||||
.multi_get_or_create_shorteventid(timeline.pdus.iter().filter_map(|(_, pdu)| {
|
||||
if pdu.state_key().is_some() {
|
||||
Some(pdu.event_id.as_ref())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}))
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
trace!("{} state events in timeline", state_events_in_timeline.len());
|
||||
|
||||
/*
|
||||
fetch the state events which were added since the last sync.
|
||||
|
||||
specifically we fetch the difference between the state at the last sync and the state at the _end_
|
||||
of the timeline, and then we filter out state events in the timeline itself using the shorteventids we fetched.
|
||||
this is necessary to account for splits in the DAG, as explained above.
|
||||
*/
|
||||
let state_diff = services
|
||||
.rooms
|
||||
.short
|
||||
.multi_get_eventid_from_short::<'_, OwnedEventId, _>(
|
||||
@@ -158,29 +252,18 @@ pub(super) async fn build_state_incremental<'a>(
|
||||
.state_added((last_sync_end_shortstatehash, timeline_end_shortstatehash))
|
||||
.await?
|
||||
.stream()
|
||||
.map(at!(1)),
|
||||
.ready_filter_map(|(_, shorteventid)| {
|
||||
if state_events_in_timeline.contains(&shorteventid) {
|
||||
None
|
||||
} else {
|
||||
Some(shorteventid)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.ignore_err()
|
||||
.ready_for_each(|event_id| {
|
||||
state_event_ids.insert(event_id);
|
||||
})
|
||||
.await;
|
||||
.ignore_err();
|
||||
|
||||
if !use_state_after {
|
||||
// If state_after isn't enabled, filter out state events which also exist
|
||||
// in the timeline. If splits exist in the DAG, this may not be exactly the same
|
||||
// thing as the state diff ending at the start of the timeline, but Synapse
|
||||
// also does this and it's technically more useful behavior anyway.
|
||||
// See: https://github.com/element-hq/synapse/issues/16941
|
||||
|
||||
for (_, pdu) in &timeline.pdus {
|
||||
state_event_ids.remove(pdu.event_id());
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, fetch the PDU contents and collect them into a vec
|
||||
let state_diff_pdus = state_event_ids
|
||||
.stream()
|
||||
// finally, fetch the PDU contents and collect them into a vec
|
||||
let state_diff_pdus = state_diff
|
||||
.broad_filter_map(|event_id| async move {
|
||||
services
|
||||
.rooms
|
||||
|
||||
@@ -15,7 +15,7 @@ use conduwuit::{
|
||||
BoolExt, FutureBoolExt, IterStream, ReadyExt, TryFutureExtExt,
|
||||
future::ReadyEqExt,
|
||||
math::{ruma_from_usize, usize_from_ruma},
|
||||
stream::{TryIgnore, WidebandExt},
|
||||
stream::WidebandExt,
|
||||
},
|
||||
warn,
|
||||
};
|
||||
@@ -41,7 +41,6 @@ use ruma::{
|
||||
uint,
|
||||
};
|
||||
use service::account_data::AnyRawAccountDataEvent;
|
||||
use tokio::pin;
|
||||
|
||||
use super::share_encrypted_room;
|
||||
use crate::{
|
||||
@@ -70,6 +69,7 @@ pub(crate) async fn sync_events_v5_route(
|
||||
ClientIp(client_ip): ClientIp,
|
||||
body: Ruma<sync_events::v5::Request>,
|
||||
) -> Result<sync_events::v5::Response> {
|
||||
debug_assert!(DEFAULT_BUMP_TYPES.is_sorted(), "DEFAULT_BUMP_TYPES is not sorted");
|
||||
let ref sender_user = body.sender_user().to_owned();
|
||||
let ref sender_device = body.sender_device().to_owned();
|
||||
|
||||
@@ -858,27 +858,12 @@ where
|
||||
continue;
|
||||
};
|
||||
|
||||
let since_shortstatehash = async {
|
||||
pin! {
|
||||
let pdus_rev = services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_rev(room_id, Some(PduCount::Normal(globalsince.saturating_sub(1))))
|
||||
.ignore_err();
|
||||
}
|
||||
|
||||
let (_, pdu_at_last_sync_end) = pdus_rev.next().await?;
|
||||
|
||||
Some(
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.pdu_shortstatehash(&pdu_at_last_sync_end.event_id)
|
||||
.await
|
||||
.expect("pdu should have a shortstatehash"),
|
||||
)
|
||||
}
|
||||
.await;
|
||||
let since_shortstatehash = services
|
||||
.rooms
|
||||
.user
|
||||
.get_token_shortstatehash(room_id, globalsince)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
let encrypted_room = services
|
||||
.rooms
|
||||
|
||||
@@ -35,8 +35,8 @@ pub(crate) async fn get_supported_versions_route(
|
||||
/// `/_matrix/federation/v1/version`
|
||||
pub(crate) async fn conduwuit_server_version() -> Result<impl IntoResponse> {
|
||||
Ok(Json(serde_json::json!({
|
||||
"name": conduwuit::BRANDING,
|
||||
"version": conduwuit::version(),
|
||||
"name": conduwuit::version::name(),
|
||||
"version": conduwuit::version::version(),
|
||||
})))
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use conduwuit::{Err, Result};
|
||||
use ruma::{
|
||||
api::client::discovery::{
|
||||
discover_homeserver::{self, HomeserverInfo},
|
||||
discover_policy_server, discover_support,
|
||||
discover_support::{self, Contact, ContactRole},
|
||||
},
|
||||
assign,
|
||||
};
|
||||
@@ -66,7 +66,46 @@ pub(crate) async fn well_known_support(
|
||||
.as_ref()
|
||||
.map(ToString::to_string);
|
||||
|
||||
let contacts = services.admin.get_support_contacts().await;
|
||||
let email_address = services.config.well_known.support_email.clone();
|
||||
let matrix_id = services.config.well_known.support_mxid.clone();
|
||||
let pgp_key = services.config.well_known.support_pgp_key.clone();
|
||||
|
||||
// TODO: support defining multiple contacts in the config
|
||||
let mut contacts: Vec<Contact> = vec![];
|
||||
|
||||
let role = services
|
||||
.config
|
||||
.well_known
|
||||
.support_role
|
||||
.clone()
|
||||
.unwrap_or(ContactRole::Admin);
|
||||
|
||||
// Add configured contact if at least one contact method is specified
|
||||
let configured_contact = match (matrix_id, email_address) {
|
||||
| (Some(matrix_id), email_address) =>
|
||||
Some(assign!(Contact::with_matrix_id(role, matrix_id), { email_address })),
|
||||
| (None, Some(email_address)) => Some(Contact::with_email_address(role, email_address)),
|
||||
| (None, None) => None,
|
||||
};
|
||||
|
||||
if let Some(mut configured_contact) = configured_contact {
|
||||
configured_contact.pgp_key = pgp_key;
|
||||
|
||||
contacts.push(configured_contact);
|
||||
}
|
||||
|
||||
// Try to add admin users as contacts if no contacts are configured
|
||||
if contacts.is_empty() {
|
||||
let admin_users = services.admin.get_admins().await;
|
||||
|
||||
for user_id in &admin_users {
|
||||
if *user_id == services.globals.server_user {
|
||||
continue;
|
||||
}
|
||||
|
||||
contacts.push(Contact::with_matrix_id(ContactRole::Admin, user_id.to_owned()));
|
||||
}
|
||||
}
|
||||
|
||||
if contacts.is_empty() && support_page.is_none() {
|
||||
// No admin room, no configured contacts, and no support page
|
||||
@@ -75,18 +114,3 @@ pub(crate) async fn well_known_support(
|
||||
|
||||
Ok(assign!(discover_support::Response::with_contacts(contacts), { support_page }))
|
||||
}
|
||||
|
||||
/// # `GET /.well-known/matrix/policy_server`
|
||||
///
|
||||
/// Advertises the policy server's public key, allowing clients to discover the
|
||||
/// values to be set in m.room.policy. Introduced in spec v1.18.
|
||||
pub(crate) async fn well_known_policy_server(
|
||||
State(services): State<crate::State>,
|
||||
_body: Ruma<discover_policy_server::Request>,
|
||||
) -> Result<discover_policy_server::Response> {
|
||||
if let Some(key) = services.config.well_known.policy_server_public_key.clone() {
|
||||
Ok(discover_policy_server::Response::new(key))
|
||||
} else {
|
||||
Err!(Request(NotFound("No policy server available.")))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#![type_length_limit = "16384"] //TODO: reduce me
|
||||
#![recursion_limit = "256"] // My Giant Async Function
|
||||
#![allow(clippy::toplevel_ref_arg)]
|
||||
|
||||
extern crate conduwuit_core as conduwuit;
|
||||
|
||||
+3
-6
@@ -10,7 +10,7 @@ use axum::{
|
||||
response::{IntoResponse, Redirect},
|
||||
routing::{any, get, post},
|
||||
};
|
||||
use conduwuit::err;
|
||||
use conduwuit::{Server, err};
|
||||
pub(super) use conduwuit_service::state::State;
|
||||
use http::{Uri, uri};
|
||||
|
||||
@@ -18,8 +18,8 @@ use self::handler::RouterExt;
|
||||
pub(super) use self::{args::Args as Ruma, response::RumaResponse};
|
||||
use crate::{admin, client, server};
|
||||
|
||||
pub fn build(router: Router<State>, state: State) -> Router<State> {
|
||||
let config = &state.server.config;
|
||||
pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||
let config = &server.config;
|
||||
let mut router = router
|
||||
.ruma_route(&client::appservice_ping)
|
||||
.ruma_route(&client::get_supported_versions_route)
|
||||
@@ -183,11 +183,8 @@ pub fn build(router: Router<State>, state: State) -> Router<State> {
|
||||
.ruma_route(&client::put_suspended_status)
|
||||
.ruma_route(&client::well_known_support)
|
||||
.ruma_route(&client::well_known_client)
|
||||
.ruma_route(&client::well_known_policy_server)
|
||||
.ruma_route(&client::get_rtc_transports)
|
||||
.ruma_route(&client::room_initial_sync_route)
|
||||
.ruma_route(&client::get_authorization_server_metadata_route)
|
||||
.merge(client::oauth::router(state))
|
||||
.route("/_conduwuit/server_version", get(client::conduwuit_server_version))
|
||||
.route("/_continuwuity/server_version", get(client::conduwuit_server_version))
|
||||
.ruma_route(&admin::rooms::ban::ban_room)
|
||||
|
||||
+14
-55
@@ -1,7 +1,6 @@
|
||||
use std::any::{Any, TypeId};
|
||||
|
||||
use conduwuit::{Err, Error, Result, err};
|
||||
use http::StatusCode;
|
||||
use conduwuit::{Err, Result, err};
|
||||
use ruma::{
|
||||
OwnedDeviceId, OwnedServerName, OwnedUserId, UserId,
|
||||
api::{
|
||||
@@ -10,16 +9,12 @@ use ruma::{
|
||||
AccessToken, AccessTokenOptional, AppserviceToken, AppserviceTokenOptional,
|
||||
AuthScheme, NoAccessToken, NoAuthentication,
|
||||
},
|
||||
client,
|
||||
error::{ErrorKind, UnknownTokenErrorData},
|
||||
federation::authentication::ServerSignatures,
|
||||
},
|
||||
assign,
|
||||
};
|
||||
use service::{
|
||||
Services,
|
||||
server_keys::{PubKeyMap, PubKeys},
|
||||
users::AccessTokenStatus,
|
||||
};
|
||||
|
||||
use crate::{router::args::AuthQueryParams, service::appservice::RegistrationInfo};
|
||||
@@ -90,21 +85,10 @@ impl CheckAuth for ServerSignatures {
|
||||
let keys: PubKeyMap = [(output.origin.as_str().into(), keys)].into();
|
||||
|
||||
match output.verify_request(request, destination, &keys) {
|
||||
| Ok(()) => {
|
||||
if services
|
||||
.moderation
|
||||
.is_remote_server_forbidden(&output.origin)
|
||||
{
|
||||
return Err!(Request(Forbidden(
|
||||
"You are blocked from federating with this server."
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(Auth {
|
||||
origin: Some(output.origin.clone()),
|
||||
..Default::default()
|
||||
})
|
||||
},
|
||||
| Ok(()) => Ok(Auth {
|
||||
origin: Some(output.origin.clone()),
|
||||
..Default::default()
|
||||
}),
|
||||
| Err(err) =>
|
||||
Err!(Request(Unauthorized(warn!("Failed to verify X-Matrix header: {err}")))),
|
||||
}
|
||||
@@ -119,21 +103,12 @@ impl CheckAuth for AccessToken {
|
||||
query: AuthQueryParams,
|
||||
route: TypeId,
|
||||
) -> Result<Auth> {
|
||||
// Check for appservice tokens first
|
||||
|
||||
let (sender_user, sender_device, appservice_info) = {
|
||||
if let Some((sender_user, sender_device, status)) =
|
||||
if let Ok((sender_user, sender_device)) =
|
||||
services.users.find_from_token(&output).await
|
||||
{
|
||||
// If the token is expired we return a soft logout
|
||||
if matches!(status, AccessTokenStatus::Expired) {
|
||||
return Err(Error::Request(
|
||||
ErrorKind::UnknownToken(
|
||||
assign!(UnknownTokenErrorData::new(), { soft_logout: true }),
|
||||
),
|
||||
"This token has expired".into(),
|
||||
StatusCode::UNAUTHORIZED,
|
||||
));
|
||||
}
|
||||
|
||||
// Locked users can only use /logout and /logout/all
|
||||
if services
|
||||
.users
|
||||
@@ -141,10 +116,11 @@ impl CheckAuth for AccessToken {
|
||||
.await
|
||||
.is_ok_and(std::convert::identity)
|
||||
{
|
||||
if !(route == TypeId::of::<client::session::logout::v3::Request>()
|
||||
|| route == TypeId::of::<client::session::logout_all::v3::Request>())
|
||||
{
|
||||
return Err!(Request(UserLocked("Your account is locked.")));
|
||||
if !(route == TypeId::of::<ruma::api::client::session::logout::v3::Request>()
|
||||
|| route
|
||||
== TypeId::of::<ruma::api::client::session::logout_all::v3::Request>(
|
||||
)) {
|
||||
return Err!(Request(Unauthorized("Your account is locked.")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,11 +168,7 @@ impl CheckAuth for AccessToken {
|
||||
|
||||
(Some(sender_user), sender_device, Some(appservice_info))
|
||||
} else {
|
||||
return Err(Error::Request(
|
||||
ErrorKind::UnknownToken(UnknownTokenErrorData::new()),
|
||||
"Invalid token".into(),
|
||||
StatusCode::UNAUTHORIZED,
|
||||
));
|
||||
return Err!(Request(Unauthorized("Invalid access token.")));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -286,19 +258,6 @@ impl CheckAuth for NoAccessToken {
|
||||
err!(Request(Unauthorized(warn!("Failed to extract authorization: {}", err))))
|
||||
})?;
|
||||
|
||||
// Check special access restrictions
|
||||
if (route == TypeId::of::<client::profile::get_avatar_url::v3::Request>()
|
||||
|| route == TypeId::of::<client::profile::get_display_name::v3::Request>()
|
||||
|| route == TypeId::of::<client::profile::get_profile_field::v3::Request>()
|
||||
|| route == TypeId::of::<client::profile::get_profile::v3::Request>())
|
||||
&& services.config.require_auth_for_profile_requests
|
||||
&& token.is_none()
|
||||
{
|
||||
return Err!(Request(Unauthorized(
|
||||
"This server requires authentication to access user profiles."
|
||||
)));
|
||||
}
|
||||
|
||||
<AccessTokenOptional as CheckAuth>::verify(services, token, request, query, route).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,15 +22,6 @@ pub(crate) async fn get_event_route(
|
||||
.await
|
||||
.map_err(|_| err!(Request(NotFound("Event not found."))))?;
|
||||
|
||||
if services
|
||||
.rooms
|
||||
.pdu_metadata
|
||||
.is_event_rejected(&body.event_id)
|
||||
.await
|
||||
{
|
||||
return Err!(Request(NotFound("Event not found.")));
|
||||
}
|
||||
|
||||
let room_id: &RoomId = event
|
||||
.get("room_id")
|
||||
.and_then(|val| val.as_str())
|
||||
|
||||
@@ -26,15 +26,6 @@ pub(crate) async fn get_event_authorization_route(
|
||||
.check()
|
||||
.await?;
|
||||
|
||||
if services
|
||||
.rooms
|
||||
.pdu_metadata
|
||||
.is_event_rejected(&body.event_id)
|
||||
.await
|
||||
{
|
||||
return Err!(Request(NotFound("Event not found.")));
|
||||
}
|
||||
|
||||
if !services
|
||||
.rooms
|
||||
.state_cache
|
||||
|
||||
@@ -78,15 +78,6 @@ pub(crate) async fn get_missing_events_route(
|
||||
body.room_id
|
||||
)));
|
||||
}
|
||||
if services
|
||||
.rooms
|
||||
.pdu_metadata
|
||||
.is_event_rejected(pdu.event_id())
|
||||
.await
|
||||
{
|
||||
debug!(%next_event_id, "event rejected, not traversing");
|
||||
continue;
|
||||
}
|
||||
|
||||
if !services
|
||||
.rooms
|
||||
|
||||
@@ -24,15 +24,6 @@ pub(crate) async fn get_room_state_route(
|
||||
.check()
|
||||
.await?;
|
||||
|
||||
if services
|
||||
.rooms
|
||||
.pdu_metadata
|
||||
.is_event_rejected(&body.event_id)
|
||||
.await
|
||||
{
|
||||
return Err!(Request(NotFound("Event not found.")));
|
||||
}
|
||||
|
||||
if !services
|
||||
.rooms
|
||||
.state_cache
|
||||
|
||||
@@ -25,15 +25,6 @@ pub(crate) async fn get_room_state_ids_route(
|
||||
.check()
|
||||
.await?;
|
||||
|
||||
if services
|
||||
.rooms
|
||||
.pdu_metadata
|
||||
.is_event_rejected(&body.event_id)
|
||||
.await
|
||||
{
|
||||
return Err!(Request(NotFound("Event not found.")));
|
||||
}
|
||||
|
||||
if !services
|
||||
.rooms
|
||||
.state_cache
|
||||
|
||||
@@ -11,8 +11,8 @@ pub(crate) async fn get_server_version_route(
|
||||
) -> Result<get_server_version::v1::Response> {
|
||||
Ok(assign!(get_server_version::v1::Response::new(), {
|
||||
server: Some(assign!(get_server_version::v1::Server::new(), {
|
||||
name: Some(conduwuit::BRANDING.into()),
|
||||
version: Some(conduwuit::version().into()),
|
||||
name: Some(conduwuit::version::name().into()),
|
||||
version: Some(conduwuit::version::version().into()),
|
||||
})),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::env::consts::OS;
|
||||
|
||||
use either::Either;
|
||||
use figment::Figment;
|
||||
use ruma::events::room::server_acl::RoomServerAclEventContent;
|
||||
|
||||
use super::DEPRECATED_KEYS;
|
||||
use crate::{Config, Err, Result, Server, debug, debug_info, debug_warn, error, warn};
|
||||
@@ -255,40 +254,6 @@ pub fn check(config: &Config) -> Result {
|
||||
));
|
||||
}
|
||||
|
||||
match (&config.default_room_acl_allow, &config.default_room_acl_deny) {
|
||||
| (Some(_), Some(_)) => {
|
||||
return Err!(Config(
|
||||
"default_room_acl_deny",
|
||||
"Cannot provide a value for both default_room_acl_allow and \
|
||||
default_room_acl_deny."
|
||||
));
|
||||
},
|
||||
| (Some(allow), None) => {
|
||||
if !RoomServerAclEventContent::new(true, allow.clone(), vec![])
|
||||
.is_allowed(&config.server_name)
|
||||
{
|
||||
return Err!(Config(
|
||||
"default_room_acl_allow",
|
||||
"The default room Access Control List does not allow this server in the \
|
||||
rooms it creates. Note that when using an allow list, servers are denied \
|
||||
unless they match an allow value."
|
||||
));
|
||||
}
|
||||
},
|
||||
| (None, Some(deny)) => {
|
||||
if !RoomServerAclEventContent::new(true, vec!["*".to_owned()], deny.clone())
|
||||
.is_allowed(&config.server_name)
|
||||
{
|
||||
return Err!(Config(
|
||||
"default_room_acl_deny",
|
||||
"The default room Access Control List denies this server access to the \
|
||||
rooms it creates."
|
||||
));
|
||||
}
|
||||
},
|
||||
| _ => (),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
+59
-154
@@ -4,7 +4,7 @@ pub mod manager;
|
||||
pub mod proxy;
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
collections::{BTreeMap, BTreeSet, HashMap},
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||
path::PathBuf,
|
||||
};
|
||||
@@ -21,7 +21,6 @@ use regex::RegexSet;
|
||||
use ruma::{
|
||||
OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, RoomVersionId,
|
||||
api::client::{discovery::discover_support::ContactRole, rtc::RtcTransport},
|
||||
serde::Base64,
|
||||
};
|
||||
use serde::{Deserialize, Serialize, de::IgnoredAny};
|
||||
use url::Url;
|
||||
@@ -476,18 +475,20 @@ pub struct Config {
|
||||
#[serde(default = "default_federation_timeout")]
|
||||
pub federation_timeout: u64,
|
||||
|
||||
/// Policy server request timeout (seconds). Generally policy
|
||||
/// MSC4284 Policy server request timeout (seconds). Generally policy
|
||||
/// servers should respond near instantly, however may slow down under
|
||||
/// load. If a policy server doesn't respond in a short amount of time, the
|
||||
/// room it is configured in may become unusable if this limit is set too
|
||||
/// high. 30 seconds is a good default, however lower values may be
|
||||
/// acceptable if temporary send failures are an okay trade-off.
|
||||
/// high. 10 seconds is a good default, however dropping this to 3-5 seconds
|
||||
/// can be acceptable.
|
||||
///
|
||||
/// Please be aware that policy requests are *NOT* currently re-tried, so if
|
||||
/// a spam check request fails, the event will be assumed to be not spam,
|
||||
/// which in some cases may result in spam being sent to or received from
|
||||
/// the room that would typically be prevented.
|
||||
///
|
||||
/// About policy servers: https://matrix.org/blog/2025/04/introducing-policy-servers/
|
||||
/// (Stabilized in Matrix v1.18)
|
||||
///
|
||||
/// default: 30
|
||||
/// default: 10
|
||||
#[serde(default = "default_policy_server_request_timeout")]
|
||||
pub policy_server_request_timeout: u64,
|
||||
|
||||
@@ -655,25 +656,19 @@ pub struct Config {
|
||||
/// even if `recaptcha_site_key` is set.
|
||||
pub recaptcha_private_site_key: Option<String>,
|
||||
|
||||
/// display: nested
|
||||
#[serde(default)]
|
||||
pub registration_terms: RegistrationTerms,
|
||||
|
||||
/// display: nested
|
||||
#[serde(default)]
|
||||
pub oauth: OauthConfig,
|
||||
|
||||
/// Controls whether users are allowed to deactivate their own accounts
|
||||
/// through the account management panel or their Matrix clients. Server
|
||||
/// admins can always deactivate users using the relevant admin commands.
|
||||
/// Policy documents, such as terms and conditions or a privacy policy,
|
||||
/// which users must agree to when registering an account.
|
||||
///
|
||||
/// Note that, in some jurisdictions, you may be legally required to honor
|
||||
/// users who request to deactivate their accounts if you set this option
|
||||
/// to `false`.
|
||||
/// Example:
|
||||
/// ```ignore
|
||||
/// [global.registration_terms.privacy_policy]
|
||||
/// en = { name = "Privacy Policy", url = "https://homeserver.example/en/privacy_policy.html" }
|
||||
/// es = { name = "Política de Privacidad", url = "https://homeserver.example/es/privacy_policy.html" }
|
||||
/// ```
|
||||
///
|
||||
/// default: true
|
||||
#[serde(default = "true_fn")]
|
||||
pub allow_deactivation: bool,
|
||||
/// default: {}
|
||||
#[serde(default)]
|
||||
pub registration_terms: HashMap<String, HashMap<String, TermsDocument>>,
|
||||
|
||||
/// Controls whether encrypted rooms and events are allowed.
|
||||
#[serde(default = "true_fn")]
|
||||
@@ -765,28 +760,6 @@ pub struct Config {
|
||||
#[serde(default = "default_default_room_version")]
|
||||
pub default_room_version: RoomVersionId,
|
||||
|
||||
/// A default allow value for the Access Control List when creating a room.
|
||||
///
|
||||
/// If a list is provided, new rooms will be created with
|
||||
/// a m.room.server_acl event. Only servers which match one of the patterns
|
||||
/// in the list will be permitted to participate in the room.
|
||||
///
|
||||
/// ACLs in existing rooms will not be updated automatically. This is not
|
||||
/// a substitute for moderation bots.
|
||||
pub default_room_acl_allow: Option<Vec<String>>,
|
||||
|
||||
/// A default deny value for the Access Control List when creating a room.
|
||||
///
|
||||
/// If a list is provided, new rooms will be created with
|
||||
/// a m.room.server_acl event. Servers which match one of the patterns
|
||||
/// in the list will be NOT permitted to participate in the room.
|
||||
///
|
||||
/// This config cannot be used if the default_room_acl_allow config is used.
|
||||
///
|
||||
/// ACLs in existing rooms will not be updated automatically. This is not
|
||||
/// a substitute for moderation bots.
|
||||
pub default_room_acl_deny: Option<Vec<String>>,
|
||||
|
||||
/// display: nested
|
||||
#[serde(default)]
|
||||
pub well_known: WellKnownConfig,
|
||||
@@ -1841,6 +1814,19 @@ pub struct Config {
|
||||
#[serde(default)]
|
||||
pub block_non_admin_invites: bool,
|
||||
|
||||
/// Enable or disable making requests to MSC4284 Policy Servers.
|
||||
/// It is recommended you keep this enabled unless you experience frequent
|
||||
/// connectivity issues, such as in a restricted networking environment.
|
||||
#[serde(default = "true_fn")]
|
||||
pub enable_msc4284_policy_servers: bool,
|
||||
|
||||
/// Enable running locally generated events through configured MSC4284
|
||||
/// policy servers. You may wish to disable this if your server is
|
||||
/// single-user for a slight speed benefit in some rooms, but otherwise
|
||||
/// should leave it enabled.
|
||||
#[serde(default = "true_fn")]
|
||||
pub policy_server_check_own_events: bool,
|
||||
|
||||
/// Allow admins to enter commands in rooms other than "#admins" (admin
|
||||
/// room) by prefixing your message with "\!admin" or "\\!admin" followed up
|
||||
/// a normal continuwuity admin command. The reply will be publicly visible
|
||||
@@ -2077,10 +2063,12 @@ pub struct Config {
|
||||
pub stream_amplification: usize,
|
||||
|
||||
/// Number of sender task workers; determines sender parallelism. Default is
|
||||
/// core count. Override by setting a different value.
|
||||
/// '0' which means the value is determined internally, likely matching the
|
||||
/// number of tokio worker-threads or number of cores, etc. Override by
|
||||
/// setting a non-zero value.
|
||||
///
|
||||
/// default: core count
|
||||
#[serde(default = "default_sender_workers")]
|
||||
/// default: 0
|
||||
#[serde(default)]
|
||||
pub sender_workers: usize,
|
||||
|
||||
/// Enables listener sockets; can be set to false to disable listening. This
|
||||
@@ -2180,10 +2168,6 @@ pub struct WellKnownConfig {
|
||||
/// Will be included alongside any contact information
|
||||
pub support_page: Option<Url>,
|
||||
|
||||
/// The ed25519 public key for the policy server available at this server's
|
||||
/// name. Must be unpadded base64.
|
||||
pub policy_server_public_key: Option<Base64<ruma::serde::base64::Standard>>,
|
||||
|
||||
/// Role string for server support contacts, to be served as part of the
|
||||
/// MSC1929 server support endpoint at /.well-known/matrix/support.
|
||||
///
|
||||
@@ -2334,10 +2318,8 @@ pub struct SmtpConfig {
|
||||
/// - `address@domain.org` to not use a name
|
||||
pub sender: Mailbox,
|
||||
|
||||
/// Whether to allow public registration with an email address.
|
||||
///
|
||||
/// Note that, if this option is enabled, anyone will be able to register an
|
||||
/// account with just an email address.
|
||||
/// Whether to require that users provide an email address when they
|
||||
/// register.
|
||||
///
|
||||
/// If either this option or `require_email_for_token_registration` are set,
|
||||
/// users will not be allowed to remove their email address.
|
||||
@@ -2347,38 +2329,13 @@ pub struct SmtpConfig {
|
||||
pub require_email_for_registration: bool,
|
||||
|
||||
/// Whether to require that users who register with a registration token
|
||||
/// provide an email address. This option is independent of
|
||||
/// `require_email_for_registration`.
|
||||
/// provide an email address.
|
||||
///
|
||||
/// default: false
|
||||
#[serde(default)]
|
||||
pub require_email_for_token_registration: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[config_example_generator(
|
||||
filename = "conduwuit-example.toml",
|
||||
section = "global.registration_terms",
|
||||
optional = "true"
|
||||
)]
|
||||
pub struct RegistrationTerms {
|
||||
/// The language code to provide to clients along with the policy documents.
|
||||
///
|
||||
/// default: "en"
|
||||
pub language: String,
|
||||
/// Policy documents, such as terms and conditions or a privacy policy,
|
||||
/// which users must agree to when registering an account.
|
||||
///
|
||||
/// Example:
|
||||
/// ```ignore
|
||||
/// [global.registration_terms.documents]
|
||||
/// privacy_policy = { name = "Privacy Policy", url = "https://homeserver.example/en/privacy_policy.html" }
|
||||
/// ```
|
||||
///
|
||||
/// default: {}
|
||||
pub documents: BTreeMap<String, TermsDocument>,
|
||||
}
|
||||
|
||||
/// A policy document for use with a m.login.terms stage.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct TermsDocument {
|
||||
@@ -2386,43 +2343,6 @@ pub struct TermsDocument {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
#[config_example_generator(
|
||||
filename = "conduwuit-example.toml",
|
||||
section = "global.oauth",
|
||||
optional = "true"
|
||||
)]
|
||||
pub struct OauthConfig {
|
||||
/// The compatibility mode to use for OAuth.
|
||||
///
|
||||
/// - "disabled": OAuth will be unavailable. Users will only be able to log
|
||||
/// in using legacy authentication.
|
||||
/// - "hybrid": OAuth and legacy authentication will both be available. Some
|
||||
/// clients may only use one or the other.
|
||||
/// - "exclusive": Only OAuth will be available. Clients which require
|
||||
/// legacy authentication will be unable to log in.
|
||||
///
|
||||
/// default: "hybrid"
|
||||
pub compatibility_mode: OAuthMode,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum OAuthMode {
|
||||
Disabled,
|
||||
#[default]
|
||||
Hybrid,
|
||||
Exclusive,
|
||||
}
|
||||
|
||||
impl OAuthMode {
|
||||
#[must_use]
|
||||
pub fn uiaa_available(&self) -> bool { matches!(self, Self::Disabled | Self::Hybrid) }
|
||||
|
||||
#[must_use]
|
||||
pub fn oauth_available(&self) -> bool { matches!(self, Self::Hybrid | Self::Exclusive) }
|
||||
}
|
||||
|
||||
const DEPRECATED_KEYS: &[&str] = &[
|
||||
"cache_capacity",
|
||||
"conduit_cache_capacity_modifier",
|
||||
@@ -2520,47 +2440,45 @@ fn default_database_backups_to_keep() -> i16 { 1 }
|
||||
|
||||
fn default_db_write_buffer_capacity_mb() -> f64 { 48.0 + parallelism_scaled_f64(4.0) }
|
||||
|
||||
fn default_db_cache_capacity_mb() -> f64 { 512.0 + parallelism_scaled_f64(512.0) }
|
||||
fn default_db_cache_capacity_mb() -> f64 { 128.0 + parallelism_scaled_f64(64.0) }
|
||||
|
||||
fn default_pdu_cache_capacity() -> u32 { parallelism_scaled_u32(50_000).saturating_add(100_000) }
|
||||
fn default_pdu_cache_capacity() -> u32 { parallelism_scaled_u32(10_000).saturating_add(100_000) }
|
||||
|
||||
fn default_cache_capacity_modifier() -> f64 { 1.0 }
|
||||
|
||||
fn default_auth_chain_cache_capacity() -> u32 {
|
||||
parallelism_scaled_u32(50_000).saturating_add(100_000)
|
||||
parallelism_scaled_u32(10_000).saturating_add(100_000)
|
||||
}
|
||||
|
||||
fn default_shorteventid_cache_capacity() -> u32 {
|
||||
parallelism_scaled_u32(100_000).saturating_add(100_000)
|
||||
parallelism_scaled_u32(50_000).saturating_add(100_000)
|
||||
}
|
||||
|
||||
fn default_eventidshort_cache_capacity() -> u32 {
|
||||
parallelism_scaled_u32(50_000).saturating_add(100_000)
|
||||
parallelism_scaled_u32(25_000).saturating_add(100_000)
|
||||
}
|
||||
|
||||
fn default_eventid_pdu_cache_capacity() -> u32 {
|
||||
parallelism_scaled_u32(50_000).saturating_add(100_000)
|
||||
parallelism_scaled_u32(25_000).saturating_add(100_000)
|
||||
}
|
||||
|
||||
fn default_shortstatekey_cache_capacity() -> u32 {
|
||||
parallelism_scaled_u32(100_000).saturating_add(100_000)
|
||||
parallelism_scaled_u32(10_000).saturating_add(100_000)
|
||||
}
|
||||
|
||||
fn default_statekeyshort_cache_capacity() -> u32 {
|
||||
parallelism_scaled_u32(50_000).saturating_add(100_000)
|
||||
parallelism_scaled_u32(10_000).saturating_add(100_000)
|
||||
}
|
||||
|
||||
fn default_servernameevent_data_cache_capacity() -> u32 {
|
||||
parallelism_scaled_u32(100_000).saturating_add(100_000)
|
||||
parallelism_scaled_u32(100_000).saturating_add(500_000)
|
||||
}
|
||||
|
||||
fn default_stateinfo_cache_capacity() -> u32 { parallelism_scaled_u32(500).clamp(100, 12000) }
|
||||
fn default_stateinfo_cache_capacity() -> u32 { parallelism_scaled_u32(100) }
|
||||
|
||||
fn default_roomid_spacehierarchy_cache_capacity() -> u32 {
|
||||
parallelism_scaled_u32(500).clamp(100, 12000)
|
||||
}
|
||||
fn default_roomid_spacehierarchy_cache_capacity() -> u32 { parallelism_scaled_u32(1000) }
|
||||
|
||||
fn default_dns_cache_entries() -> u32 { 327_680 }
|
||||
fn default_dns_cache_entries() -> u32 { 32768 }
|
||||
|
||||
fn default_dns_min_ttl() -> u64 { 60 * 180 }
|
||||
|
||||
@@ -2594,7 +2512,7 @@ fn default_federation_conn_timeout() -> u64 { 10 }
|
||||
|
||||
fn default_federation_timeout() -> u64 { 60 }
|
||||
|
||||
fn default_policy_server_request_timeout() -> u64 { 30 }
|
||||
fn default_policy_server_request_timeout() -> u64 { 10 }
|
||||
|
||||
fn default_federation_idle_timeout() -> u64 { 25 }
|
||||
|
||||
@@ -2768,26 +2686,15 @@ fn default_admin_log_capture() -> String {
|
||||
|
||||
fn default_admin_room_tag() -> String { "m.server_notice".to_owned() }
|
||||
|
||||
#[must_use]
|
||||
#[allow(clippy::as_conversions, clippy::cast_precision_loss)]
|
||||
pub fn parallelism_scaled_f64(val: f64) -> f64 { val * (sys::available_parallelism() as f64) }
|
||||
fn parallelism_scaled_f64(val: f64) -> f64 { val * (sys::available_parallelism() as f64) }
|
||||
|
||||
#[must_use]
|
||||
#[allow(clippy::as_conversions, clippy::cast_possible_truncation)]
|
||||
pub fn parallelism_scaled_u32(val: u32) -> u32 {
|
||||
val.saturating_mul(sys::available_parallelism() as u32)
|
||||
fn parallelism_scaled_u32(val: u32) -> u32 {
|
||||
let val = val.try_into().expect("failed to cast u32 to usize");
|
||||
parallelism_scaled(val).try_into().unwrap_or(u32::MAX)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[allow(clippy::as_conversions, clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
|
||||
pub fn parallelism_scaled_i32(val: i32) -> i32 {
|
||||
val.saturating_mul(sys::available_parallelism() as i32)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn parallelism_scaled(val: usize) -> usize {
|
||||
val.saturating_mul(sys::available_parallelism())
|
||||
}
|
||||
fn parallelism_scaled(val: usize) -> usize { val.saturating_mul(sys::available_parallelism()) }
|
||||
|
||||
fn default_trusted_server_batch_size() -> usize { 256 }
|
||||
|
||||
@@ -2807,8 +2714,6 @@ fn default_stream_width_scale() -> f32 { 1.0 }
|
||||
|
||||
fn default_stream_amplification() -> usize { 1024 }
|
||||
|
||||
fn default_sender_workers() -> usize { parallelism_scaled(1) }
|
||||
|
||||
fn default_client_receive_timeout() -> u64 { 75 }
|
||||
|
||||
fn default_client_request_timeout() -> u64 { 180 }
|
||||
|
||||
+5
-18
@@ -6,10 +6,7 @@ mod serde;
|
||||
|
||||
use std::{any::Any, borrow::Cow, convert::Infallible, error::Error as _, sync::PoisonError};
|
||||
|
||||
use ruma::api::error::{ErrorKind, RetryAfter::Delay};
|
||||
|
||||
pub use self::{err::visit, log::*};
|
||||
use crate::Error::BadRequest;
|
||||
|
||||
#[derive(thiserror::Error)]
|
||||
pub enum Error {
|
||||
@@ -92,7 +89,7 @@ pub enum Error {
|
||||
#[error("Arithmetic operation failed: {0}")]
|
||||
Arithmetic(Cow<'static, str>),
|
||||
#[error("{0:?}: {1}")]
|
||||
BadRequest(ErrorKind, &'static str), //TODO: remove
|
||||
BadRequest(ruma::api::error::ErrorKind, &'static str), //TODO: remove
|
||||
#[error("{0}")]
|
||||
BadServerResponse(Cow<'static, str>),
|
||||
#[error(transparent)]
|
||||
@@ -120,7 +117,7 @@ pub enum Error {
|
||||
#[error("from {0}: {1}")]
|
||||
Redaction(ruma::OwnedServerName, ruma::canonical_json::RedactionError),
|
||||
#[error("{0:?}: {1}")]
|
||||
Request(ErrorKind, Cow<'static, str>, http::StatusCode),
|
||||
Request(ruma::api::error::ErrorKind, Cow<'static, str>, http::StatusCode),
|
||||
#[error(transparent)]
|
||||
Ruma(#[from] ruma::api::error::Error),
|
||||
#[error(transparent)]
|
||||
@@ -161,20 +158,19 @@ impl Error {
|
||||
match self {
|
||||
| Self::Federation(origin, error) => format!("Answer from {origin}: {error}"),
|
||||
| Self::Ruma(error) => response::ruma_error_message(error),
|
||||
| Self::Request(_, message, _) => message.clone().into_owned(),
|
||||
| _ => format!("{self}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the Matrix error code / error kind
|
||||
#[inline]
|
||||
pub fn kind(&self) -> ErrorKind {
|
||||
pub fn kind(&self) -> ruma::api::error::ErrorKind {
|
||||
use ruma::api::error::ErrorKind::{Unknown, Unrecognized};
|
||||
|
||||
match self {
|
||||
| Self::Federation(_, error) | Self::Ruma(error) =>
|
||||
response::ruma_error_kind(error).clone(),
|
||||
| BadRequest(kind, ..) | Self::Request(kind, ..) => kind.clone(),
|
||||
| Self::BadRequest(kind, ..) | Self::Request(kind, ..) => kind.clone(),
|
||||
| Self::FeatureDisabled(..) => Unrecognized,
|
||||
| _ => Unknown,
|
||||
}
|
||||
@@ -204,15 +200,6 @@ impl Error {
|
||||
/// Result where Ok(None) is instead Err(e) if e.is_not_found().
|
||||
#[inline]
|
||||
pub fn is_not_found(&self) -> bool { self.status_code() == http::StatusCode::NOT_FOUND }
|
||||
|
||||
pub fn retry_after(&self) -> Option<std::time::Duration> {
|
||||
if let BadRequest(ErrorKind::LimitExceeded(limit_data), ..) = self {
|
||||
if let Some(Delay(after)) = limit_data.retry_after {
|
||||
return Some(after);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Error {
|
||||
@@ -273,7 +260,7 @@ impl std::fmt::Display for FormattedReqwestError {
|
||||
write!(f, "{real_error}")
|
||||
}
|
||||
} else {
|
||||
write!(f, "Request error: {}", self.0)
|
||||
write!(f, "Request error: {}", &self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,8 +73,11 @@ pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode {
|
||||
// 413
|
||||
| TooLarge => StatusCode::PAYLOAD_TOO_LARGE,
|
||||
|
||||
// 405
|
||||
| Unrecognized => StatusCode::METHOD_NOT_ALLOWED,
|
||||
|
||||
// 404
|
||||
| Unrecognized | NotFound => StatusCode::NOT_FOUND,
|
||||
| NotFound => StatusCode::NOT_FOUND,
|
||||
|
||||
// 403
|
||||
| GuestAccessForbidden
|
||||
|
||||
@@ -7,16 +7,19 @@
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
pub const BRANDING: &str = "continuwuity";
|
||||
pub const ROUTE_PREFIX: &str = "/_continuwuity";
|
||||
pub const WEBSITE: &str = "https://continuwuity.org";
|
||||
pub const SEMANTIC: &str = env!("CARGO_PKG_VERSION");
|
||||
static BRANDING: &str = "continuwuity";
|
||||
static WEBSITE: &str = "https://continuwuity.org";
|
||||
static SEMANTIC: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
static VERSION: OnceLock<String> = OnceLock::new();
|
||||
static VERSION_UA: OnceLock<String> = OnceLock::new();
|
||||
static USER_AGENT: OnceLock<String> = OnceLock::new();
|
||||
static USER_AGENT_MEDIA: OnceLock<String> = OnceLock::new();
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn name() -> &'static str { BRANDING }
|
||||
|
||||
#[inline]
|
||||
pub fn version() -> &'static str { VERSION.get_or_init(init_version) }
|
||||
|
||||
@@ -29,10 +32,10 @@ pub fn user_agent() -> &'static str { USER_AGENT.get_or_init(init_user_agent) }
|
||||
#[inline]
|
||||
pub fn user_agent_media() -> &'static str { USER_AGENT_MEDIA.get_or_init(init_user_agent_media) }
|
||||
|
||||
fn init_user_agent() -> String { format!("{BRANDING}/{} (bot; +{WEBSITE})", version_ua()) }
|
||||
fn init_user_agent() -> String { format!("{}/{} (bot; +{WEBSITE})", name(), version_ua()) }
|
||||
|
||||
fn init_user_agent_media() -> String {
|
||||
format!("{BRANDING}/{} (embedbot; facebookexternalhit/1.1; +{WEBSITE})", version_ua())
|
||||
format!("{}/{} (embedbot; facebookexternalhit/1.1; +{WEBSITE})", name(), version_ua())
|
||||
}
|
||||
|
||||
fn init_version_ua() -> String {
|
||||
|
||||
@@ -21,7 +21,6 @@ pub fn versions() -> Vec<String> {
|
||||
"v1.12".to_owned(),
|
||||
"v1.13".to_owned(),
|
||||
"v1.14".to_owned(),
|
||||
"v1.15".to_owned(),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -34,7 +34,10 @@ pub use ::tracing;
|
||||
pub use conduwuit_build_metadata as build_metadata;
|
||||
pub use config::Config;
|
||||
pub use error::Error;
|
||||
pub use info::version::*;
|
||||
pub use info::{
|
||||
version,
|
||||
version::{name, version},
|
||||
};
|
||||
pub use matrix::{Event, EventTypeExt, Pdu, PduCount, PduEvent, PduId, pdu, state_res};
|
||||
pub use parking_lot::{Mutex as SyncMutex, RwLock as SyncRwLock};
|
||||
pub use server::Server;
|
||||
|
||||
@@ -5,7 +5,6 @@ pub type DigestOut = [u8; 256 / 8];
|
||||
/// Sha256 hash (input gather joined by 0xFF bytes)
|
||||
#[must_use]
|
||||
#[tracing::instrument(skip(inputs), level = "trace")]
|
||||
#[allow(clippy::unnecessary_fallible_conversions)]
|
||||
pub fn delimited<'a, T, I>(mut inputs: I) -> DigestOut
|
||||
where
|
||||
I: Iterator<Item = T> + 'a,
|
||||
@@ -26,7 +25,6 @@ where
|
||||
/// Sha256 hash (input gather)
|
||||
#[must_use]
|
||||
#[tracing::instrument(skip(inputs), level = "trace")]
|
||||
#[allow(clippy::unnecessary_fallible_conversions)]
|
||||
pub fn concat<'a, T, I>(inputs: I) -> DigestOut
|
||||
where
|
||||
I: Iterator<Item = T> + 'a,
|
||||
@@ -45,7 +43,6 @@ where
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[tracing::instrument(skip(input), level = "trace")]
|
||||
#[allow(clippy::unnecessary_fallible_conversions)]
|
||||
pub fn hash<T>(input: T) -> DigestOut
|
||||
where
|
||||
T: AsRef<[u8]>,
|
||||
|
||||
@@ -284,11 +284,11 @@ fn is_within_bounds() {
|
||||
use utils::time::{TimeDirection, is_within_bounds};
|
||||
|
||||
let now = SystemTime::now();
|
||||
let yesterday = now - Duration::from_hours(24);
|
||||
let yesterday = now - Duration::from_secs(86400);
|
||||
assert!(is_within_bounds(yesterday, now, TimeDirection::Before));
|
||||
assert!(!is_within_bounds(yesterday, now, TimeDirection::After));
|
||||
|
||||
let tomorrow = now + Duration::from_hours(24);
|
||||
let tomorrow = now + Duration::from_secs(86400);
|
||||
assert!(is_within_bounds(tomorrow, now, TimeDirection::After));
|
||||
assert!(!is_within_bounds(tomorrow, now, TimeDirection::Before));
|
||||
|
||||
|
||||
+10
-16
@@ -61,23 +61,17 @@ pub fn format(ts: SystemTime, str: &str) -> String {
|
||||
pub fn pretty(d: Duration) -> String {
|
||||
use Unit::*;
|
||||
|
||||
let fmt = |w, u| {
|
||||
if w == 1 {
|
||||
format!("{w} {u}")
|
||||
} else {
|
||||
format!("{w} {u}s")
|
||||
}
|
||||
};
|
||||
let gen64 = |w, u| fmt(w, u);
|
||||
let gen128 = |w, u| gen64(u64::try_from(w).expect("u128 to u64"), u);
|
||||
let fmt = |w, f, u| format!("{w}.{f} {u}");
|
||||
let gen64 = |w, f, u| fmt(w, (f * 100.0) as u32, u);
|
||||
let gen128 = |w, f, u| gen64(u64::try_from(w).expect("u128 to u64"), f, u);
|
||||
match whole_and_frac(d) {
|
||||
| (Days(whole), _) => gen64(whole, "day"),
|
||||
| (Hours(whole), _) => gen64(whole, "hour"),
|
||||
| (Mins(whole), _) => gen64(whole, "minute"),
|
||||
| (Secs(whole), _) => gen64(whole, "second"),
|
||||
| (Millis(whole), _) => gen128(whole, "millisecond"),
|
||||
| (Micros(whole), _) => gen128(whole, "microsecond"),
|
||||
| (Nanos(whole), _) => gen128(whole, "nanosecond"),
|
||||
| (Days(whole), frac) => gen64(whole, frac, "days"),
|
||||
| (Hours(whole), frac) => gen64(whole, frac, "hours"),
|
||||
| (Mins(whole), frac) => gen64(whole, frac, "minutes"),
|
||||
| (Secs(whole), frac) => gen64(whole, frac, "seconds"),
|
||||
| (Millis(whole), frac) => gen128(whole, frac, "milliseconds"),
|
||||
| (Micros(whole), frac) => gen128(whole, frac, "microseconds"),
|
||||
| (Nanos(whole), frac) => gen128(whole, frac, "nanoseconds"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ fn descriptor_cf_options(
|
||||
set_table_options(&mut opts, &desc, cache)?;
|
||||
|
||||
opts.set_min_write_buffer_number(1);
|
||||
opts.set_max_write_buffer_number(3);
|
||||
opts.set_max_write_buffer_number(2);
|
||||
opts.set_write_buffer_size(desc.write_size);
|
||||
|
||||
opts.set_target_file_size_base(desc.file_size);
|
||||
|
||||
+6
-26
@@ -49,10 +49,6 @@ pub(super) static MAPS: &[Descriptor] = &[
|
||||
name: "bannedroomids",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "clientid_clientmetadata",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "disabledroomids",
|
||||
..descriptor::RANDOM_SMALL
|
||||
@@ -161,10 +157,6 @@ pub(super) static MAPS: &[Descriptor] = &[
|
||||
name: "referencedevents",
|
||||
..descriptor::RANDOM
|
||||
},
|
||||
Descriptor {
|
||||
name: "refreshtoken_refreshtokeninfo",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "registrationtoken_info",
|
||||
..descriptor::RANDOM_SMALL
|
||||
@@ -201,7 +193,12 @@ pub(super) static MAPS: &[Descriptor] = &[
|
||||
},
|
||||
Descriptor {
|
||||
name: "roomsynctoken_shortstatehash",
|
||||
..descriptor::DROPPED
|
||||
file_shape: 3,
|
||||
val_size_hint: Some(8),
|
||||
block_size: 512,
|
||||
compression_level: 3,
|
||||
bottommost_level: Some(6),
|
||||
..descriptor::SEQUENTIAL
|
||||
},
|
||||
Descriptor {
|
||||
name: "roomuserdataid_accountdata",
|
||||
@@ -314,11 +311,6 @@ pub(super) static MAPS: &[Descriptor] = &[
|
||||
key_size_hint: Some(48),
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "rejectedeventids",
|
||||
key_size_hint: Some(48),
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "statehash_shortstatehash",
|
||||
val_size_hint: Some(8),
|
||||
@@ -374,14 +366,6 @@ pub(super) static MAPS: &[Descriptor] = &[
|
||||
name: "userdevicetxnid_response",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "userdeviceid_oauthsessioninfo",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "userdeviceid_tokenexpires",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "userfilterid_filter",
|
||||
..descriptor::RANDOM_SMALL
|
||||
@@ -486,8 +470,4 @@ pub(super) static MAPS: &[Descriptor] = &[
|
||||
name: "userroomid_invitesender",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
Descriptor {
|
||||
name: "websessionid_session",
|
||||
..descriptor::RANDOM_SMALL
|
||||
},
|
||||
];
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ use conduwuit_core::{
|
||||
#[clap(
|
||||
about,
|
||||
long_about = None,
|
||||
name = conduwuit_core::BRANDING,
|
||||
name = conduwuit_core::name(),
|
||||
version = conduwuit_core::version(),
|
||||
)]
|
||||
pub struct Args {
|
||||
|
||||
+1
-1
@@ -110,7 +110,7 @@ pub(crate) fn init(
|
||||
.with_batch_exporter(exporter)
|
||||
.build();
|
||||
|
||||
let tracer = provider.tracer(conduwuit_core::BRANDING);
|
||||
let tracer = provider.tracer(conduwuit_core::name());
|
||||
|
||||
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
|
||||
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ fn options(config: &Config) -> ClientOptions {
|
||||
traces_sample_rate: config.sentry_traces_sample_rate,
|
||||
debug: cfg!(debug_assertions),
|
||||
release: release_name(),
|
||||
user_agent: conduwuit_core::user_agent().into(),
|
||||
user_agent: conduwuit_core::version::user_agent().into(),
|
||||
attach_stacktrace: config.sentry_attach_stacktrace,
|
||||
before_send: Some(Arc::new(before_send)),
|
||||
before_breadcrumb: Some(Arc::new(before_breadcrumb)),
|
||||
|
||||
@@ -8,7 +8,7 @@ use axum::{
|
||||
extract::State,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use conduwuit::{Result, debug_warn, err, error, info, trace};
|
||||
use conduwuit::{Result, debug, debug_error, debug_warn, err, error, trace};
|
||||
use conduwuit_service::Services;
|
||||
use futures::FutureExt;
|
||||
use http::{Method, StatusCode, Uri};
|
||||
@@ -102,19 +102,17 @@ fn handle_result(method: &Method, uri: &Uri, result: Response) -> Result<Respons
|
||||
let reason = status.canonical_reason().unwrap_or("Unknown Reason");
|
||||
|
||||
if status.is_server_error() {
|
||||
info!(%method, %uri, "{code} {reason}");
|
||||
error!(%method, %uri, "{code} {reason}");
|
||||
} else if status.is_client_error() {
|
||||
info!(%method, %uri, "{code} {reason}");
|
||||
debug_error!(%method, %uri, "{code} {reason}");
|
||||
} else if status.is_redirection() {
|
||||
trace!(%method, %uri, "{code} {reason}");
|
||||
debug!(%method, %uri, "{code} {reason}");
|
||||
} else {
|
||||
trace!(%method, %uri, "{code} {reason}");
|
||||
}
|
||||
|
||||
if status == StatusCode::METHOD_NOT_ALLOWED {
|
||||
return Ok(
|
||||
err!(Request(Unrecognized("Method not allowed"), METHOD_NOT_ALLOWED)).into_response()
|
||||
);
|
||||
return Ok(err!(Request(Unrecognized("Method Not Allowed"))).into_response());
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
|
||||
@@ -9,8 +9,8 @@ use ruma::api::error::ErrorKind;
|
||||
pub(crate) fn build(services: &Arc<Services>) -> (Router, Guard) {
|
||||
let router = Router::<state::State>::new();
|
||||
let (state, guard) = state::create(services.clone());
|
||||
let router = conduwuit_api::router::build(router, state)
|
||||
.merge(conduwuit_web::build(services))
|
||||
let router = conduwuit_api::router::build(router, &services.server)
|
||||
.merge(conduwuit_web::build())
|
||||
.fallback(not_found)
|
||||
.with_state(state);
|
||||
|
||||
|
||||
@@ -119,7 +119,6 @@ recaptcha-verify = { version = "0.2.0", default-features = false }
|
||||
reqwest_recaptcha = { package = "reqwest", version = "0.12.28", default-features = false, features = ["rustls-tls-native-roots-no-provider"] } # As long as recaptcha-verify's reqwest is outdated
|
||||
yansi.workspace = true
|
||||
lettre.workspace = true
|
||||
serde_urlencoded.workspace = true
|
||||
|
||||
[target.'cfg(all(unix, target_os = "linux"))'.dependencies]
|
||||
sd-notify.workspace = true
|
||||
|
||||
@@ -18,8 +18,6 @@ use futures::{Future, FutureExt, StreamExt, TryFutureExt};
|
||||
use loole::{Receiver, Sender};
|
||||
use ruma::{
|
||||
OwnedEventId, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId, UInt, UserId,
|
||||
api::client::discovery::discover_support::{Contact, ContactRole},
|
||||
assign,
|
||||
events::{
|
||||
Mentions,
|
||||
room::message::{
|
||||
@@ -30,7 +28,7 @@ use ruma::{
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::{
|
||||
Dep, account_data, config, globals,
|
||||
Dep, account_data, globals,
|
||||
media::{MXC_LENGTH, mxc::Mxc},
|
||||
rooms::{self, state::RoomMutexGuard},
|
||||
};
|
||||
@@ -46,7 +44,6 @@ pub struct Service {
|
||||
|
||||
struct Services {
|
||||
server: Arc<Server>,
|
||||
config: Dep<config::Service>,
|
||||
globals: Dep<globals::Service>,
|
||||
alias: Dep<rooms::alias::Service>,
|
||||
timeline: Dep<rooms::timeline::Service>,
|
||||
@@ -118,7 +115,6 @@ impl crate::Service for Service {
|
||||
Ok(Arc::new(Self {
|
||||
services: Services {
|
||||
server: args.server.clone(),
|
||||
config: args.depend::<config::Service>("config"),
|
||||
globals: args.depend::<globals::Service>("globals"),
|
||||
alias: args.depend::<rooms::alias::Service>("rooms::alias"),
|
||||
timeline: args.depend::<rooms::timeline::Service>("rooms::timeline"),
|
||||
@@ -623,52 +619,4 @@ impl Service {
|
||||
let weak = services.map(Arc::downgrade);
|
||||
*receiver = weak;
|
||||
}
|
||||
|
||||
/// Get the server's configured support contacts.
|
||||
pub async fn get_support_contacts(&self) -> Vec<Contact> {
|
||||
let email_address = self.services.config.well_known.support_email.clone();
|
||||
let matrix_id = self.services.config.well_known.support_mxid.clone();
|
||||
let pgp_key = self.services.config.well_known.support_pgp_key.clone();
|
||||
|
||||
// TODO: support defining multiple contacts in the config
|
||||
let mut contacts: Vec<Contact> = vec![];
|
||||
|
||||
let role = self
|
||||
.services
|
||||
.config
|
||||
.well_known
|
||||
.support_role
|
||||
.clone()
|
||||
.unwrap_or(ContactRole::Admin);
|
||||
|
||||
// Add configured contact if at least one contact method is specified
|
||||
let configured_contact = match (matrix_id, email_address) {
|
||||
| (Some(matrix_id), email_address) =>
|
||||
Some(assign!(Contact::with_matrix_id(role, matrix_id), { email_address })),
|
||||
| (None, Some(email_address)) =>
|
||||
Some(Contact::with_email_address(role, email_address)),
|
||||
| (None, None) => None,
|
||||
};
|
||||
|
||||
if let Some(mut configured_contact) = configured_contact {
|
||||
configured_contact.pgp_key = pgp_key;
|
||||
|
||||
contacts.push(configured_contact);
|
||||
}
|
||||
|
||||
// Try to add admin users as contacts if no contacts are configured
|
||||
if contacts.is_empty() {
|
||||
let admin_users = self.get_admins().await;
|
||||
|
||||
for user_id in &admin_users {
|
||||
if *user_id == self.services.globals.server_user {
|
||||
continue;
|
||||
}
|
||||
|
||||
contacts.push(Contact::with_matrix_id(ContactRole::Admin, user_id.to_owned()));
|
||||
}
|
||||
}
|
||||
|
||||
contacts
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ impl crate::Service for Service {
|
||||
for (id, registration) in appservices {
|
||||
// During startup, resolve any token collisions in favour of appservices
|
||||
// by logging out conflicting user devices
|
||||
if let Some((user_id, device_id, _)) = self
|
||||
if let Ok((user_id, device_id)) = self
|
||||
.services
|
||||
.users
|
||||
.find_from_token(®istration.as_token)
|
||||
@@ -158,7 +158,7 @@ impl Service {
|
||||
.users
|
||||
.find_from_token(®istration.as_token)
|
||||
.await
|
||||
.is_some()
|
||||
.is_ok()
|
||||
{
|
||||
return Err(err!(Request(InvalidParam(
|
||||
"Cannot register appservice: The provided token is already in use by a user \
|
||||
|
||||
@@ -39,7 +39,7 @@ impl crate::Service for Service {
|
||||
let url_preview_user_agent = config
|
||||
.url_preview_user_agent
|
||||
.clone()
|
||||
.unwrap_or_else(|| conduwuit::user_agent_media().to_owned());
|
||||
.unwrap_or_else(|| conduwuit::version::user_agent_media().to_owned());
|
||||
|
||||
Ok(Arc::new(Self {
|
||||
default: base(config)?
|
||||
@@ -149,7 +149,7 @@ fn base(config: &Config) -> Result<reqwest::ClientBuilder> {
|
||||
.timeout(Duration::from_secs(config.request_total_timeout))
|
||||
.pool_idle_timeout(Duration::from_secs(config.request_idle_timeout))
|
||||
.pool_max_idle_per_host(config.request_idle_per_host.into())
|
||||
.user_agent(conduwuit::user_agent())
|
||||
.user_agent(conduwuit::version::user_agent())
|
||||
.redirect(redirect::Policy::limited(6))
|
||||
.danger_accept_invalid_certs(config.allow_invalid_tls_certificates_yes_i_know_what_the_fuck_i_am_doing_with_this_and_i_know_this_is_insecure)
|
||||
.connection_verbose(cfg!(debug_assertions));
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::{
|
||||
use askama::Template;
|
||||
use async_trait::async_trait;
|
||||
use conduwuit::{Result, info, utils::ReadyExt};
|
||||
use futures::StreamExt;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ruma::{UserId, events::room::message::RoomMessageEventContent};
|
||||
|
||||
use crate::{
|
||||
@@ -120,7 +120,7 @@ impl Service {
|
||||
///
|
||||
/// Returns Ok(true) if the specified user was the first user, and Ok(false)
|
||||
/// if they were not.
|
||||
pub async fn empower_first_user(&self, user: &UserId) -> bool {
|
||||
pub async fn empower_first_user(&self, user: &UserId) -> Result<bool> {
|
||||
#[derive(Template)]
|
||||
#[template(path = "welcome.md")]
|
||||
struct WelcomeMessage<'a> {
|
||||
@@ -130,14 +130,10 @@ impl Service {
|
||||
|
||||
// If first run mode isn't active, do nothing.
|
||||
if !self.disable_first_run() {
|
||||
return false;
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
self.services
|
||||
.admin
|
||||
.make_user_admin(user)
|
||||
.await
|
||||
.expect("should have been able to empower the first user");
|
||||
self.services.admin.make_user_admin(user).boxed().await?;
|
||||
|
||||
// Send the welcome message
|
||||
let welcome_message = WelcomeMessage {
|
||||
@@ -150,12 +146,11 @@ impl Service {
|
||||
self.services
|
||||
.admin
|
||||
.send_loud_message(RoomMessageEventContent::text_markdown(welcome_message))
|
||||
.await
|
||||
.expect("should have been able to send welcome message");
|
||||
.await?;
|
||||
|
||||
info!("{user} has been invited to the admin room as the first user.");
|
||||
|
||||
true
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Get the single-use registration token which may be used to create the
|
||||
@@ -186,7 +181,7 @@ impl Service {
|
||||
eprintln!(
|
||||
"Welcome to {} {}!",
|
||||
"Continuwuity".bold().bright_magenta(),
|
||||
conduwuit::version().bold()
|
||||
conduwuit::version::version().bold()
|
||||
);
|
||||
eprintln!();
|
||||
eprintln!(
|
||||
|
||||
@@ -44,7 +44,7 @@ impl crate::Service for Service {
|
||||
db,
|
||||
server: args.server.clone(),
|
||||
bad_event_ratelimiter: Arc::new(SyncRwLock::new(HashMap::new())),
|
||||
admin_alias: OwnedRoomAliasId::try_from(format!("#admins:{}", args.server.name))
|
||||
admin_alias: OwnedRoomAliasId::try_from(format!("#admins:{}", &args.server.name))
|
||||
.expect("#admins:server_name is valid alias name"),
|
||||
server_user: UserId::parse_with_server_name(
|
||||
String::from("conduit"),
|
||||
|
||||
@@ -37,7 +37,7 @@ pub struct PasswordReset<'a> {
|
||||
}
|
||||
|
||||
impl MessageTemplate for PasswordReset<'_> {
|
||||
fn subject(&self) -> String { format!("Password reset request for {}", self.user_id) }
|
||||
fn subject(&self) -> String { format!("Password reset request for {}", &self.user_id) }
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
|
||||
@@ -92,8 +92,8 @@ impl Mailer<'_> {
|
||||
|
||||
let message = MessageBuilder::new()
|
||||
.from(self.sender.clone())
|
||||
.to(recipient.clone())
|
||||
.subject(subject.clone())
|
||||
.to(recipient)
|
||||
.subject(subject)
|
||||
.date_now()
|
||||
.header(ContentType::TEXT_PLAIN)
|
||||
.body(body)
|
||||
@@ -104,8 +104,6 @@ impl Mailer<'_> {
|
||||
.await
|
||||
.map_err(|err: TransportError| err!("Failed to send message: {err}"))?;
|
||||
|
||||
info!(recipient = recipient.to_string(), ?subject, "Email sent");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ pub mod key_backups;
|
||||
pub mod mailer;
|
||||
pub mod media;
|
||||
pub mod moderation;
|
||||
pub mod oauth;
|
||||
pub mod password_reset;
|
||||
pub mod presence;
|
||||
pub mod pusher;
|
||||
pub mod registration_tokens;
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
use std::{collections::BTreeSet, hash::Hash};
|
||||
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[non_exhaustive]
|
||||
pub struct ClientMetadata {
|
||||
#[serde(default)]
|
||||
pub application_type: ApplicationType,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub client_name: Option<String>,
|
||||
|
||||
pub client_uri: Url,
|
||||
|
||||
#[serde(default, deserialize_with = "btreeset_skip_err")]
|
||||
pub grant_types: BTreeSet<GrantType>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub logo_uri: Option<Url>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub policy_uri: Option<Url>,
|
||||
|
||||
#[serde(default)]
|
||||
pub redirect_uris: Vec<Url>,
|
||||
|
||||
#[serde(default, deserialize_with = "btreeset_skip_err")]
|
||||
pub response_types: BTreeSet<ResponseType>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub token_endpoint_auth_method: Option<String>,
|
||||
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub tos_uri: Option<Url>,
|
||||
}
|
||||
|
||||
impl ClientMetadata {
|
||||
pub(super) const ACCEPTABLE_LOCALHOSTS: [&str; 3] = ["localhost", "127.0.0.1", "[::1]"];
|
||||
|
||||
pub(super) fn validate(&self) -> Result<(), &'static str> {
|
||||
let Some(client_domain) = self.client_uri.domain() else {
|
||||
return Err("Client URI must have a domain.");
|
||||
};
|
||||
|
||||
if self.client_uri.scheme() != "https" {
|
||||
return Err("Client URI must be HTTPS.");
|
||||
}
|
||||
|
||||
if !self.client_uri.username().is_empty() || self.client_uri.password().is_some() {
|
||||
return Err("Client URI must not include credentials.");
|
||||
}
|
||||
|
||||
for uri in [&self.logo_uri, &self.policy_uri, &self.tos_uri]
|
||||
.iter()
|
||||
.filter_map(|uri| uri.as_ref())
|
||||
{
|
||||
if uri.scheme() != "https" {
|
||||
return Err("All metadata URIs must be HTTPS.");
|
||||
}
|
||||
|
||||
if !uri.username().is_empty() || uri.password().is_some() {
|
||||
return Err("All metadata URIs must not include credentials.");
|
||||
}
|
||||
|
||||
if !uri
|
||||
.domain()
|
||||
.is_some_and(|domain| is_subdomain(domain, client_domain))
|
||||
{
|
||||
return Err("All metadata URIs must be subdomains of the client URI.");
|
||||
}
|
||||
}
|
||||
|
||||
for uri in &self.redirect_uris {
|
||||
match uri.scheme() {
|
||||
| "https" => {
|
||||
// HTTPS URIs are okay for native and web clients
|
||||
|
||||
if !uri.username().is_empty() || uri.password().is_some() {
|
||||
return Err("HTTPS redirect URIs must not contain credentials.");
|
||||
}
|
||||
},
|
||||
| "http" if self.application_type == ApplicationType::Native => {
|
||||
if uri
|
||||
.host_str()
|
||||
.is_none_or(|host| !Self::ACCEPTABLE_LOCALHOSTS.contains(&host))
|
||||
{
|
||||
return Err("HTTP redirect URIs for native applications must only \
|
||||
refer to localhost.");
|
||||
}
|
||||
|
||||
if uri.port().is_some() {
|
||||
return Err("HTTP redirect URIs for native applications do not need to \
|
||||
specify a port. All ports will be accepted during \
|
||||
authorization.");
|
||||
}
|
||||
},
|
||||
| private_scheme if self.application_type == ApplicationType::Native => {
|
||||
let rdns_client_uri = client_domain.split('.').rev().join(".");
|
||||
|
||||
if !private_scheme.starts_with(&rdns_client_uri) {
|
||||
return Err("Private-use scheme URIs for native applications must \
|
||||
begin with the application's client URI domain in \
|
||||
reverse-DNS notation.");
|
||||
}
|
||||
|
||||
if uri.has_authority() {
|
||||
return Err("Private-use scheme URIs for native applications must not \
|
||||
have an authority.");
|
||||
}
|
||||
},
|
||||
| _ =>
|
||||
return Err("A redirect URI's scheme is not valid for this application type."),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ApplicationType {
|
||||
#[default]
|
||||
Web,
|
||||
Native,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GrantType {
|
||||
AuthorizationCode,
|
||||
RefreshToken,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[non_exhaustive]
|
||||
pub enum ResponseType {
|
||||
Code,
|
||||
}
|
||||
|
||||
/// Deserialize a BTreeSet from a sequence, skipping items which fail to
|
||||
/// deserialize. This is used as a deserialize helper for ClientMetadata to
|
||||
/// ignore unknown enum variants in a few fields.
|
||||
fn btreeset_skip_err<'de, D, V>(de: D) -> Result<BTreeSet<V>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
V: Deserialize<'de> + Hash + Eq + Ord,
|
||||
{
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use serde::de::{SeqAccess, Visitor};
|
||||
|
||||
struct BTreeSetVisitor<V> {
|
||||
item: PhantomData<V>,
|
||||
}
|
||||
|
||||
impl<'de, V> Visitor<'de> for BTreeSetVisitor<V>
|
||||
where
|
||||
V: Deserialize<'de> + Hash + Eq + Ord,
|
||||
{
|
||||
type Value = BTreeSet<V>;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(formatter, "a sequence")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
let mut set = BTreeSet::new();
|
||||
|
||||
while let Some(element) = seq.next_element().transpose() {
|
||||
if let Ok(element) = element {
|
||||
set.insert(element);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(set)
|
||||
}
|
||||
}
|
||||
|
||||
de.deserialize_seq(BTreeSetVisitor { item: PhantomData })
|
||||
}
|
||||
|
||||
fn is_subdomain(subdomain: &str, domain: &str) -> bool {
|
||||
if subdomain == domain {
|
||||
return true;
|
||||
}
|
||||
|
||||
subdomain.ends_with(&format!(".{domain}"))
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
use std::{collections::BTreeSet, fmt::Debug, hash::Hash, mem::discriminant};
|
||||
|
||||
use regex::Regex;
|
||||
use ruma::OwnedDeviceId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use super::client_metadata::ResponseType;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct AuthorizationCodeQuery {
|
||||
pub response_type: ResponseType,
|
||||
pub client_id: String,
|
||||
pub redirect_uri: Url,
|
||||
pub scope: RawScopes,
|
||||
pub state: String,
|
||||
#[serde(default)]
|
||||
pub response_mode: ResponseMode,
|
||||
pub code_challenge: String,
|
||||
pub code_challenge_method: CodeChallengeMethod,
|
||||
#[serde(default)]
|
||||
pub prompt: Option<Prompt>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[non_exhaustive]
|
||||
pub enum ResponseMode {
|
||||
#[default]
|
||||
// default for `code` response type, see https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#:~:text=Client%2E-,For,encoding%2E,-See
|
||||
Query,
|
||||
Fragment,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[non_exhaustive]
|
||||
pub enum CodeChallengeMethod {
|
||||
S256,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[non_exhaustive]
|
||||
pub enum Prompt {
|
||||
Create,
|
||||
#[serde(other)]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialOrd, Ord)]
|
||||
pub enum Scope {
|
||||
Device(OwnedDeviceId),
|
||||
ClientApi,
|
||||
}
|
||||
|
||||
impl PartialEq for Scope {
|
||||
fn eq(&self, other: &Self) -> bool { discriminant(self) == discriminant(other) }
|
||||
}
|
||||
|
||||
impl Eq for Scope {}
|
||||
|
||||
impl Hash for Scope {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { discriminant(self).hash(state); }
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Scope {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let urn = match self {
|
||||
| Self::ClientApi => "urn:matrix:client:api:*".to_owned(),
|
||||
| Self::Device(device_id) => format!("urn:matrix:client:device:{device_id}"),
|
||||
};
|
||||
|
||||
f.write_str(&urn)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct RawScopes(String);
|
||||
|
||||
impl RawScopes {
|
||||
pub fn to_scopes(&self) -> Result<BTreeSet<Scope>, String> {
|
||||
let client_api_token_regex =
|
||||
Regex::new(r"urn:matrix:(client|org.matrix.msc2967.client):api:\*").unwrap();
|
||||
let device_token_regex = Regex::new(
|
||||
r"urn:matrix:(client|org.matrix.msc2967.client):device:([a-zA-Z0-9-._~]{5,})",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut scopes = BTreeSet::new();
|
||||
|
||||
for token in self.0.split(' ') {
|
||||
let scope_was_new = {
|
||||
if client_api_token_regex.is_match(token) {
|
||||
scopes.insert(Scope::ClientApi)
|
||||
} else if let Some(captures) = device_token_regex.captures(token) {
|
||||
scopes.insert(Scope::Device(captures.get(2).unwrap().as_str().into()))
|
||||
} else if token == "openid" {
|
||||
// TODO(unspecced): Element sets this scope but doesn't use it for anything
|
||||
true
|
||||
} else {
|
||||
return Err(format!("Invalid scope: {token}"));
|
||||
}
|
||||
};
|
||||
|
||||
if !scope_was_new {
|
||||
return Err("Scope was specified more than once".to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(scopes)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AuthorizationCodeResponse {
|
||||
pub state: String,
|
||||
pub code: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "grant_type", rename_all = "snake_case")]
|
||||
pub enum TokenRequest {
|
||||
AuthorizationCode {
|
||||
code: String,
|
||||
redirect_uri: Url,
|
||||
client_id: String,
|
||||
code_verifier: String,
|
||||
},
|
||||
RefreshToken {
|
||||
client_id: String,
|
||||
refresh_token: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl TokenRequest {
|
||||
#[must_use]
|
||||
pub fn client_id(&self) -> &str {
|
||||
match self {
|
||||
| Self::AuthorizationCode { client_id, .. }
|
||||
| Self::RefreshToken { client_id, .. } => client_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct TokenResponse {
|
||||
pub access_token: String,
|
||||
pub token_type: TokenType,
|
||||
pub expires_in: u64,
|
||||
pub refresh_token: String,
|
||||
pub scope: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub enum TokenType {
|
||||
Bearer,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RevokeTokenRequest {
|
||||
pub token: String,
|
||||
}
|
||||
@@ -1,503 +0,0 @@
|
||||
use std::{
|
||||
collections::{BTreeSet, HashMap},
|
||||
sync::{Arc, Mutex},
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use base64::Engine;
|
||||
use conduwuit::{
|
||||
Err, Result, err, info,
|
||||
utils::{self, hash::sha256},
|
||||
};
|
||||
use database::{Deserialized, Json, Map};
|
||||
use itertools::Itertools;
|
||||
use ruma::{DeviceId, OwnedDeviceId, OwnedUserId, UserId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
Dep,
|
||||
oauth::{
|
||||
client_metadata::{ApplicationType, ClientMetadata, ResponseType},
|
||||
grant::{
|
||||
AuthorizationCodeQuery, AuthorizationCodeResponse, CodeChallengeMethod, ResponseMode,
|
||||
Scope, TokenRequest, TokenResponse, TokenType,
|
||||
},
|
||||
},
|
||||
users,
|
||||
};
|
||||
|
||||
pub mod client_metadata;
|
||||
pub mod grant;
|
||||
|
||||
pub struct Service {
|
||||
services: Services,
|
||||
db: Data,
|
||||
tickets: Mutex<HashMap<String, HashMap<OAuthTicket, SystemTime>>>,
|
||||
pending_code_grants: tokio::sync::Mutex<HashMap<String, PendingCodeGrant>>,
|
||||
}
|
||||
|
||||
struct Data {
|
||||
clientid_clientmetadata: Arc<Map>,
|
||||
userdeviceid_oauthsessioninfo: Arc<Map>,
|
||||
refreshtoken_refreshtokeninfo: Arc<Map>,
|
||||
}
|
||||
|
||||
struct Services {
|
||||
users: Dep<users::Service>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct SessionInfo {
|
||||
pub client_id: String,
|
||||
pub scopes: BTreeSet<Scope>,
|
||||
current_refresh_token: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct RefreshTokenInfo {
|
||||
client_id: String,
|
||||
user_id: OwnedUserId,
|
||||
device_id: OwnedDeviceId,
|
||||
}
|
||||
|
||||
struct PendingCodeGrant {
|
||||
authorizing_user: OwnedUserId,
|
||||
requested_scopes: BTreeSet<Scope>,
|
||||
client_name: Option<String>,
|
||||
expected_client_id: String,
|
||||
expected_redirect_uri: Url,
|
||||
code_challenge: String,
|
||||
requested_at: SystemTime,
|
||||
}
|
||||
|
||||
impl PendingCodeGrant {
|
||||
const MAX_AGE: Duration = Duration::from_mins(1);
|
||||
const RANDOM_CODE_LENGTH: usize = 32;
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn generate_code() -> String { utils::random_string(Self::RANDOM_CODE_LENGTH) }
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn is_valid_for(&self, client_id: &str) -> bool {
|
||||
let now = SystemTime::now();
|
||||
|
||||
self.expected_client_id == client_id
|
||||
&& now
|
||||
.duration_since(self.requested_at)
|
||||
.is_ok_and(|age| age < Self::MAX_AGE)
|
||||
}
|
||||
}
|
||||
|
||||
/// A time-limited grant for a client to perform some sensitive action.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum OAuthTicket {
|
||||
CrossSigningReset,
|
||||
}
|
||||
|
||||
impl OAuthTicket {
|
||||
const MAX_AGE: Duration = Duration::from_mins(10);
|
||||
|
||||
#[must_use]
|
||||
pub fn ticket_issue_path(&self) -> &'static str {
|
||||
match self {
|
||||
| Self::CrossSigningReset => "/account/cross_signing_reset",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Service for Service {
|
||||
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
|
||||
Ok(Arc::new(Self {
|
||||
services: Services {
|
||||
users: args.depend::<users::Service>("users"),
|
||||
},
|
||||
db: Data {
|
||||
clientid_clientmetadata: args.db["clientid_clientmetadata"].clone(),
|
||||
userdeviceid_oauthsessioninfo: args.db["userdeviceid_oauthsessioninfo"].clone(),
|
||||
refreshtoken_refreshtokeninfo: args.db["refreshtoken_refreshtokeninfo"].clone(),
|
||||
},
|
||||
tickets: Mutex::default(),
|
||||
pending_code_grants: tokio::sync::Mutex::default(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
|
||||
}
|
||||
|
||||
impl Service {
|
||||
const ACCESS_TOKEN_MAX_AGE: Duration = Duration::from_hours(1);
|
||||
const RANDOM_TOKEN_LENGTH: usize = 32;
|
||||
|
||||
fn generate_token() -> String { utils::random_string(Self::RANDOM_TOKEN_LENGTH) }
|
||||
|
||||
pub async fn register_client(
|
||||
&self,
|
||||
metadata: &ClientMetadata,
|
||||
) -> Result<String, &'static str> {
|
||||
metadata.validate()?;
|
||||
|
||||
let client_id = base64::prelude::BASE64_STANDARD
|
||||
.encode(sha256::hash(serde_json::to_string(metadata).unwrap().as_bytes()));
|
||||
|
||||
if self
|
||||
.db
|
||||
.clientid_clientmetadata
|
||||
.exists(&client_id)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
self.db
|
||||
.clientid_clientmetadata
|
||||
.raw_put(&client_id, Json(metadata.clone()));
|
||||
}
|
||||
|
||||
Ok(client_id)
|
||||
}
|
||||
|
||||
pub async fn get_client_metadata(&self, client_id: &str) -> Option<ClientMetadata> {
|
||||
self.db
|
||||
.clientid_clientmetadata
|
||||
.get(client_id)
|
||||
.await
|
||||
.deserialized()
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub async fn get_session_info_for_device(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
device_id: &DeviceId,
|
||||
) -> Option<SessionInfo> {
|
||||
self.db
|
||||
.userdeviceid_oauthsessioninfo
|
||||
.qry(&(user_id, device_id))
|
||||
.await
|
||||
.deserialized::<SessionInfo>()
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub async fn request_authorization_code(
|
||||
&self,
|
||||
authorizing_user: OwnedUserId,
|
||||
query: AuthorizationCodeQuery,
|
||||
) -> Result<String, String> {
|
||||
let Some(client_metadata) = self.get_client_metadata(&query.client_id).await else {
|
||||
return Err("Invalid client ID".to_owned());
|
||||
};
|
||||
|
||||
if !(client_metadata
|
||||
.response_types
|
||||
.contains(&query.response_type)
|
||||
&& matches!(query.response_type, ResponseType::Code))
|
||||
{
|
||||
return Err("Invalid response type".to_owned());
|
||||
}
|
||||
|
||||
if !matches!(query.code_challenge_method, CodeChallengeMethod::S256) {
|
||||
return Err("Invalid code challenge type".to_owned());
|
||||
}
|
||||
|
||||
{
|
||||
let mut stripped_uri = query.redirect_uri.clone();
|
||||
|
||||
if client_metadata.application_type == ApplicationType::Native
|
||||
&& query
|
||||
.redirect_uri
|
||||
.host_str()
|
||||
.is_some_and(|host| ClientMetadata::ACCEPTABLE_LOCALHOSTS.contains(&host))
|
||||
{
|
||||
// Remove the port from localhost redirect URIs for native applications when
|
||||
// checking if it's valid
|
||||
stripped_uri.set_port(None).unwrap();
|
||||
}
|
||||
|
||||
if !client_metadata.redirect_uris.contains(&stripped_uri) {
|
||||
return Err("Invalid redirect URI".to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
let requested_scopes = query.scope.to_scopes()?;
|
||||
|
||||
let redirect_uri_query_separator = match query.response_mode {
|
||||
| ResponseMode::Fragment => '#',
|
||||
| ResponseMode::Query => '?',
|
||||
};
|
||||
|
||||
let code = PendingCodeGrant::generate_code();
|
||||
|
||||
info!(
|
||||
client_id = &query.client_id,
|
||||
client_name = &client_metadata.client_name,
|
||||
?requested_scopes,
|
||||
?authorizing_user,
|
||||
"Issuing oauth authorization code"
|
||||
);
|
||||
|
||||
let redirect_uri = format!(
|
||||
"{}{}{}",
|
||||
query.redirect_uri,
|
||||
redirect_uri_query_separator,
|
||||
serde_urlencoded::to_string(AuthorizationCodeResponse {
|
||||
state: query.state,
|
||||
code: code.clone(),
|
||||
})
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let pending_grant = PendingCodeGrant {
|
||||
authorizing_user,
|
||||
requested_scopes,
|
||||
client_name: client_metadata.client_name,
|
||||
expected_client_id: query.client_id,
|
||||
expected_redirect_uri: query.redirect_uri,
|
||||
code_challenge: query.code_challenge,
|
||||
requested_at: SystemTime::now(),
|
||||
};
|
||||
|
||||
self.pending_code_grants
|
||||
.lock()
|
||||
.await
|
||||
.insert(code, pending_grant);
|
||||
|
||||
Ok(redirect_uri)
|
||||
}
|
||||
|
||||
pub async fn issue_token(&self, request: TokenRequest) -> Result<TokenResponse> {
|
||||
match request {
|
||||
| TokenRequest::AuthorizationCode {
|
||||
code,
|
||||
redirect_uri,
|
||||
client_id,
|
||||
code_verifier,
|
||||
} => {
|
||||
let mut pending_grants = self.pending_code_grants.lock().await;
|
||||
|
||||
let Some(pending_grant) = pending_grants
|
||||
.remove(&code)
|
||||
.filter(|grant| grant.is_valid_for(&client_id))
|
||||
else {
|
||||
return Err!("Invalid code");
|
||||
};
|
||||
|
||||
if redirect_uri != pending_grant.expected_redirect_uri {
|
||||
return Err!("Unexpected redirect uri");
|
||||
}
|
||||
|
||||
let expected_code_challenge =
|
||||
base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(sha256::hash(&code_verifier));
|
||||
if expected_code_challenge != pending_grant.code_challenge {
|
||||
return Err!("Invalid code challenge");
|
||||
}
|
||||
|
||||
self.create_session(
|
||||
pending_grant.authorizing_user,
|
||||
pending_grant.requested_scopes,
|
||||
pending_grant.client_name,
|
||||
client_id,
|
||||
)
|
||||
.await
|
||||
},
|
||||
| TokenRequest::RefreshToken { client_id, refresh_token } =>
|
||||
self.refresh_session(client_id, refresh_token).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn revoke_token(&self, token: String) -> Result<()> {
|
||||
let (user_id, device_id) = if let Ok(refresh_token_info) = self
|
||||
.db
|
||||
.refreshtoken_refreshtokeninfo
|
||||
.get(&token)
|
||||
.await
|
||||
.deserialized::<RefreshTokenInfo>()
|
||||
{
|
||||
(refresh_token_info.user_id, refresh_token_info.device_id)
|
||||
} else if let Some((user_id, device_id, _)) =
|
||||
self.services.users.find_from_token(&token).await
|
||||
{
|
||||
(user_id, device_id)
|
||||
} else {
|
||||
return Err!("Invalid token");
|
||||
};
|
||||
|
||||
// This will also call [`Self::remove_session`]
|
||||
self.services
|
||||
.users
|
||||
.remove_device(&user_id, &device_id)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_session(
|
||||
&self,
|
||||
authorizing_user: OwnedUserId,
|
||||
requested_scopes: BTreeSet<Scope>,
|
||||
client_name: Option<String>,
|
||||
client_id: String,
|
||||
) -> Result<TokenResponse> {
|
||||
let access_token = Self::generate_token();
|
||||
let refresh_token = Self::generate_token();
|
||||
|
||||
let device_id = requested_scopes
|
||||
.iter()
|
||||
.find_map(|scope| {
|
||||
if let Scope::Device(device_id) = scope {
|
||||
Some(device_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok_or_else(|| err!("No device ID scope supplied"))?;
|
||||
|
||||
self.services
|
||||
.users
|
||||
.create_device(
|
||||
&authorizing_user,
|
||||
device_id,
|
||||
&access_token,
|
||||
Some(Self::ACCESS_TOKEN_MAX_AGE),
|
||||
client_name,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.db.userdeviceid_oauthsessioninfo.put(
|
||||
(&authorizing_user, device_id),
|
||||
Json(SessionInfo {
|
||||
client_id: client_id.clone(),
|
||||
current_refresh_token: refresh_token.clone(),
|
||||
scopes: requested_scopes.clone(),
|
||||
}),
|
||||
);
|
||||
|
||||
self.db.refreshtoken_refreshtokeninfo.raw_put(
|
||||
&refresh_token,
|
||||
Json(RefreshTokenInfo {
|
||||
client_id: client_id.clone(),
|
||||
user_id: authorizing_user.clone(),
|
||||
device_id: device_id.to_owned(),
|
||||
}),
|
||||
);
|
||||
|
||||
info!(
|
||||
?client_id,
|
||||
?authorizing_user,
|
||||
?device_id,
|
||||
?requested_scopes,
|
||||
"Created new oauth session"
|
||||
);
|
||||
|
||||
Ok(TokenResponse {
|
||||
access_token,
|
||||
token_type: TokenType::Bearer,
|
||||
expires_in: Self::ACCESS_TOKEN_MAX_AGE.as_secs(),
|
||||
scope: requested_scopes.iter().join(" "),
|
||||
refresh_token,
|
||||
})
|
||||
}
|
||||
|
||||
async fn refresh_session(
|
||||
&self,
|
||||
client_id: String,
|
||||
refresh_token: String,
|
||||
) -> Result<TokenResponse> {
|
||||
let Some(refresh_token_info) = self
|
||||
.db
|
||||
.refreshtoken_refreshtokeninfo
|
||||
.get(&refresh_token)
|
||||
.await
|
||||
.deserialized::<RefreshTokenInfo>()
|
||||
.ok()
|
||||
else {
|
||||
return Err!("Invalid refresh token");
|
||||
};
|
||||
|
||||
assert_eq!(&client_id, &refresh_token_info.client_id, "refresh token client id mismatch");
|
||||
|
||||
let mut session_info = self
|
||||
.get_session_info_for_device(
|
||||
&refresh_token_info.user_id,
|
||||
&refresh_token_info.device_id,
|
||||
)
|
||||
.await
|
||||
.expect("session info should exist");
|
||||
|
||||
assert_eq!(&client_id, &session_info.client_id, "session info client id mismatch");
|
||||
|
||||
let new_access_token = Self::generate_token();
|
||||
let new_refresh_token = Self::generate_token();
|
||||
let scope = session_info.scopes.iter().join(" ");
|
||||
session_info
|
||||
.current_refresh_token
|
||||
.clone_from(&new_refresh_token);
|
||||
|
||||
self.services
|
||||
.users
|
||||
.set_token(
|
||||
&refresh_token_info.user_id,
|
||||
&refresh_token_info.device_id,
|
||||
&new_access_token,
|
||||
Some(Self::ACCESS_TOKEN_MAX_AGE),
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.db.userdeviceid_oauthsessioninfo.put(
|
||||
(&refresh_token_info.user_id, &refresh_token_info.device_id),
|
||||
Json(session_info),
|
||||
);
|
||||
|
||||
self.db.refreshtoken_refreshtokeninfo.remove(&refresh_token);
|
||||
drop(refresh_token);
|
||||
self.db
|
||||
.refreshtoken_refreshtokeninfo
|
||||
.raw_put(&new_refresh_token, Json(refresh_token_info));
|
||||
|
||||
Ok(TokenResponse {
|
||||
access_token: new_access_token,
|
||||
token_type: TokenType::Bearer,
|
||||
expires_in: Self::ACCESS_TOKEN_MAX_AGE.as_secs(),
|
||||
scope,
|
||||
refresh_token: new_refresh_token,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn remove_session(&self, user_id: &UserId, device_id: &DeviceId) {
|
||||
let session_info = self.get_session_info_for_device(user_id, device_id).await;
|
||||
|
||||
if let Some(session_info) = session_info {
|
||||
self.db
|
||||
.refreshtoken_refreshtokeninfo
|
||||
.remove(&session_info.current_refresh_token);
|
||||
self.db
|
||||
.userdeviceid_oauthsessioninfo
|
||||
.del((user_id, device_id));
|
||||
info!(?user_id, ?device_id, "Removed OAuth session");
|
||||
}
|
||||
}
|
||||
|
||||
/// Issue a ticket for `localpart` to perform some action.
|
||||
pub fn issue_ticket(&self, localpart: String, ticket: OAuthTicket) {
|
||||
self.tickets
|
||||
.lock()
|
||||
.unwrap()
|
||||
.entry(localpart)
|
||||
.or_default()
|
||||
.insert(ticket, SystemTime::now());
|
||||
}
|
||||
|
||||
/// Try to consume an unexpired ticket for `localpart`.
|
||||
pub fn try_consume_ticket(&self, localpart: &str, ticket: OAuthTicket) -> bool {
|
||||
let now = SystemTime::now();
|
||||
|
||||
self.tickets
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get_mut(localpart)
|
||||
.and_then(|tickets| tickets.remove(&ticket))
|
||||
.is_some_and(|issued| {
|
||||
now.duration_since(issued)
|
||||
.is_ok_and(|duration| duration < OAuthTicket::MAX_AGE)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use conduwuit::utils::{ReadyExt, stream::TryExpect};
|
||||
use database::{Database, Deserialized, Json, Map};
|
||||
use ruma::{OwnedUserId, UserId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub(super) struct Data {
|
||||
passwordresettoken_info: Arc<Map>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ResetTokenInfo {
|
||||
pub user: OwnedUserId,
|
||||
pub issued_at: SystemTime,
|
||||
}
|
||||
|
||||
impl ResetTokenInfo {
|
||||
// one hour
|
||||
const MAX_TOKEN_AGE: Duration = Duration::from_hours(1);
|
||||
|
||||
pub fn is_valid(&self) -> bool {
|
||||
let now = SystemTime::now();
|
||||
|
||||
now.duration_since(self.issued_at)
|
||||
.is_ok_and(|duration| duration < Self::MAX_TOKEN_AGE)
|
||||
}
|
||||
}
|
||||
|
||||
impl Data {
|
||||
pub(super) fn new(db: &Arc<Database>) -> Self {
|
||||
Self {
|
||||
passwordresettoken_info: db["passwordresettoken_info"].clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Associate a reset token with its info in the database.
|
||||
pub(super) fn save_token(&self, token: &str, info: &ResetTokenInfo) {
|
||||
self.passwordresettoken_info.raw_put(token, Json(info));
|
||||
}
|
||||
|
||||
/// Lookup the info for a reset token.
|
||||
pub(super) async fn lookup_token_info(&self, token: &str) -> Option<ResetTokenInfo> {
|
||||
self.passwordresettoken_info
|
||||
.get(token)
|
||||
.await
|
||||
.deserialized()
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Find a user's existing reset token, if any.
|
||||
pub(super) async fn find_token_for_user(
|
||||
&self,
|
||||
user: &UserId,
|
||||
) -> Option<(String, ResetTokenInfo)> {
|
||||
self.passwordresettoken_info
|
||||
.stream::<'_, String, ResetTokenInfo>()
|
||||
.expect_ok()
|
||||
.ready_find(|(_, info)| info.user == user)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Remove a reset token.
|
||||
pub(super) fn remove_token(&self, token: &str) { self.passwordresettoken_info.remove(token); }
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
mod data;
|
||||
|
||||
use std::{sync::Arc, time::SystemTime};
|
||||
|
||||
use conduwuit::{Err, Result, utils};
|
||||
use data::{Data, ResetTokenInfo};
|
||||
use ruma::OwnedUserId;
|
||||
|
||||
use crate::{
|
||||
Dep, globals,
|
||||
users::{self, HashedPassword},
|
||||
};
|
||||
|
||||
pub const PASSWORD_RESET_PATH: &str = "/_continuwuity/account/reset_password";
|
||||
pub const RESET_TOKEN_QUERY_PARAM: &str = "token";
|
||||
const RESET_TOKEN_LENGTH: usize = 32;
|
||||
|
||||
pub struct Service {
|
||||
db: Data,
|
||||
services: Services,
|
||||
}
|
||||
|
||||
struct Services {
|
||||
users: Dep<users::Service>,
|
||||
globals: Dep<globals::Service>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ValidResetToken {
|
||||
pub token: String,
|
||||
pub info: ResetTokenInfo,
|
||||
}
|
||||
|
||||
impl crate::Service for Service {
|
||||
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
|
||||
Ok(Arc::new(Self {
|
||||
db: Data::new(args.db),
|
||||
services: Services {
|
||||
users: args.depend::<users::Service>("users"),
|
||||
globals: args.depend::<globals::Service>("globals"),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
|
||||
}
|
||||
|
||||
impl Service {
|
||||
/// Generate a random string suitable to be used as a password reset token.
|
||||
#[must_use]
|
||||
pub fn generate_token_string() -> String { utils::random_string(RESET_TOKEN_LENGTH) }
|
||||
|
||||
/// Issue a password reset token for `user`, who must be a local user with
|
||||
/// the `password` origin.
|
||||
pub async fn issue_token(&self, user_id: OwnedUserId) -> Result<ValidResetToken> {
|
||||
if !self.services.globals.user_is_local(&user_id) {
|
||||
return Err!("Cannot issue a password reset token for remote user {user_id}");
|
||||
}
|
||||
|
||||
if user_id == self.services.globals.server_user {
|
||||
return Err!("Cannot issue a password reset token for the server user");
|
||||
}
|
||||
|
||||
if self.services.users.is_deactivated(&user_id).await? {
|
||||
return Err!("Cannot issue a password reset token for deactivated user {user_id}");
|
||||
}
|
||||
|
||||
if let Some((existing_token, _)) = self.db.find_token_for_user(&user_id).await {
|
||||
self.db.remove_token(&existing_token);
|
||||
}
|
||||
|
||||
let token = Self::generate_token_string();
|
||||
let info = ResetTokenInfo {
|
||||
user: user_id,
|
||||
issued_at: SystemTime::now(),
|
||||
};
|
||||
|
||||
self.db.save_token(&token, &info);
|
||||
|
||||
Ok(ValidResetToken { token, info })
|
||||
}
|
||||
|
||||
/// Check if `token` represents a valid, non-expired password reset token.
|
||||
pub async fn check_token(&self, token: &str) -> Option<ValidResetToken> {
|
||||
self.db.lookup_token_info(token).await.and_then(|info| {
|
||||
if info.is_valid() {
|
||||
Some(ValidResetToken { token: token.to_owned(), info })
|
||||
} else {
|
||||
self.db.remove_token(token);
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Consume the supplied valid token, using it to change its user's password
|
||||
/// to `new_password`.
|
||||
pub async fn consume_token(
|
||||
&self,
|
||||
ValidResetToken { token, info }: ValidResetToken,
|
||||
new_password: &str,
|
||||
) -> Result<()> {
|
||||
if info.is_valid() {
|
||||
self.db.remove_token(&token);
|
||||
self.services
|
||||
.users
|
||||
.set_password(&info.user, Some(HashedPassword::new(new_password)?));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user