mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-08-07 02:49:45 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e106e50ed0 | ||
|
|
00f11a9e8f | ||
|
|
bf48c7b1b4 | ||
|
|
8a8a5d598e | ||
|
|
fa84a7e6c2 | ||
|
|
2bdeca0ed3 | ||
|
|
c337ea1864 | ||
|
|
b00361d274 | ||
|
|
fb17aa19bf | ||
|
|
fc28e8e1dd | ||
|
|
cea25c6580 | ||
|
|
a0dc37e024 | ||
|
|
003257693b | ||
|
|
bc898efcce | ||
|
|
fa8ad1d629 | ||
|
|
44b60050b3 | ||
|
|
4b84e7c31a | ||
|
|
4237f21903 | ||
|
|
4ac61fd87b | ||
|
|
d217927000 | ||
|
|
4b9b85f671 | ||
|
|
fa3b72947a | ||
|
|
5d963abda6 | ||
|
|
a91380ac17 | ||
|
|
1664a2c225 | ||
|
|
0d7eeb7766 | ||
|
|
16447f7383 | ||
|
|
3612b04890 | ||
|
|
e6f1b98d6a | ||
|
|
6742e1d206 | ||
|
|
dc0976a2e5 | ||
|
|
f3f23ee7ee | ||
|
|
0b15ab2006 | ||
|
|
bd0c3e33e2 | ||
|
|
1bb16c8b73 | ||
|
|
59b62b1453 | ||
|
|
2e0b156de0 | ||
|
|
3593b8ebf7 | ||
|
|
80015a6a30 | ||
|
|
95e49d3cb8 | ||
|
|
25164e030d | ||
|
|
f04924d6be | ||
|
|
df7cd58618 | ||
|
|
bf12e05dae | ||
|
|
e53366a492 | ||
|
|
ce25d79e66 | ||
|
|
71ef093edc | ||
|
|
066097a83d | ||
|
|
1c24ed09fa | ||
|
|
d3dbe09380 | ||
|
|
e9d0baa32e | ||
|
|
7dd10ebc6c | ||
|
|
29ac62ec27 | ||
|
|
8c8985e8f2 | ||
|
|
730920b571 | ||
|
|
66334bdd53 | ||
|
|
6b7bf80de5 | ||
|
|
4c35bfbda4 | ||
|
|
ba51748ad3 | ||
|
|
63dfe8f7e3 | ||
|
|
c6836e51b2 | ||
|
|
33a9fce828 | ||
|
|
267fd4de5b | ||
|
|
ce30f83052 | ||
|
|
e09a2c0e0f | ||
|
|
992abfab8f | ||
|
|
51931de94c | ||
|
|
85a84f93c7 | ||
|
|
e58ef326d4 | ||
|
|
0d439188e8 | ||
|
|
22ef704ac6 | ||
|
|
7bdd4065c8 | ||
|
|
0fcb072239 | ||
|
|
05bb1f4ac7 | ||
|
|
89e3a8c182 | ||
|
|
f0576f8d7d | ||
|
|
11f77defc7 | ||
|
|
23c75855ba | ||
|
|
5253a8d36a | ||
|
|
c9adee86f5 | ||
|
|
3c47516c85 | ||
|
|
299d3230a1 | ||
|
|
9ea8fbc482 | ||
|
|
f7877d122b | ||
|
|
17119670c0 | ||
|
|
535fcab4b5 | ||
|
|
cbe34affa3 | ||
|
|
ed0b3c764e | ||
|
|
190269f616 | ||
|
|
3ea8c82ef0 | ||
|
|
2a627baa96 | ||
|
|
50de6277b8 | ||
|
|
4429323e11 | ||
|
|
18f8d6c65c | ||
|
|
aac49b09c7 | ||
|
|
c8d35cca57 | ||
|
|
cca0f20148 | ||
|
|
23d188f612 | ||
|
|
ae707ab465 | ||
|
|
8244d78cb2 | ||
|
|
b0315da3d7 | ||
|
|
eca01cfb3f | ||
|
|
cf13f5afc1 | ||
|
|
c944f1124a | ||
|
|
70fc95e800 | ||
|
|
507bd1edc7 | ||
|
|
ee0e594aef | ||
|
|
fd080914c2 | ||
|
|
9dd2b28605 | ||
|
|
34abe1cce4 | ||
|
|
a340e6786d | ||
|
|
c37ee97351 | ||
|
|
7d2d42542c | ||
|
|
fd0082fd2c | ||
|
|
ca8f3ae057 | ||
|
|
8a748e02df | ||
|
|
79ee7838e7 | ||
|
|
e9a441fb6e | ||
|
|
93f5557232 | ||
|
|
3d88767977 | ||
|
|
b68cff3b2f | ||
|
|
36c3845ff6 | ||
|
|
7f1c7b964e | ||
|
|
1cbcd3fc88 | ||
|
|
ad9ddd91cd | ||
|
|
a2b4c07cf7 | ||
|
|
2ead282bec | ||
|
|
9496d05cd2 | ||
|
|
c8ab1713c3 | ||
|
|
84acdccb93 | ||
|
|
6164f62395 | ||
|
|
b36ad9cfef | ||
|
|
d557f0f456 | ||
|
|
d858c5c1f4 | ||
|
|
972201da60 | ||
|
|
56d9c9a8b5 | ||
|
|
84ce3e0d0e | ||
|
|
578cf9d2d7 | ||
|
|
ca69af97c1 | ||
|
|
ac26193ffe | ||
|
|
a84e559640 | ||
|
|
2e559a0d3e | ||
|
|
2634fed2d4 | ||
|
|
31a3676cd8 | ||
|
|
aa1609d611 | ||
|
|
8e13f07d2a |
+15
-15
@@ -43,7 +43,7 @@ jobs:
|
||||
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
|
||||
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
|
||||
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
|
||||
&& contains(fromJSON(inputs.machines), fromJSON('["X64"]')[0])
|
||||
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
|
||||
|
||||
name: Format
|
||||
uses: ./.github/workflows/bake.yml
|
||||
@@ -54,9 +54,9 @@ jobs:
|
||||
rust_toolchains: '["nightly"]'
|
||||
rust_targets: ${{inputs.rust_targets}}
|
||||
sys_names: ${{inputs.sys_names}}
|
||||
sys_targets: ${{inputs.sys_targets}}
|
||||
sys_versions: ${{inputs.sys_versions}}
|
||||
machines: '["X64"]'
|
||||
sys_targets: '["x86_64-v1-linux-gnu"]'
|
||||
machines: ${{inputs.machines}}
|
||||
excludes: ${{inputs.excludes}}
|
||||
includes: ${{inputs.includes}}
|
||||
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
|
||||
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
|
||||
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
|
||||
&& contains(fromJSON(inputs.machines), fromJSON('["X64"]')[0])
|
||||
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
|
||||
|
||||
name: Typos
|
||||
uses: ./.github/workflows/bake.yml
|
||||
@@ -78,8 +78,8 @@ jobs:
|
||||
sys_names: ${{inputs.sys_names}}
|
||||
sys_versions: ${{inputs.sys_versions}}
|
||||
rust_targets: ${{inputs.rust_targets}}
|
||||
sys_targets: ${{inputs.sys_targets}}
|
||||
machines: '["X64"]'
|
||||
sys_targets: '["x86_64-v1-linux-gnu"]'
|
||||
machines: ${{inputs.machines}}
|
||||
excludes: ${{inputs.excludes}}
|
||||
includes: ${{inputs.includes}}
|
||||
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
|
||||
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
|
||||
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
|
||||
&& contains(fromJSON(inputs.machines), fromJSON('["X64"]')[0])
|
||||
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
|
||||
|
||||
name: Audit
|
||||
uses: ./.github/workflows/bake.yml
|
||||
@@ -101,8 +101,8 @@ jobs:
|
||||
sys_names: ${{inputs.sys_names}}
|
||||
sys_versions: ${{inputs.sys_versions}}
|
||||
rust_targets: ${{inputs.rust_targets}}
|
||||
sys_targets: ${{inputs.sys_targets}}
|
||||
machines: '["X64"]'
|
||||
sys_targets: '["x86_64-v1-linux-gnu"]'
|
||||
machines: ${{inputs.machines}}
|
||||
excludes: ${{inputs.excludes}}
|
||||
includes: ${{inputs.includes}}
|
||||
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
|
||||
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
|
||||
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
|
||||
&& contains(fromJSON(inputs.machines), fromJSON('["X64"]')[0])
|
||||
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
|
||||
|
||||
name: Lychee
|
||||
uses: ./.github/workflows/bake.yml
|
||||
@@ -124,8 +124,8 @@ jobs:
|
||||
sys_names: ${{inputs.sys_names}}
|
||||
sys_versions: ${{inputs.sys_versions}}
|
||||
rust_targets: ${{inputs.rust_targets}}
|
||||
sys_targets: ${{inputs.sys_targets}}
|
||||
machines: '["X64"]'
|
||||
sys_targets: '["x86_64-v1-linux-gnu"]'
|
||||
machines: ${{inputs.machines}}
|
||||
excludes: ${{inputs.excludes}}
|
||||
includes: ${{inputs.includes}}
|
||||
|
||||
@@ -135,7 +135,7 @@ jobs:
|
||||
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
|
||||
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
|
||||
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
|
||||
&& contains(fromJSON(inputs.machines), fromJSON('["X64"]')[0])
|
||||
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
|
||||
|
||||
name: Check
|
||||
uses: ./.github/workflows/bake.yml
|
||||
@@ -147,8 +147,8 @@ jobs:
|
||||
sys_names: ${{inputs.sys_names}}
|
||||
sys_versions: ${{inputs.sys_versions}}
|
||||
rust_targets: ${{inputs.rust_targets}}
|
||||
sys_targets: ${{inputs.sys_targets}}
|
||||
machines: '["X64"]'
|
||||
sys_targets: '["x86_64-v1-linux-gnu"]'
|
||||
machines: ${{inputs.machines}}
|
||||
excludes: ${{inputs.excludes}}
|
||||
includes: ${{inputs.includes}}
|
||||
|
||||
|
||||
+76
-31
@@ -26,7 +26,7 @@ jobs:
|
||||
default_sys_names: '["debian"]'
|
||||
default_sys_versions: '["testing-slim"]'
|
||||
default_rust_targets: '["x86_64-unknown-linux-gnu"]'
|
||||
default_sys_targets: '["x86_64-linux-gnu"]'
|
||||
default_sys_targets: '["x86_64-v1-linux-gnu"]'
|
||||
default_machines: '["X64"]'
|
||||
|
||||
outputs:
|
||||
@@ -38,21 +38,28 @@ jobs:
|
||||
sys_targets: ${{vars.SYS_TARGETS || env.default_sys_targets}}
|
||||
sys_versions: ${{vars.SYS_VERSIONS || env.default_sys_versions}}
|
||||
machines: ${{vars.MACHINES || env.default_machines}}
|
||||
package: ${{vars.PACKAGE || !contains(github.ref, 'refs/pull/')}}
|
||||
publish: ${{vars.PUBLISH || !contains(github.ref, 'refs/pull/')}}
|
||||
build_pkgs: ${{vars.BUILD_PKGS || github.ref == 'refs/heads/main' || contains(github.ref, 'tags/v')}}
|
||||
check_pkgs: ${{vars.CHECK_PKGS || 'false'}}
|
||||
complement: ${{vars.COMPLEMENT || 'true'}}
|
||||
complement_runner: 'het'
|
||||
docker_repo: ${{vars.DOCKER_REPO}}
|
||||
release_url: ${{steps.release.outputs.upload_url}}
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
runner: ${{fromJSON(vars.RUNNERS)}}
|
||||
machine: ${{fromJSON(vars.MACHINES)}}
|
||||
exclude: ${{fromJSON(vars.RUNNERS_EXCLUDES)}}
|
||||
runner: ${{fromJSON(vars.RUNNERS || '["het"]')}}
|
||||
machine: ${{fromJSON(vars.MACHINES || '["X64"]')}}
|
||||
exclude: ${{fromJSON(vars.RUNNERS_EXCLUDES || '[]')}}
|
||||
|
||||
steps:
|
||||
- name: Initialize Builder
|
||||
env:
|
||||
runner: ${{matrix.runner}}
|
||||
reserved_space: '{"het": "256GB", "aws": "48GB", "gcp": "160GB"}'
|
||||
max_used_space: '{"het": "448GB", "aws": "64GB", "gcp": "192GB"}'
|
||||
reserved_space: '{"het": "384GB", "aws": "48GB", "gcp": "160GB"}'
|
||||
max_used_space: '{"het": "512GB", "aws": "64GB", "gcp": "192GB"}'
|
||||
run: |
|
||||
set +e
|
||||
docker buildx inspect "${GITHUB_ACTOR}"
|
||||
@@ -134,10 +141,16 @@ jobs:
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "none"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "default"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "rust_toolchain": "nightly"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-linux-gnu"},
|
||||
{"rust_target": "x86_64-unknown-linux-gnu", "sys_target": "aarch64-linux-gnu"},
|
||||
{"sys_target": "x86_64-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "aarch64-linux-gnu", "machine": "X64"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v1-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v2-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v3-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v4-linux-gnu"},
|
||||
{"rust_target": "x86_64-unknown-linux-gnu", "sys_target": "aarch64-v8-linux-gnu"},
|
||||
{"sys_target": "aarch64-v8-linux-gnu", "machine": "X64"},
|
||||
{"sys_target": "x86_64-v1-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v2-linux-gnu"},
|
||||
{"sys_target": "x86_64-v3-linux-gnu"},
|
||||
{"sys_target": "x86_64-v4-linux-gnu"},
|
||||
]
|
||||
|
||||
test:
|
||||
@@ -149,7 +162,8 @@ jobs:
|
||||
needs: [init, lint]
|
||||
uses: ./.github/workflows/test.yml
|
||||
with:
|
||||
complement: ${{fromJSON(vars.COMPLEMENT || 'true')}}
|
||||
complement: ${{fromJSON(needs.init.outputs.complement)}}
|
||||
complement_runner: ${{needs.init.outputs.complement_runner}}
|
||||
cargo_profiles: ${{needs.init.outputs.cargo_profiles}}
|
||||
feat_sets: ${{needs.init.outputs.feat_sets}}
|
||||
rust_toolchains: ${{needs.init.outputs.rust_toolchains}}
|
||||
@@ -158,7 +172,6 @@ jobs:
|
||||
sys_targets: ${{needs.init.outputs.sys_targets}}
|
||||
sys_versions: ${{needs.init.outputs.sys_versions}}
|
||||
machines: ${{needs.init.outputs.machines}}
|
||||
complement_runner: 'het'
|
||||
excludes: >
|
||||
[
|
||||
{"cargo_profile": "test", "rust_toolchain": "stable", "feat_set": "none"},
|
||||
@@ -171,21 +184,36 @@ jobs:
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "none"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "default"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "rust_toolchain": "nightly"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-linux-gnu"},
|
||||
{"rust_target": "x86_64-unknown-linux-gnu", "sys_target": "aarch64-linux-gnu"},
|
||||
{"sys_target": "x86_64-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "aarch64-linux-gnu", "machine": "X64"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v1-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v2-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v3-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v4-linux-gnu"},
|
||||
{"rust_target": "x86_64-unknown-linux-gnu", "sys_target": "aarch64-v8-linux-gnu"},
|
||||
{"sys_target": "aarch64-v8-linux-gnu", "machine": "X64"},
|
||||
{"sys_target": "x86_64-v1-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v2-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "none"},
|
||||
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "default"},
|
||||
{"sys_target": "x86_64-v3-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "none"},
|
||||
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "default"},
|
||||
{"sys_target": "x86_64-v4-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "none"},
|
||||
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "default"},
|
||||
]
|
||||
|
||||
package:
|
||||
if: >
|
||||
!failure() && !cancelled()
|
||||
&& needs.init.outputs.package
|
||||
|
||||
name: Package
|
||||
needs: [init, lint]
|
||||
uses: ./.github/workflows/package.yml
|
||||
with:
|
||||
release_url: ${{needs.init.outputs.release_url}}
|
||||
check_pkgs: ${{needs.init.outputs.check_pkgs}}
|
||||
build_pkgs: ${{needs.init.outputs.build_pkgs}}
|
||||
cargo_profiles: ${{needs.init.outputs.cargo_profiles}}
|
||||
feat_sets: ${{needs.init.outputs.feat_sets}}
|
||||
rust_toolchains: ${{needs.init.outputs.rust_toolchains}}
|
||||
@@ -198,33 +226,41 @@ jobs:
|
||||
[
|
||||
{"feat_set": "none"},
|
||||
{"cargo_profile": "test"},
|
||||
{"cargo_profile": "release-native"},
|
||||
{"cargo_profile": "release-debuginfo", "feat_set": "default"},
|
||||
{"cargo_profile": "release-debuginfo", "rust_toolchain": "nightly"},
|
||||
{"cargo_profile": "release-debuginfo", "bake_target": "oci"},
|
||||
{"cargo_profile": "release-debuginfo", "rust_target": "aarch64-unknown-linux-gnu"},
|
||||
{"cargo_profile": "release-native", "feat_set": "default"},
|
||||
{"cargo_profile": "release-native", "rust_toolchain": "stable"},
|
||||
{"cargo_profile": "release-native", "feat_set": "default"},
|
||||
{"cargo_profile": "release-native", "bake_target": "docker"},
|
||||
{"cargo_profile": "release-native", "bake_target": "oci"},
|
||||
{"cargo_profile": "release-native", "rust_target": "aarch64-unknown-linux-gnu"},
|
||||
{"cargo_profile": "release-debuginfo", "sys_target": "x86_64-v2-linux-gnu"},
|
||||
{"cargo_profile": "release-debuginfo", "sys_target": "x86_64-v3-linux-gnu"},
|
||||
{"cargo_profile": "release-debuginfo", "sys_target": "x86_64-v4-linux-gnu"},
|
||||
{"cargo_profile": "release", "rust_toolchain": "nightly"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "default"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-linux-gnu"},
|
||||
{"rust_target": "x86_64-unknown-linux-gnu", "sys_target": "aarch64-linux-gnu"},
|
||||
{"sys_target": "x86_64-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "aarch64-linux-gnu", "machine": "X64"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v1-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v2-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v3-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v4-linux-gnu"},
|
||||
{"rust_target": "x86_64-unknown-linux-gnu", "sys_target": "aarch64-v8-linux-gnu"},
|
||||
{"sys_target": "aarch64-v8-linux-gnu", "machine": "X64"},
|
||||
{"sys_target": "x86_64-v1-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v2-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "default"},
|
||||
{"sys_target": "x86_64-v3-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "default"},
|
||||
{"sys_target": "x86_64-v4-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "default"},
|
||||
]
|
||||
|
||||
publish:
|
||||
if: >
|
||||
!failure() && !cancelled()
|
||||
&& needs.init.outputs.publish
|
||||
|
||||
name: Publish
|
||||
needs: [init, test, package]
|
||||
uses: ./.github/workflows/publish.yml
|
||||
with:
|
||||
docker_repo: ${{vars.DOCKER_REPO}}
|
||||
docker_repo: ${{needs.init.outputs.docker_repo}}
|
||||
release_url: ${{needs.init.outputs.release_url}}
|
||||
cargo_profiles: ${{needs.init.outputs.cargo_profiles}}
|
||||
feat_sets: ${{needs.init.outputs.feat_sets}}
|
||||
@@ -242,10 +278,19 @@ jobs:
|
||||
{"cargo_profile": "release-native"},
|
||||
{"cargo_profile": "release", "rust_toolchain": "nightly"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "default"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-linux-gnu"},
|
||||
{"rust_target": "x86_64-unknown-linux-gnu", "sys_target": "aarch64-linux-gnu"},
|
||||
{"sys_target": "x86_64-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "aarch64-linux-gnu", "machine": "X64"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v1-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v2-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v3-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v4-linux-gnu"},
|
||||
{"rust_target": "x86_64-unknown-linux-gnu", "sys_target": "aarch64-v8-linux-gnu"},
|
||||
{"sys_target": "aarch64-v8-linux-gnu", "machine": "X64"},
|
||||
{"sys_target": "x86_64-v1-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v2-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "default"},
|
||||
{"sys_target": "x86_64-v3-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "default"},
|
||||
{"sys_target": "x86_64-v4-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "default"},
|
||||
]
|
||||
|
||||
secrets:
|
||||
|
||||
@@ -38,6 +38,12 @@ on:
|
||||
release_url:
|
||||
type: string
|
||||
description: For release assets
|
||||
build_pkgs:
|
||||
type: string
|
||||
default: 'true'
|
||||
check_pkgs:
|
||||
type: string
|
||||
default: 'false'
|
||||
|
||||
jobs:
|
||||
book:
|
||||
@@ -46,7 +52,7 @@ jobs:
|
||||
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["release"]')[0])
|
||||
&& contains(fromJSON(inputs.feat_sets), fromJSON('["default"]')[0])
|
||||
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["stable"]')[0])
|
||||
&& contains(fromJSON(inputs.machines), fromJSON('["X64"]')[0])
|
||||
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
|
||||
|
||||
name: Book
|
||||
uses: ./.github/workflows/bake.yml
|
||||
@@ -57,9 +63,9 @@ jobs:
|
||||
rust_toolchains: '["stable"]'
|
||||
rust_targets: ${{inputs.rust_targets}}
|
||||
sys_names: ${{inputs.sys_names}}
|
||||
sys_targets: ${{inputs.sys_targets}}
|
||||
sys_versions: ${{inputs.sys_versions}}
|
||||
machines: '["X64"]'
|
||||
sys_targets: '["x86_64-v1-linux-gnu"]'
|
||||
machines: ${{inputs.machines}}
|
||||
runner: 'het'
|
||||
excludes: ${{inputs.excludes}}
|
||||
includes: ${{inputs.includes}}
|
||||
@@ -144,7 +150,7 @@ jobs:
|
||||
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
|
||||
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["release"]')[0])
|
||||
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["stable"]')[0])
|
||||
&& github.ref == 'refs/heads/main'
|
||||
&& fromJSON(inputs.build_pkgs)
|
||||
|
||||
name: Distro Packages
|
||||
uses: ./.github/workflows/bake.yml
|
||||
@@ -179,7 +185,7 @@ jobs:
|
||||
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
|
||||
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["release"]')[0])
|
||||
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["stable"]')[0])
|
||||
&& github.ref == 'refs/heads/main'
|
||||
&& fromJSON(inputs.check_pkgs)
|
||||
|
||||
name: Check Packages
|
||||
needs: [pkgs]
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
documents:
|
||||
if: >
|
||||
!failure() && !cancelled()
|
||||
&& contains(fromJSON(inputs.machines), fromJSON('["X64"]')[0])
|
||||
&& contains(fromJSON(inputs.sys_targets), fromJSON('["X64"]')[0])
|
||||
&& (github.ref == 'refs/heads/main' || contains(github.ref, 'refs/tags/v'))
|
||||
&& !contains(github.ref, '-draft')
|
||||
|
||||
@@ -70,16 +70,14 @@ jobs:
|
||||
&& inputs.machines
|
||||
&& inputs.docker_repo != ''
|
||||
&& (github.ref == 'refs/heads/main' || contains(github.ref, 'refs/tags/v'))
|
||||
&& !contains(github.ref, '-draft')
|
||||
|
||||
name: Containers
|
||||
runs-on: ${{matrix.machine}}
|
||||
permissions: write-all
|
||||
continue-on-error: false
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
bake_target: ${{fromJSON('["github", "dockerhub"]')}}
|
||||
bake_target: ${{fromJSON('["docker_io", "ghcr_io"]')}}
|
||||
cargo_profile: ${{fromJSON(inputs.cargo_profiles)}}
|
||||
rust_toolchain: ${{fromJSON(inputs.rust_toolchains)}}
|
||||
feat_set: ${{fromJSON(inputs.feat_sets)}}
|
||||
@@ -92,6 +90,10 @@ jobs:
|
||||
include: ${{fromJSON(inputs.includes)}}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: GitHub Login
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -120,8 +122,200 @@ jobs:
|
||||
acct: ${{github.actor}}
|
||||
repo: ${{github.repository}}
|
||||
docker_repo: ${{inputs.docker_repo}}
|
||||
docker_tag_preview: ${{ matrix.cargo_profile == 'release' && matrix.feat_set == 'all' && matrix.sys_target == 'x86_64-linux-gnu' && contains(github.ref, 'refs/tags/v') }}
|
||||
docker_tag_latest: ${{ matrix.cargo_profile == 'release' && matrix.feat_set == 'all' && matrix.sys_target == 'x86_64-linux-gnu' && contains(github.ref, 'refs/tags/v') && !contains(github.ref, '-rc') }}
|
||||
|
||||
run: |
|
||||
docker/bake.sh "${{matrix.bake_target}}"
|
||||
|
||||
bundles:
|
||||
if: >
|
||||
!failure() && !cancelled()
|
||||
&& inputs.machines
|
||||
&& inputs.docker_repo != ''
|
||||
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["release"]')[0])
|
||||
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
|
||||
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["stable"]')[0])
|
||||
&& (github.ref == 'refs/heads/main' || contains(github.ref, 'refs/tags/v'))
|
||||
|
||||
name: Bundles
|
||||
needs: [containers]
|
||||
runs-on: 'het'
|
||||
permissions: write-all
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
bake_target: ${{fromJSON('["docker_io", "ghcr_io"]')}}
|
||||
cargo_profile: ${{fromJSON('["release"]')}}
|
||||
feat_set: ${{fromJSON('["all"]')}}
|
||||
rust_toolchain: ${{fromJSON('["stable"]')}}
|
||||
sys_name: ${{fromJSON(inputs.sys_names)}}
|
||||
sys_version: ${{fromJSON(inputs.sys_versions)}}
|
||||
rust_target: ${{fromJSON(inputs.rust_targets)}}
|
||||
sys_target: ${{fromJSON(inputs.sys_targets)}}
|
||||
machine: ${{fromJSON(inputs.machines)}}
|
||||
exclude: ${{fromJSON(inputs.excludes)}}
|
||||
include: ${{fromJSON(inputs.includes)}}
|
||||
|
||||
steps:
|
||||
- name: GitHub Login
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.ghcr_token }}
|
||||
|
||||
- name: DockerHub Login
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.dockerhub_token }}
|
||||
|
||||
- name: Release Branch
|
||||
if: >
|
||||
!failure() && !cancelled()
|
||||
&& (github.ref == 'refs/heads/main' || contains(github.ref, 'refs/tags/v'))
|
||||
|
||||
env:
|
||||
acct: ${{github.actor}}
|
||||
ghcr_repo: ${{github.repository}}
|
||||
docker_repo: ${{inputs.docker_repo}}
|
||||
registry: ${{matrix.bake_target}}
|
||||
cargo_profile: ${{matrix.cargo_profile}}
|
||||
feat_set: ${{matrix.feat_set}}
|
||||
sys_target: ${{matrix.sys_target}}
|
||||
ref_name: ${{github.ref_name}}
|
||||
|
||||
run: |
|
||||
registry="$(echo -n $registry | sed s/_/./)"
|
||||
if test "$registry" = "ghcr.io"; then
|
||||
repo="$ghcr_repo"
|
||||
else
|
||||
repo="$docker_repo"
|
||||
fi
|
||||
|
||||
docker manifest create "${registry}/${repo}:${ref_name}" \
|
||||
--amend "${registry}/${repo}:${ref_name}-${cargo_profile}-${feat_set}-${sys_target}"
|
||||
|
||||
- name: Release Preview
|
||||
if: >
|
||||
!failure() && !cancelled()
|
||||
&& contains(github.ref, 'refs/tags/v')
|
||||
&& !contains(github.ref, '-draft')
|
||||
|
||||
env:
|
||||
acct: ${{github.actor}}
|
||||
ghcr_repo: ${{github.repository}}
|
||||
docker_repo: ${{inputs.docker_repo}}
|
||||
registry: ${{matrix.bake_target}}
|
||||
cargo_profile: ${{matrix.cargo_profile}}
|
||||
feat_set: ${{matrix.feat_set}}
|
||||
sys_target: ${{matrix.sys_target}}
|
||||
ref_name: ${{github.ref_name}}
|
||||
|
||||
run: |
|
||||
registry="$(echo -n $registry | sed s/_/./)"
|
||||
if test "$registry" = "ghcr.io"; then
|
||||
repo="$ghcr_repo"
|
||||
else
|
||||
repo="$docker_repo"
|
||||
fi
|
||||
|
||||
docker manifest create "${registry}/${repo}:preview" \
|
||||
--amend "${registry}/${repo}:${ref_name}-${cargo_profile}-${feat_set}-${sys_target}"
|
||||
|
||||
- name: Release Latest
|
||||
if: >
|
||||
!failure() && !cancelled()
|
||||
&& contains(github.ref, 'refs/tags/v')
|
||||
&& !contains(github.ref, '-draft')
|
||||
&& !contains(github.ref, '-rc')
|
||||
|
||||
env:
|
||||
acct: ${{github.actor}}
|
||||
ghcr_repo: ${{github.repository}}
|
||||
docker_repo: ${{inputs.docker_repo}}
|
||||
registry: ${{matrix.bake_target}}
|
||||
cargo_profile: ${{matrix.cargo_profile}}
|
||||
feat_set: ${{matrix.feat_set}}
|
||||
sys_target: ${{matrix.sys_target}}
|
||||
ref_name: ${{github.ref_name}}
|
||||
|
||||
run: |
|
||||
registry="$(echo -n $registry | sed s/_/./)"
|
||||
if test "$registry" = "ghcr.io"; then
|
||||
repo="$ghcr_repo"
|
||||
else
|
||||
repo="$docker_repo"
|
||||
fi
|
||||
|
||||
docker manifest create "${registry}/${repo}:latest" \
|
||||
--amend "${registry}/${repo}:${ref_name}-${cargo_profile}-${feat_set}-${sys_target}"
|
||||
|
||||
delivery:
|
||||
if: >
|
||||
!failure() && !cancelled()
|
||||
&& inputs.machines
|
||||
&& inputs.docker_repo != ''
|
||||
&& (github.ref == 'refs/heads/main' || contains(github.ref, 'refs/tags/v'))
|
||||
|
||||
name: Delivery
|
||||
needs: [bundles, documents]
|
||||
runs-on: 'het'
|
||||
permissions: write-all
|
||||
steps:
|
||||
- name: GitHub Login
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.ghcr_token }}
|
||||
|
||||
- name: DockerHub Login
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.dockerhub_token }}
|
||||
|
||||
- name: Push Branch
|
||||
if: >
|
||||
!failure() && !cancelled()
|
||||
&& (github.ref == 'refs/heads/main' || contains(github.ref, 'refs/tags/v'))
|
||||
|
||||
env:
|
||||
ref_name: ${{github.ref_name}}
|
||||
ghcr_repo: ${{github.repository}}
|
||||
docker_repo: ${{inputs.docker_repo}}
|
||||
|
||||
run: |
|
||||
docker manifest push -p "ghcr.io/${ghcr_repo}:${ref_name}"
|
||||
docker manifest push -p "docker.io/${docker_repo}:${ref_name}"
|
||||
|
||||
- name: Push Preview
|
||||
if: >
|
||||
!failure() && !cancelled()
|
||||
&& contains(github.ref, 'refs/tags/v')
|
||||
&& !contains(github.ref, '-draft')
|
||||
|
||||
env:
|
||||
ghcr_repo: ${{github.repository}}
|
||||
docker_repo: ${{inputs.docker_repo}}
|
||||
|
||||
run: |
|
||||
docker manifest push -p "ghcr.io/${ghcr_repo}:preview"
|
||||
docker manifest push -p "docker.io/${docker_repo}:preview"
|
||||
|
||||
- name: Push Latest
|
||||
if: >
|
||||
!failure() && !cancelled()
|
||||
&& contains(github.ref, 'refs/tags/v')
|
||||
&& !contains(github.ref, '-draft')
|
||||
&& !contains(github.ref, '-rc')
|
||||
|
||||
env:
|
||||
ghcr_repo: ${{github.repository}}
|
||||
docker_repo: ${{inputs.docker_repo}}
|
||||
|
||||
run: |
|
||||
docker manifest push -p "ghcr.io/${ghcr_repo}:latest"
|
||||
docker manifest push -p "docker.io/${docker_repo}:latest"
|
||||
|
||||
+46
-17
@@ -50,7 +50,7 @@ jobs:
|
||||
contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
|
||||
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
|
||||
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
|
||||
&& contains(fromJSON(inputs.machines), fromJSON('["X64"]')[0])
|
||||
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
|
||||
|
||||
name: Unit
|
||||
uses: ./.github/workflows/bake.yml
|
||||
@@ -62,8 +62,8 @@ jobs:
|
||||
sys_names: ${{inputs.sys_names}}
|
||||
sys_versions: ${{inputs.sys_versions}}
|
||||
rust_targets: ${{inputs.rust_targets}}
|
||||
sys_targets: ${{inputs.sys_targets}}
|
||||
machines: '["X64"]'
|
||||
sys_targets: '["x86_64-v1-linux-gnu"]'
|
||||
machines: ${{inputs.machines}}
|
||||
excludes: ${{inputs.excludes}}
|
||||
includes: ${{inputs.includes}}
|
||||
|
||||
@@ -92,6 +92,7 @@ jobs:
|
||||
{"cargo_profile": "test", "feat_set": "none", "bake_target": "smoke-valgrind"},
|
||||
{"cargo_profile": "test", "rust_toolchain": "stable"},
|
||||
{"cargo_profile": "test", "rust_target": "aarch64-unknown-linux-gnu"},
|
||||
{"cargo_profile": "test", "sys_target": "x86_64-v2-linux-gnu", "bake_target": "smoke-valgrind"},
|
||||
{"cargo_profile": "release", "rust_toolchain": "nightly"},
|
||||
{"cargo_profile": "release", "rust_toolchain": "stable", "feat_set": "none"},
|
||||
{"cargo_profile": "release", "bake_target": "smoke-valgrind"},
|
||||
@@ -104,12 +105,29 @@ jobs:
|
||||
{"cargo_profile": "release-native", "feat_set": "default"},
|
||||
{"cargo_profile": "release-native", "bake_target": "smoke-valgrind"},
|
||||
{"cargo_profile": "release-native", "rust_target": "aarch64-unknown-linux-gnu"},
|
||||
{"cargo_profile": "release-native", "sys_target": "x86_64-v2-linux-gnu"},
|
||||
{"cargo_profile": "release-native", "sys_target": "x86_64-v3-linux-gnu"},
|
||||
{"cargo_profile": "release-native", "sys_target": "x86_64-v4-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "none"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "default"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-linux-gnu"},
|
||||
{"rust_target": "x86_64-unknown-linux-gnu", "sys_target": "aarch64-linux-gnu"},
|
||||
{"sys_target": "x86_64-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "aarch64-linux-gnu", "machine": "X64"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v1-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v2-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v3-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v4-linux-gnu"},
|
||||
{"rust_target": "x86_64-unknown-linux-gnu", "sys_target": "aarch64-v8-linux-gnu"},
|
||||
{"sys_target": "aarch64-v8-linux-gnu", "machine": "X64"},
|
||||
{"sys_target": "x86_64-v1-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v2-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "none"},
|
||||
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "default"},
|
||||
{"sys_target": "x86_64-v3-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "none"},
|
||||
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "default"},
|
||||
{"sys_target": "x86_64-v3-linux-gnu", "bake_target": "smoke-valgrind"},
|
||||
{"sys_target": "x86_64-v4-linux-gnu", "machine": "ARM64"},
|
||||
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "none"},
|
||||
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "default"},
|
||||
{"sys_target": "x86_64-v4-linux-gnu", "bake_target": "smoke-valgrind"},
|
||||
]
|
||||
|
||||
complement:
|
||||
@@ -119,7 +137,7 @@ jobs:
|
||||
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
|
||||
&& contains(fromJSON(inputs.complement_feat_sets), fromJSON('["all"]')[0])
|
||||
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
|
||||
&& contains(fromJSON(inputs.machines), fromJSON('["X64"]')[0])
|
||||
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
|
||||
|
||||
name: Complement
|
||||
uses: ./.github/workflows/bake.yml
|
||||
@@ -131,8 +149,8 @@ jobs:
|
||||
sys_names: ${{inputs.sys_names}}
|
||||
sys_versions: ${{inputs.sys_versions}}
|
||||
rust_targets: ${{inputs.rust_targets}}
|
||||
sys_targets: ${{inputs.sys_targets}}
|
||||
machines: '["X64"]'
|
||||
sys_targets: '["x86_64-v1-linux-gnu"]'
|
||||
machines: ${{inputs.machines}}
|
||||
runner: ${{inputs.complement_runner}}
|
||||
excludes: ${{inputs.excludes}}
|
||||
includes: ${{inputs.includes}}
|
||||
@@ -145,7 +163,7 @@ jobs:
|
||||
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
|
||||
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
|
||||
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
|
||||
&& contains(fromJSON(inputs.machines), fromJSON('["X64"]')[0])
|
||||
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
|
||||
|
||||
name: Matrix Compliance
|
||||
needs: [complement]
|
||||
@@ -163,8 +181,8 @@ jobs:
|
||||
sys_name: ${{fromJSON(inputs.sys_names)}}
|
||||
sys_version: ${{fromJSON(inputs.sys_versions)}}
|
||||
rust_target: ${{fromJSON(inputs.rust_targets)}}
|
||||
sys_target: ${{fromJSON(inputs.sys_targets)}}
|
||||
machine: ${{fromJSON('["X64"]')}}
|
||||
sys_target: ${{fromJSON('["x86_64-v1-linux-gnu"]')}}
|
||||
machine: ${{fromJSON(inputs.machines)}}
|
||||
exclude: ${{fromJSON(inputs.excludes)}}
|
||||
include: ${{fromJSON(inputs.includes)}}
|
||||
|
||||
@@ -185,7 +203,8 @@ jobs:
|
||||
run: |
|
||||
docker/complement.sh
|
||||
|
||||
- name: Extract
|
||||
- if: success() || failure() && steps.execute.outcome == 'failure'
|
||||
name: Extract
|
||||
id: extract
|
||||
env:
|
||||
name: complement_tester__${{matrix.sys_name}}__${{matrix.sys_version}}__${{matrix.sys_target}}
|
||||
@@ -195,13 +214,23 @@ jobs:
|
||||
cid=$(cat "$name")
|
||||
docker cp "$cid:/usr/src/complement/new_results.jsonl" tests/test_results/complement/test_results.jsonl
|
||||
|
||||
- name: Upload
|
||||
id: upload
|
||||
- if: success() || failure() && steps.execute.outcome == 'failure'
|
||||
name: Upload New Results
|
||||
id: upload-result
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test_results-${{matrix.feat_set}}-${{matrix.sys_name}}-${{matrix.sys_target}}.jsonl
|
||||
name: complement_results-${{matrix.feat_set}}-${{matrix.sys_name}}-${{matrix.sys_target}}.jsonl
|
||||
path: ./tests/test_results/complement/test_results.jsonl
|
||||
|
||||
- if: failure() && steps.execute.outcome == 'failure'
|
||||
name: Upload Failure Output
|
||||
id: upload-output
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: complement_output-${{matrix.feat_set}}-${{matrix.sys_name}}-${{matrix.sys_target}}.jsonl
|
||||
path: ./complement.jsonl
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Accept
|
||||
id: accept
|
||||
run: |
|
||||
|
||||
@@ -95,3 +95,10 @@ rustc-ice-*
|
||||
|
||||
# complement test logs are huge
|
||||
tests/test_results/complement/test_logs.jsonl
|
||||
|
||||
# cargo profiles from -Z self-profile
|
||||
*.mm_profdata
|
||||
|
||||
# Editor crashfiles
|
||||
*.save
|
||||
*.swp
|
||||
|
||||
Generated
+24
-11
@@ -1977,6 +1977,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-uring"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipaddress"
|
||||
version = "0.1.3"
|
||||
@@ -4523,16 +4534,18 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.45.1"
|
||||
version = "1.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
|
||||
checksum = "1140bb80481756a8cbe10541f37433b459c5aa1e727b4c020fbfebdc25bf3ec4"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"io-uring",
|
||||
"libc",
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"tracing",
|
||||
@@ -4793,7 +4806,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "tuwunel"
|
||||
version = "1.1.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"const-str",
|
||||
@@ -4822,8 +4835,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tuwunel_admin"
|
||||
version = "1.1.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"clap",
|
||||
"const-str",
|
||||
"ctor",
|
||||
@@ -4845,7 +4859,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tuwunel_api"
|
||||
version = "1.1.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
@@ -4878,7 +4892,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tuwunel_core"
|
||||
version = "1.1.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"arrayvec",
|
||||
@@ -4903,7 +4917,6 @@ dependencies = [
|
||||
"ipaddress",
|
||||
"itertools 0.14.0",
|
||||
"jsonwebtoken",
|
||||
"ldap3",
|
||||
"libc",
|
||||
"libloading",
|
||||
"log",
|
||||
@@ -4938,7 +4951,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tuwunel_database"
|
||||
version = "1.1.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"const-str",
|
||||
@@ -4957,7 +4970,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tuwunel_macros"
|
||||
version = "1.1.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"proc-macro2",
|
||||
@@ -4967,7 +4980,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tuwunel_router"
|
||||
version = "1.1.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum-client-ip",
|
||||
@@ -5001,7 +5014,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tuwunel_service"
|
||||
version = "1.1.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64",
|
||||
|
||||
+16
-5
@@ -28,8 +28,8 @@ keywords = [
|
||||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/matrix-construct/tuwunel"
|
||||
rust-version = "1.86.0"
|
||||
version = "1.1.0"
|
||||
rust-version = "1.88.0"
|
||||
version = "1.3.0"
|
||||
|
||||
[workspace.metadata.crane]
|
||||
name = "tuwunel"
|
||||
@@ -362,11 +362,11 @@ git = "https://github.com/matrix-construct/rust-rocksdb"
|
||||
rev = "225a42519276e502205bdc845cebdb22d70ee245"
|
||||
default-features = false
|
||||
features = [
|
||||
"bzip2",
|
||||
"lz4",
|
||||
"multi-threaded-cf",
|
||||
"mt_static",
|
||||
"lz4",
|
||||
"zstd",
|
||||
"bzip2",
|
||||
]
|
||||
|
||||
[workspace.dependencies.sanitize-filename]
|
||||
@@ -487,7 +487,7 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.tokio]
|
||||
version = "1.45.1"
|
||||
version = "1.46.0"
|
||||
default-features = false
|
||||
features = [
|
||||
"fs",
|
||||
@@ -856,6 +856,7 @@ unused-qualifications = "warn"
|
||||
|
||||
## some sadness
|
||||
elided_named_lifetimes = "allow" # TODO!
|
||||
mismatched_lifetime_syntaxes = "allow" # TODO!
|
||||
let_underscore_drop = "allow"
|
||||
missing_docs = "allow"
|
||||
# cfgs cannot be limited to expected cfgs or their de facto non-transitive/opt-in use-case e.g.
|
||||
@@ -871,6 +872,15 @@ variant_size_differences = "allow"
|
||||
# we check nightly clippy lints
|
||||
unknown_lints = "allow"
|
||||
|
||||
# rust claims elided_named_lifetimes has been replaced by
|
||||
# mismatched_lifetime_syntaxes but it actually has not.
|
||||
renamed_and_removed_lints = "allow"
|
||||
|
||||
# nightly has a new lint that conflicts with our BoolExt trait at
|
||||
# src/core/utils/bool.rs:68:48
|
||||
# <https://github.com/rust-lang/rust/issues/142748>
|
||||
unstable_name_collisions = "allow" # TODO
|
||||
|
||||
#######################################
|
||||
#
|
||||
# Clippy lints
|
||||
@@ -910,6 +920,7 @@ enum_glob_use = { level = "allow", priority = 1 }
|
||||
if_not_else = { level = "allow", priority = 1 }
|
||||
if_then_some_else_none = { level = "allow", priority = 1 }
|
||||
inline_always = { level = "allow", priority = 1 }
|
||||
map_unwrap_or = { level = "allow", priority = 1 }
|
||||
match_bool = { level = "allow", priority = 1 }
|
||||
missing_docs_in_private_items = { level = "allow", priority = 1 }
|
||||
missing_errors_doc = { level = "allow", priority = 1 }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Tuwunel <sup>🎔</sup>
|
||||
# Tuwunel<sup>💕</sup>
|
||||
|
||||

|
||||

|
||||
@@ -47,7 +47,7 @@ ### Getting Started
|
||||
This is just a summary for the impatient. See the full
|
||||
[documentation](https://matrix-construct.github.io/tuwunel/).
|
||||
|
||||
> [!WARNING]
|
||||
> [!TIP]
|
||||
> Avoid using a sub-domain for your `server_name`. You can always delegate later with a [`.well-known`](https://github.com/spantaleev/matrix-docker-ansible-deploy/blob/master/docs/configuring-well-known.md)
|
||||
> file, but you can never change your `server_name`.
|
||||
|
||||
@@ -140,31 +140,26 @@ #### Container Tracking
|
||||
|
||||
### Getting Help & Support
|
||||
|
||||
The official community will be found at [#tuwunel:tuwunel.chat](https://matrix.to/#/#tuwunel:tuwunel.chat).
|
||||
If this is currently inaccessible please be patient as it's still coming online at the time of
|
||||
the first release; we will have updates to this section. This is a fully moderated space to protect
|
||||
the wellbeing of our users and create a non-toxic work environment for staff. If you require assistance
|
||||
with anything that is not remedied by the documentation, don't hesitate to open an issue on github.
|
||||
|
||||
If you are opposed to using github, or if private discussion is required such as for security
|
||||
disclosures, or for any other reason, I would be happy to receive your DM at
|
||||
[@jason:tuwunel.me](https://matrix.to/#/@jason:tuwunel.me). This will not be bothering me as it would
|
||||
be my pleasure to help you anytime. As an emergency contact you can send an email to jasonzemos@gmail.com.
|
||||
be my pleasure to help you when possible. As an emergency contact you can send an email to
|
||||
jasonzemos@gmail.com.
|
||||
|
||||
##### Tuwunel Fanclub
|
||||
|
||||
We also have an unofficial community-run chat which is publicly accessible at
|
||||
[#tuwunel:grin.hu](https://matrix.to/#/#tuwunel:grin.hu). The members, content, or moderation
|
||||
We have an unofficial community-run chat which is publicly accessible at
|
||||
[#tuwunel:matrix.org](https://matrix.to/#/#tuwunel:matrix.org). The members, content, or moderation
|
||||
decisions of this room are not in any way related or endorsed by this project or its sponsors,
|
||||
and not all project staff will be present there. There will be at least some presence by staff to
|
||||
offer assistance so long as the room remains in minimally good standing.
|
||||
|
||||
|
||||
## Tuwunel <sup>🎔</sup>
|
||||
## Tuwunel<sup>💕</sup>
|
||||
|
||||
Tuwunel's theme is **empathy** in communication defined by the works of
|
||||
[Edith Stein](https://plato.stanford.edu/entries/stein/). Empathy is the basis for how we approach
|
||||
every message, and a reminder for how we should all conduct ourselves in every conversation.
|
||||
every message and our responsibility to the other in every conversation.
|
||||
|
||||
<!-- ANCHOR_END: body -->
|
||||
|
||||
|
||||
+47
-25
@@ -1,37 +1,59 @@
|
||||
# Tuwunel 1.1.0
|
||||
# Tuwunel 1.3.0
|
||||
|
||||
June 19, 2025
|
||||
August 4, 2025
|
||||
|
||||
All dependencies have been fully upgraded for the first time since the
|
||||
conduwuit transition. RocksDB is now synchronized to 10.2.1-tuwunel for all
|
||||
builders. The Nix build itself has now been fully migrated from conduwuit;
|
||||
special thanks to @wkordalski for making this happen. Thanks to @Askhalion
|
||||
for opening a NixOS package request which you can [vote for here](https://github.com/NixOS/nixpkgs/issues/415469).
|
||||
An [Arch package](https://aur.archlinux.org/packages/tuwunel) has also been
|
||||
created in the AUR courtesy of @drrossum in addition to the transitional
|
||||
package setup by @Kimiblock which we failed to acknowledge during the first
|
||||
release. The RPM package now has systemd and proper installation added thanks
|
||||
to a report by @alythemonk.
|
||||
Preparations for [Project Hydra](https://matrix.org/blog/2025/07/security-predisclosure/) have taken place. Users should be prepared to upgrade to `v1.4.0-rc` next week. Due to the comprehensive low-level changes which have taken place, and the inability to develop them in public with your feedback, the release will undergo an extended `-rc` period. Due to the time difference and scheduling conflicts our release may be published between 12 to 36 hours after the suggested time; though well before the written disclosure of the issues.
|
||||
|
||||
ARMv8 builds are now supported and bundled with this release. Thanks to
|
||||
@zaninime and @clement-escolano for reminding us.
|
||||
Some foundational work for `v1.4.0` was backported into this release after the announcement was made mid-July to further extend Hydra. An unexpected performance improvement drastically reduced CPU usage. As a result, integration tests began to flake, uncovering latent concurrency bugs which were addressed. These fixes primarily impact the legacy and sliding-sync systems, and further fixes improve performance and compliance, though mostly in the legacy system as sliding-sync lacks adequate test coverage.
|
||||
|
||||
This release fixes many bugs and improves performance but at the cost of planned features further rescheduled to either `v1.4.1` or `v1.5.0`.
|
||||
|
||||
JSON Web Token logins are now supported. This feature was commissioned and
|
||||
made public by an enterprise sponsor. The type `org.matrix.login.jwt` is now
|
||||
recognized.
|
||||
|
||||
### New Features
|
||||
|
||||
- JWT login support.
|
||||
- Refresh tokens (MSC2918) have been implemented. Supporting clients can now timeout their access tokens with a soft-logout after a configured `access_token_ttl`. This feature was commissioned and made public by an enterprise sponsor.
|
||||
|
||||
### Follow-up Features
|
||||
- Typing indicators have been added to sliding-sync thanks to @tmayoff. This feature (and the whole of sliding-sync) is still experimental and the indicator may not always appear or disappear as intended, nevertheless the effort will be enhanced by foundational fixes improving sliding-sync requested soon by the project's sponsor.
|
||||
|
||||
|
||||
### Enhancements
|
||||
|
||||
- @dasha_uwu maintains their streak as a serial contributor by patching the `!admin query raw` command with a base64 option allowing for low-level debugging of database records.
|
||||
|
||||
- @obioma has improved the documentation explaining how to use multiple configuration files with precedence.
|
||||
|
||||
- Upon recommendation of @grin a basic request ID has been added to the tracing logs to uniquely distinguish each request while it's interleaved among others.
|
||||
|
||||
- Requested by @fruzitent this and future releases are tagged by version as multi-arch docker images to be properly archived in the registry rather than simply overwriting `:latest`.
|
||||
|
||||
- Event processing performance has been improved by fetching and processing `prev_events` and `auth_events` concurrently. This reduces the impact of recursing large graphs while the room's mutex is locked.
|
||||
|
||||
- An experimental command `!admin debug resync-database` has been added for developers curious about #35
|
||||
|
||||
- aarch64 build and packages.
|
||||
- NixOS build support. (thanks @wkordalski and @coolGi69)
|
||||
- Dependency upgrades, including Axum 0.8. (thanks @dasha_uwu)
|
||||
- RPM package systemd and proper installation scripts.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Changing passwords for pre-migration users was precluded by an error.
|
||||
Special thanks to @teidesu for making a superb report about this.
|
||||
- Thanks to @tmayoff room avatars are properly calculated and no longer the same for all spaces (https://github.com/matrix-construct/tuwunel/pull/102).
|
||||
|
||||
- Courtesy of @coolGi69 our bump to Rust 1.88 was properly updated for Nix. Apologies to the Nix community for getting this wrong the first time.
|
||||
|
||||
- Invite rejections have been fixed, this was due to a misinterpretation of the spec in legacy sync.
|
||||
|
||||
- Knock rooms might have been buggy from the ambiguous overuse of the word "count" in the codebase. Database records which expected a summation of the users in a room instead received the sequence number of the server, both are called "count."
|
||||
|
||||
- Room knocks failed to wakeup the sync systems; some cases of account_data changes also failed to wakeup the sync systems. These have been addressed.
|
||||
|
||||
- The main sequence number fundamental to the entire server's operation (the "count" or counter) has been refactored after having exceeded architectural limitations. It has been replaced by a two-phase counter ensuring read-after-write consistency, and quasi-transactions grouping multiple writes.
|
||||
|
||||
- Sequence issues have been addressed in both legacy and sliding sync. These systems operate using a "snapshot" approach which intentionally ignores new data received by the server after the sync request has started; the snapshot approach replaced the complex of mutexes used by Conduit. The server was not originally designed for this approach and some information "from the future" continued to leak into the snapshot's window; these leaks have been sealed.
|
||||
|
||||
- Protocol compliance issues in legacy sync have been addressed. Additional compliance tests for device list updates now pass. The `state` and `timeline` on incremental sync provide expected results in more (if not all) cases.
|
||||
|
||||
- Errors requiring M_BAD_ALIAS instead of M_UNKNOWN when sending `m.room.canonical_alias` are now conforming.
|
||||
|
||||
|
||||
### Deprecations
|
||||
|
||||
- Unauthenticated media fallbacks are no longer requested by default. This can still be enabled with `request_legacy_media` if desired.
|
||||
|
||||
- Legacy Sliding-Sync has been removed in favor of Simplified Sliding-Sync. Clients which exclusively using Sliding-Sync have already migrated around the start of this year, so this removal should have no impact now.
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
array-size-threshold = 4096
|
||||
cognitive-complexity-threshold = 100 # TODO reduce me ALARA
|
||||
excessive-nesting-threshold = 11 # TODO reduce me to 4 or 5
|
||||
future-size-threshold = 7745 # TODO reduce me ALARA
|
||||
excessive-nesting-threshold = 8
|
||||
future-size-threshold = 8192
|
||||
stack-size-threshold = 196608 # TODO reduce me ALARA
|
||||
too-many-lines-threshold = 780 # TODO reduce me to <= 100
|
||||
type-complexity-threshold = 250 # reduce me to ~200
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# syntax = docker/dockerfile:1.11-labs
|
||||
|
||||
FROM input AS cargo
|
||||
ARG sys_target
|
||||
ARG sys_triple
|
||||
ARG rust_target
|
||||
ARG rust_toolchain
|
||||
ARG RUST_HOME
|
||||
@@ -38,7 +38,7 @@ COPY --link --from=input . .
|
||||
COPY --link --from=rust ${RUST_HOME} ${RUST_HOME}
|
||||
COPY --link --from=deps /usr/src/tuwunel /usr/src/tuwunel
|
||||
|
||||
WORKDIR /usr/lib/${sys_target}
|
||||
WORKDIR /usr/lib/${sys_triple}
|
||||
COPY --link --from=rocksdb . .
|
||||
|
||||
WORKDIR /usr/src/tuwunel
|
||||
@@ -59,9 +59,9 @@ ENV CARGO_TARGET_DIR="${CARGO_TARGET_DIR}"
|
||||
ENV targ_dir="${CARGO_TARGET_DIR}/${cargo_target_profile}"
|
||||
ENV targ_targ_dir="${CARGO_TARGET_DIR}/${rust_target}/${cargo_target_profile}"
|
||||
RUN \
|
||||
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared,ro \
|
||||
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared,ro \
|
||||
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared,ro \
|
||||
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared \
|
||||
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared \
|
||||
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared \
|
||||
--mount=type=cache,dst=${targ_dir}/deps,id=${cargo_target_share}/deps,sharing=locked \
|
||||
--mount=type=cache,dst=${targ_dir}/build,id=${cargo_target_share}/build,sharing=locked \
|
||||
--mount=type=cache,dst=${targ_dir}/examples,id=${cargo_target_share}/examples,sharing=locked \
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.11-labs
|
||||
|
||||
FROM input AS audit
|
||||
ARG sys_target
|
||||
ARG rust_toolchain="nightly"
|
||||
ARG RUSTUP_HOME
|
||||
ARG CARGO_HOME
|
||||
@@ -12,9 +11,9 @@ COPY --link --from=input . .
|
||||
|
||||
WORKDIR /usr/src/tuwunel
|
||||
RUN \
|
||||
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared,ro \
|
||||
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared,ro \
|
||||
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared,ro \
|
||||
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared \
|
||||
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared \
|
||||
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared \
|
||||
<<EOF
|
||||
set -eux
|
||||
rustup run ${rust_toolchain} \
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.11-labs
|
||||
|
||||
FROM input AS build-deb
|
||||
ARG sys_target
|
||||
ARG rust_target
|
||||
ARG rust_toolchain
|
||||
ARG RUSTUP_HOME
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.11-labs
|
||||
|
||||
FROM input AS fmt
|
||||
ARG sys_target
|
||||
ARG rust_toolchain="nightly"
|
||||
ARG RUSTUP_HOME
|
||||
ARG CARGO_HOME
|
||||
@@ -12,9 +11,9 @@ COPY --link --from=input . .
|
||||
|
||||
WORKDIR /usr/src/tuwunel
|
||||
RUN \
|
||||
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared,ro \
|
||||
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared,ro \
|
||||
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared,ro \
|
||||
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared \
|
||||
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared \
|
||||
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared \
|
||||
<<EOF
|
||||
set -eux
|
||||
rustup run ${rust_toolchain} \
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.11-labs
|
||||
|
||||
FROM input AS lychee
|
||||
ARG sys_target
|
||||
ARG rust_toolchain="nightly"
|
||||
ARG RUSTUP_HOME
|
||||
ARG CARGO_HOME
|
||||
@@ -16,9 +15,9 @@ COPY --link --from=input . .
|
||||
|
||||
WORKDIR /usr/src/
|
||||
RUN \
|
||||
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared,ro \
|
||||
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared,ro \
|
||||
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared,ro \
|
||||
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared \
|
||||
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared \
|
||||
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared \
|
||||
--mount=type=cache,dst=lychee,sharing=locked \
|
||||
<<EOF
|
||||
set -eux
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.11-labs
|
||||
|
||||
FROM input AS build-rpm
|
||||
ARG sys_target
|
||||
ARG rust_toolchain
|
||||
ARG RUSTUP_HOME
|
||||
ARG CARGO_HOME
|
||||
|
||||
@@ -11,9 +11,9 @@ COPY --link --from=input . .
|
||||
|
||||
WORKDIR /usr/src/tuwunel
|
||||
RUN \
|
||||
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared,ro \
|
||||
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared,ro \
|
||||
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared,ro \
|
||||
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared \
|
||||
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared \
|
||||
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared \
|
||||
<<EOF
|
||||
set -eux
|
||||
rustup run ${rust_toolchain} \
|
||||
|
||||
@@ -66,49 +66,51 @@ COPY --from=source /usr/src/tuwunel/tests/test_results/complement/test_results.j
|
||||
COPY <<EOF complement.toml
|
||||
[global]
|
||||
address = "0.0.0.0"
|
||||
admin_room_notices = false
|
||||
allow_check_for_updates = false
|
||||
allow_device_name_federation = true
|
||||
allow_guest_registration = true
|
||||
allow_invalid_tls_certificates = true
|
||||
allow_legacy_media = true
|
||||
allow_public_room_directory_over_federation = true
|
||||
allow_public_room_directory_without_auth = true
|
||||
allow_registration = true
|
||||
create_admin_room = false
|
||||
database_path = "/database"
|
||||
log = "debug,tuwunel=trace,h2=warn,hyper=warn"
|
||||
port = [8008, 8448]
|
||||
trusted_servers = []
|
||||
only_query_trusted_key_servers = false
|
||||
query_trusted_key_servers_first = false
|
||||
query_trusted_key_servers_first_on_join = false
|
||||
yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true
|
||||
dns_attempts = 20
|
||||
dns_timeout = 60
|
||||
federation_idle_timeout = 300
|
||||
intentionally_unknown_config_option_for_testing = true
|
||||
ip_range_denylist = []
|
||||
url_preview_domain_contains_allowlist = ["*"]
|
||||
url_preview_domain_explicit_denylist = ["*"]
|
||||
log = "debug,tuwunel=trace,h2=warn,hyper=warn"
|
||||
log_colors = false
|
||||
log_guest_registrations = false
|
||||
log_span_events = "NONE"
|
||||
log_thread_ids = true
|
||||
media_compat_file_link = false
|
||||
media_startup_check = true
|
||||
only_query_trusted_key_servers = false
|
||||
port = [8008, 8448]
|
||||
prune_missing_media = true
|
||||
log_colors = false
|
||||
admin_room_notices = false
|
||||
allow_check_for_updates = false
|
||||
intentionally_unknown_config_option_for_testing = true
|
||||
rocksdb_log_level = "debug"
|
||||
rocksdb_max_log_files = 1
|
||||
rocksdb_recovery_mode = 0
|
||||
rocksdb_paranoid_file_checks = true
|
||||
log_guest_registrations = false
|
||||
allow_legacy_media = true
|
||||
startup_netburst = true
|
||||
startup_netburst_keep = -1
|
||||
# valgrind makes things so slow
|
||||
dns_timeout = 60
|
||||
dns_attempts = 20
|
||||
query_trusted_key_servers_first = false
|
||||
query_trusted_key_servers_first_on_join = false
|
||||
request_conn_timeout = 60
|
||||
request_timeout = 120
|
||||
well_known_conn_timeout = 60
|
||||
well_known_timeout = 60
|
||||
federation_idle_timeout = 300
|
||||
sender_timeout = 300
|
||||
rocksdb_log_level = "debug"
|
||||
rocksdb_max_log_files = 1
|
||||
rocksdb_paranoid_file_checks = true
|
||||
rocksdb_recovery_mode = 0
|
||||
sender_idle_timeout = 300
|
||||
sender_retry_backoff_limit = 300
|
||||
allow_invalid_tls_certificates = true
|
||||
sender_timeout = 300
|
||||
startup_netburst = true
|
||||
startup_netburst_keep = -1
|
||||
trusted_servers = []
|
||||
url_preview_domain_contains_allowlist = ["*"]
|
||||
url_preview_domain_explicit_denylist = ["*"]
|
||||
well_known_conn_timeout = 60
|
||||
well_known_timeout = 60
|
||||
yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true
|
||||
|
||||
[global.tls]
|
||||
certs = "/complement/certificate.crt"
|
||||
@@ -150,7 +152,7 @@ ENTRYPOINT valgrind \
|
||||
FROM input AS complement-base
|
||||
ARG var_cache
|
||||
ARG var_lib_apt
|
||||
ARG complement_tags="tuwunel_blacklist"
|
||||
ARG complement_tags="conduwuit_blacklist"
|
||||
ARG complement_tests="./tests/..."
|
||||
ARG complement_run=".*"
|
||||
|
||||
@@ -165,7 +167,7 @@ RUN \
|
||||
EOF
|
||||
|
||||
WORKDIR /usr/src
|
||||
ADD https://github.com/matrix-construct/complement.git complement
|
||||
ADD https://github.com/matrix-construct/complement.git#81f3b61e10ec102a36921890c81b79564ebf6f40 complement
|
||||
|
||||
WORKDIR /usr/src/complement
|
||||
ENV COMPLEMENT_BASE_IMAGE="complement-testee"
|
||||
@@ -211,6 +213,7 @@ ENV COMPLEMENT_HOST_MOUNTS="/var/run/docker.sock:/var/run/docker.sock"
|
||||
ENV jq_res='{Action: .Action, Test: .Test}'
|
||||
ENV jq_sel='select((.Action == \"pass\" or .Action == \"fail\" or .Action == \"skip\") and .Test != null)'
|
||||
ENV jq_tab='[.Action, .Test] | @tsv'
|
||||
ENV jq_out='select(.Test != null) | {Test: .Test, Output: .Output}'
|
||||
COPY --from=complement-config /complement/old_results.jsonl .
|
||||
COPY <<EOF uwu.sh
|
||||
env;
|
||||
@@ -219,22 +222,23 @@ COPY <<EOF uwu.sh
|
||||
COMPLEMENT_BASE_IMAGE="\${1:-$complement_base_image}"
|
||||
go test
|
||||
-json
|
||||
-shuffle="${complement_shuffle}"
|
||||
-parallel="${complement_parallel}"
|
||||
-timeout="${complement_timeout}"
|
||||
-count="${complement_count}"
|
||||
-tags="${complement_tags}"
|
||||
-skip="${complement_skip}"
|
||||
-run="${complement_run}"
|
||||
"${complement_tests}"
|
||||
| jq --unbuffered -c "${jq_sel} | ${jq_res}"
|
||||
"-shuffle=\$complement_shuffle"
|
||||
"-parallel=\$complement_parallel"
|
||||
"-timeout=\$complement_timeout"
|
||||
"-count=\$complement_count"
|
||||
"-tags=\$complement_tags"
|
||||
"-skip=\$complement_skip"
|
||||
"-run=\$complement_run"
|
||||
"\$complement_tests"
|
||||
| jq --unbuffered -c "."
|
||||
| tee output.jsonl
|
||||
| jq --unbuffered -c "$jq_sel | $jq_res"
|
||||
| tee results.jsonl
|
||||
| jq --unbuffered -r "${jq_tab}"
|
||||
| jq --unbuffered -r "$jq_tab"
|
||||
;
|
||||
|
||||
jq -s -c "sort_by(.Test)[]" < results.jsonl | uniq > new_results.jsonl;
|
||||
wc -l old_results.jsonl new_results.jsonl;
|
||||
diff -w -y -t --width=275 --suppress-common-lines old_results.jsonl new_results.jsonl;
|
||||
jq -s -c "sort_by(.Test, .Timestamp)[] | $jq_out" < output.jsonl > full_output.jsonl;
|
||||
EOF
|
||||
RUN echo $(tr -d '\n' < uwu.sh) > uwu.sh
|
||||
RUN echo $(tr -d '\n' < uwu.sh) > uwu.sh && chmod a+x uwu.sh
|
||||
ENTRYPOINT ["/bin/bash", "/usr/src/complement/uwu.sh"]
|
||||
|
||||
@@ -26,10 +26,10 @@ EOF
|
||||
|
||||
FROM input AS rocksdb-build
|
||||
ARG rocksdb_shared=0
|
||||
ARG rocksdb_portable=1
|
||||
ARG rocksdb_portable="1"
|
||||
ARG rocksdb_opt_level="3"
|
||||
ARG rocksdb_build_type="Release"
|
||||
ARG rocksdb_cxx_flags="-ftls-model=initial-exec -mno-outline-atomics"
|
||||
ARG rocksdb_cxx_flags="-ftls-model=initial-exec"
|
||||
ARG rocksdb_make_verbose="ON"
|
||||
ARG rocksdb_make_rule_messages="OFF"
|
||||
ARG rocksdb_jemalloc=1
|
||||
|
||||
@@ -16,7 +16,7 @@ RUN <<EOF
|
||||
var="${rustup_version}/${rust_target}"
|
||||
url="https://static.rust-lang.org/rustup/archive/$var/rustup-init"
|
||||
curl -S -O -s "$url"
|
||||
chmod o+x rustup-init
|
||||
chmod a+rx rustup-init
|
||||
EOF
|
||||
|
||||
FROM input AS rust
|
||||
@@ -54,7 +54,7 @@ RUN \
|
||||
--no-update-default-toolchain \
|
||||
--no-modify-path
|
||||
|
||||
chmod -R go+rw $CARGO_HOME $RUSTUP_HOME
|
||||
chmod -R a+rw $CARGO_HOME $RUSTUP_HOME
|
||||
rm rustup-init
|
||||
|
||||
rustup component add \
|
||||
|
||||
@@ -18,7 +18,6 @@ EOF
|
||||
|
||||
|
||||
FROM input AS ingredients
|
||||
ARG sys_target
|
||||
ARG rust_target
|
||||
ARG rust_toolchain
|
||||
ARG RUST_HOME
|
||||
|
||||
+140
-49
@@ -16,12 +16,6 @@ variable "repo" {
|
||||
variable "docker_repo" {
|
||||
default = "${repo}"
|
||||
}
|
||||
variable "docker_tag_preview" {
|
||||
default = false
|
||||
}
|
||||
variable "docker_tag_latest" {
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "git_ref" {
|
||||
default = "${GITHUB_REF}"
|
||||
@@ -36,7 +30,7 @@ variable "git_ref_name" {
|
||||
cargo_feat_sets = {
|
||||
none = ""
|
||||
default = "brotli_compression,element_hacks,gzip_compression,io_uring,jemalloc,jemalloc_conf,media_thumbnail,release_max_log_level,systemd,url_preview,zstd_compression"
|
||||
all = "blurhashing,brotli_compression,tuwunel_mods,console,default,direct_tls,element_hacks,gzip_compression,hardened_malloc,io_uring,jemalloc,jemalloc_conf,jemalloc_prof,jemalloc_stats,ldap,media_thumbnail,perf_measurements,release_max_log_level,sentry_telemetry,systemd,tokio_console,url_preview,zstd_compression"
|
||||
all = "blurhashing,brotli_compression,bzip2_compression,tuwunel_mods,console,default,direct_tls,element_hacks,gzip_compression,hardened_malloc,io_uring,jemalloc,jemalloc_conf,jemalloc_prof,jemalloc_stats,ldap,lz4_compression,media_thumbnail,perf_measurements,release_max_log_level,sentry_telemetry,systemd,tokio_console,url_preview,zstd_compression"
|
||||
}
|
||||
variable "cargo_features_always" {
|
||||
default = "direct_tls"
|
||||
@@ -63,19 +57,19 @@ variable "rust_targets" {
|
||||
default = "[\"x86_64-unknown-linux-gnu\"]"
|
||||
}
|
||||
|
||||
variable "sys_targets" {
|
||||
default = "[\"x86_64-linux-gnu\"]"
|
||||
variable "sys_names" {
|
||||
default = "[\"debian\"]"
|
||||
}
|
||||
variable "sys_versions" {
|
||||
default = "[\"testing-slim\"]"
|
||||
}
|
||||
variable "sys_names" {
|
||||
default = "[\"debian\"]"
|
||||
variable "sys_targets" {
|
||||
default = "[\"x86_64-v1-linux-gnu\"]"
|
||||
}
|
||||
|
||||
# RocksDB options
|
||||
variable "rocksdb_portable" {
|
||||
default = 1
|
||||
default = "1"
|
||||
}
|
||||
variable "rocksdb_opt_level" {
|
||||
default = "3"
|
||||
@@ -279,28 +273,24 @@ group "publish" {
|
||||
]
|
||||
}
|
||||
|
||||
target "github" {
|
||||
target "ghcr_io" {
|
||||
name = elem("github", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
tags = [
|
||||
"ghcr.io/${repo}:${git_ref_name}-${cargo_profile}-${feat_set}-${sys_target}",
|
||||
docker_tag_preview? "ghcr.io/${repo}:preview": "",
|
||||
docker_tag_latest? "ghcr.io/${repo}:latest": "",
|
||||
]
|
||||
output = ["type=registry,compression=zstd,mode=min,compression-level=${zstd_image_compress_level}"]
|
||||
output = ["type=registry,oci-mediatypes=true,compression=zstd,compression-level=${zstd_image_compress_level},force-compression=true,mode=min"]
|
||||
matrix = cargo_rust_feat_sys
|
||||
inherits = [
|
||||
elem("docker", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
|
||||
]
|
||||
}
|
||||
|
||||
target "dockerhub" {
|
||||
target "docker_io" {
|
||||
name = elem("dockerhub", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
tags = [
|
||||
"${docker_repo}:${git_ref_name}-${cargo_profile}-${feat_set}-${sys_target}",
|
||||
docker_tag_preview? "${docker_repo}:preview": "",
|
||||
docker_tag_latest? "${docker_repo}:latest": "",
|
||||
"docker.io/${docker_repo}:${git_ref_name}-${cargo_profile}-${feat_set}-${sys_target}",
|
||||
]
|
||||
output = ["type=registry,compression=zstd,mode=min,compression-level=${zstd_image_compress_level}"]
|
||||
output = ["type=registry,oci-mediatypes=true,compression=zstd,compression-level=${zstd_image_compress_level},force-compression=true,mode=min"]
|
||||
matrix = cargo_rust_feat_sys
|
||||
inherits = [
|
||||
elem("docker", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
|
||||
@@ -534,7 +524,7 @@ group "installs" {
|
||||
|
||||
install_labels = {
|
||||
"org.opencontainers.image.authors" = "${package_authors}"
|
||||
"org.opencontainers.image.created" ="${package_last_modified}"
|
||||
"org.opencontainers.image.created" = "${package_last_modified}"
|
||||
"org.opencontainers.image.description" = "Matrix Chat Server in Rust"
|
||||
"org.opencontainers.image.documentation" = "https://github.com/matrix-construct/tuwunel/tree/main/docs/"
|
||||
"org.opencontainers.image.licenses" = "Apache-2.0"
|
||||
@@ -546,12 +536,26 @@ install_labels = {
|
||||
"org.opencontainers.image.version" = "${package_version}"
|
||||
}
|
||||
|
||||
install_annotations = [
|
||||
"org.opencontainers.image.authors=${package_authors}",
|
||||
"org.opencontainers.image.created=${package_last_modified}",
|
||||
"org.opencontainers.image.description=Matrix Chat Server in Rust",
|
||||
"org.opencontainers.image.documentation=https://github.com/matrix-construct/tuwunel/tree/main/docs/",
|
||||
"org.opencontainers.image.licenses=Apache-2.0",
|
||||
"org.opencontainers.image.revision=${package_revision}",
|
||||
"org.opencontainers.image.source=https://github.com/matrix-construct/tuwunel",
|
||||
"org.opencontainers.image.title=${package_name}",
|
||||
"org.opencontainers.image.url=https://github.com/matrix-construct/tuwunel",
|
||||
"org.opencontainers.image.vendor=matrix-construct",
|
||||
"org.opencontainers.image.version=${package_version}",
|
||||
]
|
||||
|
||||
target "oci" {
|
||||
name = elem("oci", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
tags = [
|
||||
elem_tag("oci", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
|
||||
]
|
||||
output = ["type=oci,dest=tuwunel-oci.tar.zst,mode=min,compression=zstd,compression-level=${zstd_image_compress_level}"]
|
||||
output = ["type=oci,dest=tuwunel-oci.tar.zst,compression=zstd,compression-level=${zstd_image_compress_level},force-compression=true,mode=min"]
|
||||
matrix = cargo_rust_feat_sys
|
||||
inherits = [
|
||||
elem("docker", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
|
||||
@@ -563,7 +567,7 @@ target "docker" {
|
||||
tags = [
|
||||
elem_tag("docker", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
|
||||
]
|
||||
output = ["type=docker,compression=gzip,mode=min,compression-level=${gz_image_compress_level}"]
|
||||
output = ["type=docker,compression=zstd,compression-level=${zstd_image_compress_level},force-compression=true,mode=min"]
|
||||
matrix = cargo_rust_feat_sys
|
||||
inherits = [
|
||||
elem("static", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
|
||||
@@ -643,7 +647,8 @@ target "install" {
|
||||
elem_tag("install", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
|
||||
]
|
||||
labels = install_labels
|
||||
output = ["type=docker,compression=zstd,mode=min,compression-level=${zstd_image_compress_level}"]
|
||||
annotations = install_annotations
|
||||
output = ["type=docker,compression=zstd,mode=max,compression-level=${zstd_image_compress_level}"]
|
||||
cache_to = ["type=local,compression=zstd,mode=max,compression-level=${cache_compress_level}"]
|
||||
dockerfile = "${docker_dir}/Dockerfile.install"
|
||||
target = "install"
|
||||
@@ -1195,13 +1200,17 @@ target "deps-base" {
|
||||
join(" ", static_rustflags),
|
||||
join(" ", static_nightly_rustflags),
|
||||
join(" ", native_rustflags),
|
||||
"-C link-arg=-L/usr/lib/gcc/${sys_target}/14", #FIXME
|
||||
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/14", #FIXME
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")?
|
||||
"-C link-arg=-l:libbz2.a": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")?
|
||||
"-C link-arg=-l:liblz4.a": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")?
|
||||
"-C link-arg=-l:libzstd.a": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "io_uring")?
|
||||
"-C link-arg=-l:liburing.a": "",
|
||||
join(" ", static_libs),
|
||||
sys_target == "aarch64-linux-gnu"?
|
||||
sys_target_triple(sys_target) == "aarch64-linux-gnu"?
|
||||
"-C link-arg=-l:libgcc.a": "",
|
||||
]):
|
||||
|
||||
@@ -1211,13 +1220,19 @@ target "deps-base" {
|
||||
join(" ", nightly_rustflags),
|
||||
join(" ", static_rustflags),
|
||||
join(" ", static_nightly_rustflags),
|
||||
"-C link-arg=-L/usr/lib/gcc/${sys_target}/14", #FIXME
|
||||
sys_target_triple(sys_target) == "x86_64-linux-gnu"?
|
||||
"-C target-cpu=${sys_target_isa(sys_target)}": "",
|
||||
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/14", #FIXME
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")?
|
||||
"-C link-arg=-l:libbz2.a": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")?
|
||||
"-C link-arg=-l:liblz4.a": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")?
|
||||
"-C link-arg=-l:libzstd.a": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "io_uring")?
|
||||
"-C link-arg=-l:liburing.a": "",
|
||||
join(" ", static_libs),
|
||||
sys_target == "aarch64-linux-gnu"?
|
||||
sys_target_triple(sys_target) == "aarch64-linux-gnu"?
|
||||
"-C link-arg=-l:libgcc.a": "",
|
||||
]):
|
||||
|
||||
@@ -1225,13 +1240,19 @@ target "deps-base" {
|
||||
join(" ", [
|
||||
join(" ", rustflags),
|
||||
join(" ", static_rustflags),
|
||||
"-C link-arg=-L/usr/lib/gcc/${sys_target}/14", #FIXME
|
||||
sys_target_triple(sys_target) == "x86_64-linux-gnu"?
|
||||
"-C target-cpu=${sys_target_isa(sys_target)}": "",
|
||||
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/14", #FIXME
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")?
|
||||
"-C link-arg=-l:libbz2.a": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")?
|
||||
"-C link-arg=-l:liblz4.a": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")?
|
||||
"-C link-arg=-l:libzstd.a": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "io_uring")?
|
||||
"-C link-arg=-l:liburing.a": "",
|
||||
join(" ", static_libs),
|
||||
sys_target == "aarch64-linux-gnu"?
|
||||
sys_target_triple(sys_target) == "aarch64-linux-gnu"?
|
||||
"-C link-arg=-l:libgcc.a": "",
|
||||
]):
|
||||
|
||||
@@ -1239,13 +1260,19 @@ target "deps-base" {
|
||||
join(" ", [
|
||||
join(" ", rustflags),
|
||||
join(" ", static_rustflags),
|
||||
"-C link-arg=-L/usr/lib/gcc/${sys_target}/14", #FIXME
|
||||
sys_target_triple(sys_target) == "x86_64-linux-gnu"?
|
||||
"-C target-cpu=${sys_target_isa(sys_target)}": "",
|
||||
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/14", #FIXME
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")?
|
||||
"-C link-arg=-l:libbz2.a": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")?
|
||||
"-C link-arg=-l:liblz4.a": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")?
|
||||
"-C link-arg=-l:libzstd.a": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "io_uring")?
|
||||
"-C link-arg=-l:liburing.a": "",
|
||||
join(" ", static_libs),
|
||||
sys_target == "aarch64-linux-gnu"?
|
||||
sys_target_triple(sys_target) == "aarch64-linux-gnu"?
|
||||
"-C link-arg=-l:libgcc.a": "",
|
||||
]):
|
||||
|
||||
@@ -1254,6 +1281,12 @@ target "deps-base" {
|
||||
join(" ", rustflags),
|
||||
join(" ", nightly_rustflags),
|
||||
join(" ", dynamic_rustflags),
|
||||
sys_target_triple(sys_target) == "x86_64-linux-gnu"?
|
||||
"-C target-cpu=${sys_target_isa(sys_target)}": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")?
|
||||
"-C link-arg=-lbz2": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")?
|
||||
"-C link-arg=-llz4": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")?
|
||||
"-C link-arg=-lzstd": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "io_uring")?
|
||||
@@ -1303,13 +1336,32 @@ target "rocksdb-build" {
|
||||
input = elem("target:kitchen", [feat_set, sys_name, sys_version, sys_target])
|
||||
}
|
||||
args = {
|
||||
rocksdb_bz2 = contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")? 1: 0
|
||||
rocksdb_lz4 = contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")? 1: 0
|
||||
rocksdb_zstd = contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")? 1: 0
|
||||
rocksdb_jemalloc = contains(split(",", cargo_feat_sets[feat_set]), "jemalloc")? 1: 0
|
||||
rocksdb_iouring = contains(split(",", cargo_feat_sets[feat_set]), "io_uring")? 1: 0
|
||||
rocksdb_portable = cargo_profile == "release_max_perf"? 0: rocksdb_portable
|
||||
rocksdb_build_type = rocksdb_build_type
|
||||
rocksdb_opt_level = rocksdb_opt_level
|
||||
rocksdb_shared = 0
|
||||
rocksdb_opt_level = rocksdb_opt_level
|
||||
rocksdb_build_type = rocksdb_build_type
|
||||
rocksdb_cxx_flags = (
|
||||
cargo_profile == "release-native" && sys_target_triple(sys_target) == "aarch64-linux-gnu"?
|
||||
"-ftls-model=local-exec -mno-outline-atomics":
|
||||
cargo_profile == "release-native"?
|
||||
"-ftls-model=local-exec":
|
||||
sys_target_triple(sys_target) == "aarch64-linux-gnu"?
|
||||
"-ftls-model=initial-exec -mno-outline-atomics":
|
||||
sys_target_triple(sys_target) == "x86_64-linux-gnu" && sys_target != "x86_64-v1-linux-gnu"?
|
||||
"-ftls-model=initial-exec -mpclmul":
|
||||
"-ftls-model=initial-exec"
|
||||
)
|
||||
rocksdb_portable = (
|
||||
cargo_profile == "release-native"?
|
||||
"0":
|
||||
sys_target_triple(sys_target) == "x86_64-linux-gnu"?
|
||||
"${sys_target_isa(sys_target)}":
|
||||
rocksdb_portable
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1405,8 +1457,8 @@ target "ingredients" {
|
||||
"--all-features": "--no-default-features"
|
||||
)
|
||||
RUST_BACKTRACE = "full"
|
||||
ROCKSDB_LIB_DIR="/usr/lib/${sys_target}"
|
||||
JEMALLOC_OVERRIDE="/usr/lib/${sys_target}/libjemalloc.a"
|
||||
ROCKSDB_LIB_DIR="/usr/lib/${sys_target_triple(sys_target)}"
|
||||
JEMALLOC_OVERRIDE="/usr/lib/${sys_target_triple(sys_target)}/libjemalloc.a"
|
||||
ZSTD_SYS_USE_PKG_CONFIG = (
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")? 1: 0
|
||||
)
|
||||
@@ -1487,8 +1539,8 @@ target "rust" {
|
||||
cargo_installs = join(" ", cargo_installs)
|
||||
|
||||
CARGO_TERM_VERBOSE = CARGO_TERM_VERBOSE
|
||||
RUSTUP_HOME = "/opt/rust/rustup/${sys_name}/${sys_target}"
|
||||
CARGO_HOME = "/opt/rust/cargo/${sys_name}/${sys_target}"
|
||||
RUSTUP_HOME = "/opt/rust/rustup/${sys_name}/${sys_target_triple(sys_target)}"
|
||||
CARGO_HOME = "/opt/rust/cargo/${sys_name}/${sys_target_triple(sys_target)}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1536,6 +1588,7 @@ kitchen_packages = [
|
||||
"cmake",
|
||||
"curl",
|
||||
"git",
|
||||
"gzip",
|
||||
"libc6-dev",
|
||||
"libssl-dev",
|
||||
"make",
|
||||
@@ -1559,10 +1612,12 @@ target "kitchen" {
|
||||
}
|
||||
args = {
|
||||
packages = join(" ", [
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "io_uring")? "liburing-dev": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")? "libzstd-dev": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "jemalloc")? "libjemalloc-dev": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")? "libbz2-dev": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "hardened_malloc")? "g++": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "io_uring")? "liburing-dev": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "jemalloc")? "libjemalloc-dev": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")? "liblz4-dev": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")? "libzstd-dev": "",
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -1662,18 +1717,18 @@ target "runtime" {
|
||||
}
|
||||
args = {
|
||||
packages = join(" ", [
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")? "bzip2": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "io_uring")? "liburing2": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")? "libzstd1": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "jemalloc")? "libjemalloc2": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")? "liblz4-1": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")? "libzstd1": "",
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
base_pkgs = [
|
||||
"adduser",
|
||||
"bzip2",
|
||||
"ca-certificates",
|
||||
"gzip",
|
||||
]
|
||||
|
||||
target "base" {
|
||||
@@ -1692,8 +1747,8 @@ target "base" {
|
||||
}
|
||||
args = {
|
||||
DEBIAN_FRONTEND="noninteractive"
|
||||
var_lib_apt = "/var/lib/apt/${sys_name}/${sys_version}/${sys_target}"
|
||||
var_cache = "/var/cache/${sys_name}/${sys_version}/${sys_target}"
|
||||
var_lib_apt = "/var/lib/apt/${sys_name}/${sys_version}/${sys_target_triple(sys_target)}"
|
||||
var_cache = "/var/cache/${sys_name}/${sys_version}/${sys_target_triple(sys_target)}"
|
||||
packages = join(" ", base_pkgs)
|
||||
}
|
||||
}
|
||||
@@ -1713,12 +1768,21 @@ target "system" {
|
||||
cache_to = ["type=local,compression=zstd,mode=max,compression-level=${cache_compress_level}"]
|
||||
cache_from = ["type=local"]
|
||||
dockerfile = "${docker_dir}/Dockerfile.system"
|
||||
matrix = sys
|
||||
context = "."
|
||||
matrix = sys
|
||||
platforms = (
|
||||
sys_target_triple(sys_target) == "x86_64-linux-gnu"?
|
||||
["linux/amd64/${sys_target_ver(sys_target)}"]:
|
||||
sys_target_triple(sys_target) == "aarch64-linux-gnu"?
|
||||
["linux/arm64"]:
|
||||
["local"]
|
||||
)
|
||||
args = {
|
||||
sys_name = sys_name
|
||||
sys_version = sys_version
|
||||
sys_target = sys_target
|
||||
sys_triple = sys_target_triple(sys_target)
|
||||
sys_isa = sys_target_isa(sys_target)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1727,6 +1791,33 @@ target "system" {
|
||||
# Utils
|
||||
#
|
||||
|
||||
function "sys_target_isa" {
|
||||
params = [sys_target]
|
||||
result = (
|
||||
sys_target_ver(sys_target) != "v1"?
|
||||
join("-",
|
||||
[
|
||||
replace(split("-", sys_target)[0], "_", "-"),
|
||||
sys_target_ver(sys_target)
|
||||
]):
|
||||
replace(split("-", sys_target)[0], "_", "-")
|
||||
)
|
||||
}
|
||||
|
||||
function "sys_target_triple" {
|
||||
params = [sys_target]
|
||||
result = join("-", [
|
||||
split("-", sys_target)[0],
|
||||
split("-", sys_target)[2],
|
||||
split("-", sys_target)[3],
|
||||
])
|
||||
}
|
||||
|
||||
function "sys_target_ver" {
|
||||
params = [sys_target]
|
||||
result = split("-", sys_target)[1]
|
||||
}
|
||||
|
||||
function "elem_tag" {
|
||||
params = [prefix, matrix, tag]
|
||||
result = join(":", [elem(prefix, matrix), tag])
|
||||
|
||||
+2
-11
@@ -14,8 +14,8 @@ default_feat_sets='["all"]'
|
||||
default_rust_toolchains='["nightly"]'
|
||||
default_rust_targets='["x86_64-unknown-linux-gnu"]'
|
||||
default_sys_names='["debian"]'
|
||||
default_sys_targets='["x86_64-linux-gnu"]'
|
||||
default_sys_versions='["testing-slim"]'
|
||||
default_sys_targets='["x86_64-v1-linux-gnu"]'
|
||||
|
||||
if test ! -z "$cargo_profile"; then
|
||||
env_cargo_profiles="[\"${cargo_profile}\"]"
|
||||
@@ -63,16 +63,6 @@ rocksdb_opt_level=3
|
||||
rocksdb_portable=1
|
||||
git_checkout="HEAD"
|
||||
use_chef="true"
|
||||
complement_count=1
|
||||
complement_skip="TestPartialStateJoin.*"
|
||||
complement_skip="${complement_skip}|TestRoomDeleteAlias/Pa.*/Can_delete_canonical_alias"
|
||||
complement_skip="${complement_skip}|TestUnbanViaInvite.*"
|
||||
complement_skip="${complement_skip}|TestRoomDeleteAlias/Pa.*/Regular_users_can_add_and_delete_aliases_when.*"
|
||||
complement_skip="${complement_skip}|TestToDeviceMessagesOverFederation/stopped_server"
|
||||
complement_skip="${complement_skip}|TestLogin/parallel/POST_/login_as_non-existing_user_is_rejected"
|
||||
complement_skip="${complement_skip}|TestRoomState/Parallel/GET_/publicRooms_lists_newly-created_room"
|
||||
complement_skip="${complement_skip}|TestThreadReceiptsInSyncMSC4102"
|
||||
complement_run=".*"
|
||||
set +a
|
||||
|
||||
###############################################################################
|
||||
@@ -83,6 +73,7 @@ if test "$CI" = "true"; then
|
||||
fi
|
||||
|
||||
args=""
|
||||
args="$args --provenance=false"
|
||||
args="$args --builder ${builder_name}"
|
||||
#args="$args --set *.platform=${sys_platform}"
|
||||
|
||||
|
||||
+48
-11
@@ -12,9 +12,26 @@ default_feat_set="all"
|
||||
default_rust_toolchain="nightly"
|
||||
default_rust_target="x86_64-unknown-linux-gnu"
|
||||
default_sys_name="debian"
|
||||
default_sys_target="x86_64-linux-gnu"
|
||||
default_sys_target="x86_64-v1-linux-gnu"
|
||||
default_sys_version="testing-slim"
|
||||
|
||||
default_complement_verbose=0
|
||||
default_complement_count=1
|
||||
default_complement_parallel=1
|
||||
default_complement_shuffle=0
|
||||
default_complement_timeout="1h"
|
||||
default_complement_run=".*"
|
||||
|
||||
run="${1:-$default_complement_run}"
|
||||
skip=""
|
||||
skip="${skip}TestToDeviceMessagesOverFederation/stopped_server"
|
||||
skip="${skip}|TestToDeviceMessagesOverFederation/interrupted_connectivity"
|
||||
skip="${skip}|TestRoomCreate/Parallel/POST_/createRoom_makes_a_room_with_a_topic_and_writes_rich_topic_representation"
|
||||
skip="${skip}|TestRoomCreate/Parallel/POST_/createRoom_makes_a_room_with_a_topic_via_initial_state_overwritten_by_topic"
|
||||
skip="${skip}|TestLogin/parallel/POST_/"
|
||||
skip="${skip}|TestUnbanViaInvite"
|
||||
skip="${skip}|TestRoomState/Parallel/GET_/publicRooms_lists_newly-created_room"
|
||||
|
||||
set -a
|
||||
cargo_profile="${cargo_profile:-$default_cargo_profile}"
|
||||
feat_set="${feat_set:-$default_feat_set}"
|
||||
@@ -30,12 +47,21 @@ set +a
|
||||
|
||||
###############################################################################
|
||||
|
||||
envs=""
|
||||
envs="$envs -e complement_verbose=${complement_verbose:-$default_complement_verbose}"
|
||||
envs="$envs -e complement_count=${complement_count:-$default_complement_count}"
|
||||
envs="$envs -e complement_parallel=${complement_parallel:-$default_complement_parallel}"
|
||||
envs="$envs -e complement_shuffle=${complement_shuffle:-$default_complement_shuffle}"
|
||||
envs="$envs -e complement_timeout=${complement_timeout:-$default_complement_timeout}"
|
||||
envs="$envs -e complement_skip=${complement_skip:-$skip}"
|
||||
envs="$envs -e complement_run=${1:-$default_complement_run}"
|
||||
|
||||
set -x
|
||||
tester_image="complement-tester--${sys_name}--${sys_version}--${sys_target}"
|
||||
testee_image="complement-testee--${cargo_profile}--${rust_toolchain}--${rust_target}--${feat_set}--${sys_name}--${sys_version}--${sys_target}"
|
||||
name="complement_tester__${sys_name}__${sys_version}__${sys_target}"
|
||||
sock="/var/run/docker.sock"
|
||||
arg="--name $name -v $sock:$sock --network=host $tester_image ${testee_image}"
|
||||
arg="--name $name -v $sock:$sock --network=host $envs $tester_image ${testee_image}"
|
||||
set +x
|
||||
|
||||
if test "$CI_VERBOSE_ENV" = "true"; then
|
||||
@@ -45,21 +71,32 @@ fi
|
||||
|
||||
docker rm -f "$name" 2>/dev/null
|
||||
|
||||
trap 'set +x; date; echo -e "\033[1;41;37mERROR\033[0m"' ERR
|
||||
arg="-d $arg"
|
||||
cid=$(docker run $arg)
|
||||
|
||||
if test "$CI" = "true"; then
|
||||
arg="-d $arg"
|
||||
cid=$(docker run $arg)
|
||||
else
|
||||
arg="--rm -a stdout -a stderr $arg"
|
||||
docker run $arg
|
||||
exit $?
|
||||
echo -n "$cid" > "$name"
|
||||
fi
|
||||
|
||||
echo -n "$cid" > "$name"
|
||||
output_src="$cid:/usr/src/complement/full_output.jsonl"
|
||||
output_dst="complement.jsonl"
|
||||
extract_output() {
|
||||
docker cp "$output_src" "$output_dst"
|
||||
}
|
||||
|
||||
trap 'docker container stop $cid; set +x; date; echo -e "\033[1;41;37mERROR\033[0m"' INT
|
||||
result_src="$cid:/usr/src/complement/new_results.jsonl"
|
||||
result_dst="tests/test_results/complement/test_results.jsonl"
|
||||
extract_results() {
|
||||
docker cp "$result_src" "$result_dst"
|
||||
}
|
||||
|
||||
trap 'extract_output; set +x; date; echo -e "\033[1;41;37mERROR\033[0m"' ERR
|
||||
trap 'docker container stop $cid; extract_output' INT
|
||||
docker logs -f "$cid"
|
||||
docker wait "$cid" 2>/dev/null
|
||||
|
||||
extract_results
|
||||
git diff -U0 --color --shortstat "$result_dst" | (grep "$run" || true)
|
||||
|
||||
git diff --quiet --exit-code "$result_dst"
|
||||
echo -e "\033[1;42;30mACCEPT\033[0m"
|
||||
|
||||
+26
-6
@@ -7,14 +7,20 @@ ## Basics
|
||||
Tuwunel uses a config file for the majority of the settings, but also supports
|
||||
setting individual config options via commandline.
|
||||
|
||||
Please refer to the [example config
|
||||
file](./configuration/examples.md#example-configuration) for all of those
|
||||
settings.
|
||||
Please refer to the [example config file](./configuration/examples.md#example-configuration) for all of those settings.
|
||||
|
||||
The config file to use can be specified on the commandline when running
|
||||
Tuwunel by specifying the `-c`, `--config` flag. Alternatively, you can use
|
||||
the environment variable `TUWUNEL_CONFIG` to specify the config file to used.
|
||||
Conduit's environment variables are supported for backwards compatibility.
|
||||
Tuwunel by specifying the `-c`, `--config` flag. It is also possible to specify
|
||||
more than one config file.
|
||||
|
||||
Alternatively, you can use the environment variable `TUWUNEL_CONFIG` to specify
|
||||
the config file to used. Conduit's environment variables are supported for
|
||||
backwards compatibility.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> It is bad practice to uncomment default options without changing them. Many
|
||||
> defaults are updated by developers as features evolve and can be essential
|
||||
> to expected server function.
|
||||
|
||||
## Option commandline flag
|
||||
|
||||
@@ -31,6 +37,20 @@ ## Option commandline flag
|
||||
- `--option log=\"debug\"` works ✅
|
||||
- `--option server_name='"example.com'"` works ✅
|
||||
|
||||
## Relevance of configuration settings
|
||||
|
||||
There is a specific sequence for reading and overwriting the settings. The
|
||||
latest setting takes precedence and defines the configuration.
|
||||
|
||||
1. Set in `CONDUIT_CONFIG`.
|
||||
2. Set in `CONDUWUIT_CONFIG`.
|
||||
3. Set in `TUWUNEL_CONFIG`.
|
||||
4. Set in the first config file on the command line (e.g. `-c config_file_1.toml`).
|
||||
5. Set in the second config file on the command line (e.g. `-c config_file_2.toml`).
|
||||
6. Set in any additional config file on the command line (e.g. `-c config_file_n.toml`).
|
||||
7. Set within the options (again, the latest option in the list overrides).
|
||||
|
||||
|
||||
## Execute commandline flag
|
||||
|
||||
Tuwunel supports running admin commands on startup using the commandline
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
file = ./rust-toolchain.toml;
|
||||
|
||||
# See also `rust-toolchain.toml`
|
||||
sha256 = "sha256-X/4ZBHO3iW0fOenQ3foEvscgAPJYl2abspaBThDOukI=";
|
||||
sha256 = "sha256-Qxt8XAuaUR2OMdKbN4u8dBJOhSHxS+uS06Wl9+flVEk=";
|
||||
};
|
||||
|
||||
mkScope = pkgs: pkgs.lib.makeScope pkgs.newScope (self: {
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
# If you're having trouble making the relevant changes, bug a maintainer.
|
||||
|
||||
[toolchain]
|
||||
channel = "1.86.0"
|
||||
channel = "1.88.0"
|
||||
profile = "minimal"
|
||||
components = [
|
||||
# For rust-analyzer
|
||||
|
||||
@@ -22,6 +22,11 @@ brotli_compression = [
|
||||
"tuwunel-core/brotli_compression",
|
||||
"tuwunel-service/brotli_compression",
|
||||
]
|
||||
bzip2_compression = [
|
||||
"tuwunel-api/bzip2_compression",
|
||||
"tuwunel-database/bzip2_compression",
|
||||
"tuwunel-service/bzip2_compression",
|
||||
]
|
||||
gzip_compression = [
|
||||
"tuwunel-api/gzip_compression",
|
||||
"tuwunel-core/gzip_compression",
|
||||
@@ -56,6 +61,11 @@ jemalloc_stats = [
|
||||
"tuwunel-database/jemalloc_stats",
|
||||
"tuwunel-service/jemalloc_stats",
|
||||
]
|
||||
lz4_compression = [
|
||||
"tuwunel-api/lz4_compression",
|
||||
"tuwunel-database/lz4_compression",
|
||||
"tuwunel-service/lz4_compression",
|
||||
]
|
||||
release_max_log_level = [
|
||||
"tuwunel-api/release_max_log_level",
|
||||
"tuwunel-core/release_max_log_level",
|
||||
@@ -74,6 +84,7 @@ zstd_compression = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
base64.workspace = true
|
||||
clap.workspace = true
|
||||
tuwunel-api.workspace = true
|
||||
tuwunel-core.workspace = true
|
||||
|
||||
@@ -638,7 +638,7 @@ pub(super) async fn force_set_room_state_from_server(
|
||||
|
||||
self.services
|
||||
.rooms
|
||||
.outlier
|
||||
.timeline
|
||||
.add_pdu_outlier(&event_id, &value);
|
||||
|
||||
if let Some(state_key) = &pdu.state_key {
|
||||
@@ -668,7 +668,7 @@ pub(super) async fn force_set_room_state_from_server(
|
||||
|
||||
self.services
|
||||
.rooms
|
||||
.outlier
|
||||
.timeline
|
||||
.add_pdu_outlier(&event_id, &value);
|
||||
}
|
||||
|
||||
@@ -1035,3 +1035,16 @@ struct Claim {
|
||||
.map(async |token| self.write_str(&token).await)?
|
||||
.await
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn resync_database(&self) -> Result {
|
||||
if !self.services.db.is_secondary() {
|
||||
return Err!("Not a secondary instance.");
|
||||
}
|
||||
|
||||
self.services
|
||||
.db
|
||||
.db
|
||||
.update()
|
||||
.map_err(|e| err!("Failed to update from primary: {e:?}"))
|
||||
}
|
||||
|
||||
@@ -256,6 +256,9 @@ pub(super) enum DebugCommand {
|
||||
audience: Option<String>,
|
||||
},
|
||||
|
||||
/// - Synchronize database with primary (secondary only)
|
||||
ResyncDatabase,
|
||||
|
||||
/// - Developer test stubs
|
||||
#[command(subcommand)]
|
||||
#[allow(non_snake_case)]
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use futures::StreamExt;
|
||||
use ruma::{OwnedRoomId, OwnedServerName, OwnedUserId};
|
||||
use tuwunel_core::{Err, Result};
|
||||
@@ -26,25 +24,7 @@ pub(super) async fn enable_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn incoming_federation(&self) -> Result {
|
||||
let msg = {
|
||||
let map = self
|
||||
.services
|
||||
.rooms
|
||||
.event_handler
|
||||
.federation_handletime
|
||||
.read()
|
||||
.expect("locked");
|
||||
|
||||
let mut msg = format!("Handling {} incoming pdus:\n", map.len());
|
||||
for (r, (e, i)) in map.iter() {
|
||||
let elapsed = i.elapsed();
|
||||
writeln!(msg, "{} {}: {}m{}s", r, e, elapsed.as_secs() / 60, elapsed.as_secs() % 60)?;
|
||||
}
|
||||
|
||||
msg
|
||||
};
|
||||
|
||||
self.write_str(&msg).await
|
||||
Err!("This command is temporarily disabled")
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
|
||||
@@ -32,7 +32,7 @@ pub(super) async fn process(subcommand: GlobalsCommand, context: &Context<'_>) -
|
||||
},
|
||||
| GlobalsCommand::CurrentCount => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.globals.db.current_count();
|
||||
let results = services.globals.current_count();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
|
||||
|
||||
@@ -19,6 +19,9 @@ pub(crate) enum PresenceCommand {
|
||||
PresenceSince {
|
||||
/// UNIX timestamp since (u64)
|
||||
since: u64,
|
||||
|
||||
/// Upper-bound of since
|
||||
to: Option<u64>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -34,11 +37,11 @@ pub(super) async fn process(subcommand: PresenceCommand, context: &Context<'_>)
|
||||
|
||||
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
|
||||
},
|
||||
| PresenceCommand::PresenceSince { since } => {
|
||||
| PresenceCommand::PresenceSince { since, to } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<(_, _, _)> = services
|
||||
.presence
|
||||
.presence_since(since)
|
||||
.presence_since(since, to)
|
||||
.map(|(user_id, count, bytes)| (user_id.to_owned(), count, bytes.to_vec()))
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
+13
-2
@@ -1,5 +1,6 @@
|
||||
use std::{borrow::Cow, collections::BTreeMap, ops::Deref, sync::Arc};
|
||||
|
||||
use base64::prelude::*;
|
||||
use clap::Subcommand;
|
||||
use futures::{FutureExt, Stream, StreamExt, TryStreamExt};
|
||||
use tokio::time::Instant;
|
||||
@@ -30,6 +31,10 @@ pub(crate) enum RawCommand {
|
||||
|
||||
/// Key
|
||||
key: String,
|
||||
|
||||
/// Encode as base64
|
||||
#[arg(long, short)]
|
||||
base64: bool,
|
||||
},
|
||||
|
||||
/// - Raw database delete (for string keys)
|
||||
@@ -421,13 +426,19 @@ pub(super) async fn raw_del(&self, map: String, key: String) -> Result {
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn raw_get(&self, map: String, key: String) -> Result {
|
||||
pub(super) async fn raw_get(&self, map: String, key: String, base64: bool) -> Result {
|
||||
let map = self.services.db.get(&map)?;
|
||||
let timer = Instant::now();
|
||||
let handle = map.get(&key).await?;
|
||||
|
||||
let query_time = timer.elapsed();
|
||||
let result = String::from_utf8_lossy(&handle);
|
||||
|
||||
let result = if base64 {
|
||||
BASE64_STANDARD.encode(&handle)
|
||||
} else {
|
||||
String::from_utf8_lossy(&handle).to_string()
|
||||
};
|
||||
|
||||
self.write_str(&format!("Query completed in {query_time:?}:\n\n```rs\n{result:?}\n```"))
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ pub(super) async fn list_rooms(
|
||||
.rooms
|
||||
.metadata
|
||||
.iter_ids()
|
||||
.filter_map(|room_id| async move {
|
||||
.filter_map(async |room_id| {
|
||||
(!exclude_disabled
|
||||
|| !self
|
||||
.services
|
||||
@@ -29,7 +29,7 @@ pub(super) async fn list_rooms(
|
||||
.await)
|
||||
.then_some(room_id)
|
||||
})
|
||||
.filter_map(|room_id| async move {
|
||||
.filter_map(async |room_id| {
|
||||
(!exclude_banned
|
||||
|| !self
|
||||
.services
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use clap::Subcommand;
|
||||
use futures::StreamExt;
|
||||
use ruma::OwnedRoomId;
|
||||
use tuwunel_core::{Err, Result};
|
||||
use tuwunel_core::Result;
|
||||
|
||||
use crate::{Context, PAGE_SIZE, get_room_info};
|
||||
|
||||
@@ -57,7 +57,11 @@ pub(super) async fn process(command: RoomDirectoryCommand, context: &Context<'_>
|
||||
.collect();
|
||||
|
||||
if rooms.is_empty() {
|
||||
return Err!("No more rooms.");
|
||||
context
|
||||
.write_str("No rooms are published.")
|
||||
.await?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let body = rooms
|
||||
|
||||
@@ -47,7 +47,7 @@ async fn list_joined_members(&self, room_id: OwnedRoomId, local_only: bool) -> R
|
||||
.unwrap_or(true)
|
||||
})
|
||||
.map(ToOwned::to_owned)
|
||||
.filter_map(|user_id| async move {
|
||||
.filter_map(async |user_id| {
|
||||
Some((
|
||||
self.services
|
||||
.users
|
||||
|
||||
@@ -179,7 +179,7 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
.alias
|
||||
.local_aliases_for_room(&room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.for_each(|local_alias| async move {
|
||||
.for_each(async |local_alias| {
|
||||
self.services
|
||||
.rooms
|
||||
.alias
|
||||
@@ -363,7 +363,7 @@ async fn ban_list_of_rooms(&self) -> Result {
|
||||
.alias
|
||||
.local_aliases_for_room(&room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.for_each(|local_alias| async move {
|
||||
.for_each(async |local_alias| {
|
||||
self.services
|
||||
.rooms
|
||||
.alias
|
||||
|
||||
@@ -236,7 +236,10 @@ pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) ->
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
full_user_deactivate(self.services, &user_id, &all_joined_rooms).await?;
|
||||
full_user_deactivate(self.services, &user_id, &all_joined_rooms)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
update_displayname(self.services, &user_id, None, &all_joined_rooms).await;
|
||||
update_avatar_url(self.services, &user_id, None, None, &all_joined_rooms).await;
|
||||
leave_all_rooms(self.services, &user_id).await;
|
||||
@@ -358,7 +361,10 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
full_user_deactivate(self.services, &user_id, &all_joined_rooms).await?;
|
||||
full_user_deactivate(self.services, &user_id, &all_joined_rooms)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
update_displayname(self.services, &user_id, None, &all_joined_rooms).await;
|
||||
update_avatar_url(self.services, &user_id, None, None, &all_joined_rooms)
|
||||
.await;
|
||||
|
||||
+9
-3
@@ -22,6 +22,9 @@ brotli_compression = [
|
||||
"tuwunel-service/brotli_compression",
|
||||
"reqwest/brotli",
|
||||
]
|
||||
bzip2_compression = [
|
||||
"tuwunel-service/bzip2_compression",
|
||||
]
|
||||
element_hacks = [
|
||||
"tuwunel-service/element_hacks",
|
||||
]
|
||||
@@ -52,6 +55,9 @@ jemalloc_stats = [
|
||||
ldap = [
|
||||
"tuwunel-service/ldap",
|
||||
]
|
||||
lz4_compression = [
|
||||
"tuwunel-service/lz4_compression",
|
||||
]
|
||||
release_max_log_level = [
|
||||
"tuwunel-core/release_max_log_level",
|
||||
"tuwunel-service/release_max_log_level",
|
||||
@@ -73,10 +79,8 @@ axum-extra.workspace = true
|
||||
axum.workspace = true
|
||||
base64.workspace = true
|
||||
bytes.workspace = true
|
||||
ctor.workspace = true
|
||||
tuwunel-core.workspace = true
|
||||
tuwunel-service.workspace = true
|
||||
const-str.workspace = true
|
||||
ctor.workspace = true
|
||||
futures.workspace = true
|
||||
hmac.workspace = true
|
||||
http.workspace = true
|
||||
@@ -94,6 +98,8 @@ serde.workspace = true
|
||||
sha1.workspace = true
|
||||
tokio.workspace = true
|
||||
tracing.workspace = true
|
||||
tuwunel-core.workspace = true
|
||||
tuwunel-service.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
+5
-589
@@ -1,29 +1,23 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use register::RegistrationKind;
|
||||
use ruma::{
|
||||
OwnedRoomId, UserId,
|
||||
api::client::{
|
||||
account::{
|
||||
ThirdPartyIdRemovalStatus, change_password, check_registration_token_validity,
|
||||
deactivate, get_3pids, get_username_availability,
|
||||
register::{self, LoginType},
|
||||
ThirdPartyIdRemovalStatus, change_password, deactivate, get_3pids,
|
||||
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn,
|
||||
whoami,
|
||||
},
|
||||
uiaa::{AuthFlow, AuthType, UiaaInfo},
|
||||
},
|
||||
events::{
|
||||
GlobalAccountDataEventType, StateEventType,
|
||||
StateEventType,
|
||||
room::power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
||||
},
|
||||
push,
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Err, Error, Result, debug_info, err, error, info, is_equal_to,
|
||||
Err, Error, Result, err, info,
|
||||
matrix::{Event, pdu::PduBuilder},
|
||||
utils,
|
||||
utils::{ReadyExt, stream::BroadbandExt},
|
||||
@@ -31,570 +25,9 @@
|
||||
};
|
||||
use tuwunel_service::Services;
|
||||
|
||||
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH, join_room_by_id_helper};
|
||||
use super::SESSION_ID_LENGTH;
|
||||
use crate::Ruma;
|
||||
|
||||
const RANDOM_USER_ID_LENGTH: usize = 10;
|
||||
|
||||
/// # `GET /_matrix/client/v3/register/available`
|
||||
///
|
||||
/// Checks if a username is valid and available on this server.
|
||||
///
|
||||
/// Conditions for returning true:
|
||||
/// - The user id is not historical
|
||||
/// - The server name of the user id matches this server
|
||||
/// - No user or appservice on this server already claimed this username
|
||||
///
|
||||
/// Note: This will not reserve the username, so the username might become
|
||||
/// invalid when trying to register
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "register_available")]
|
||||
pub(crate) async fn get_register_available_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_username_availability::v3::Request>,
|
||||
) -> Result<get_username_availability::v3::Response> {
|
||||
// workaround for https://github.com/matrix-org/matrix-appservice-irc/issues/1780 due to inactivity of fixing the issue
|
||||
let is_matrix_appservice_irc = body
|
||||
.appservice_info
|
||||
.as_ref()
|
||||
.is_some_and(|appservice| {
|
||||
appservice.registration.id == "irc"
|
||||
|| appservice
|
||||
.registration
|
||||
.id
|
||||
.contains("matrix-appservice-irc")
|
||||
|| appservice
|
||||
.registration
|
||||
.id
|
||||
.contains("matrix_appservice_irc")
|
||||
});
|
||||
|
||||
if services
|
||||
.globals
|
||||
.forbidden_usernames()
|
||||
.is_match(&body.username)
|
||||
{
|
||||
return Err!(Request(Forbidden("Username is forbidden")));
|
||||
}
|
||||
|
||||
// don't force the username lowercase if it's from matrix-appservice-irc
|
||||
let body_username = if is_matrix_appservice_irc {
|
||||
body.username.clone()
|
||||
} else {
|
||||
body.username.to_lowercase()
|
||||
};
|
||||
|
||||
// Validate user id
|
||||
let user_id =
|
||||
match UserId::parse_with_server_name(&body_username, services.globals.server_name()) {
|
||||
| Ok(user_id) => {
|
||||
if let Err(e) = user_id.validate_strict() {
|
||||
// unless the username is from the broken matrix appservice IRC bridge, we
|
||||
// should follow synapse's behaviour on not allowing things like spaces
|
||||
// and UTF-8 characters in usernames
|
||||
if !is_matrix_appservice_irc {
|
||||
return Err!(Request(InvalidUsername(debug_warn!(
|
||||
"Username {body_username} contains disallowed characters or spaces: \
|
||||
{e}"
|
||||
))));
|
||||
}
|
||||
}
|
||||
|
||||
user_id
|
||||
},
|
||||
| Err(e) => {
|
||||
return Err!(Request(InvalidUsername(debug_warn!(
|
||||
"Username {body_username} is not valid: {e}"
|
||||
))));
|
||||
},
|
||||
};
|
||||
|
||||
// Check if username is creative enough
|
||||
if services.users.exists(&user_id).await {
|
||||
return Err!(Request(UserInUse("User ID is not available.")));
|
||||
}
|
||||
|
||||
if let Some(ref info) = body.appservice_info {
|
||||
if !info.is_user_match(&user_id) {
|
||||
return Err!(Request(Exclusive("Username is not in an appservice namespace.")));
|
||||
}
|
||||
}
|
||||
|
||||
if services
|
||||
.appservice
|
||||
.is_exclusive_user_id(&user_id)
|
||||
.await
|
||||
{
|
||||
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
|
||||
}
|
||||
|
||||
Ok(get_username_availability::v3::Response { available: true })
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/v3/register`
|
||||
///
|
||||
/// Register an account on this homeserver.
|
||||
///
|
||||
/// You can use [`GET
|
||||
/// /_matrix/client/v3/register/available`](fn.get_register_available_route.
|
||||
/// html) to check if the user id is valid and available.
|
||||
///
|
||||
/// - Only works if registration is enabled
|
||||
/// - If type is guest: ignores all parameters except
|
||||
/// initial_device_display_name
|
||||
/// - If sender is not appservice: Requires UIAA (but we only use a dummy stage)
|
||||
/// - If type is not guest and no username is given: Always fails after UIAA
|
||||
/// check
|
||||
/// - Creates a new account and populates it with default account data
|
||||
/// - If `inhibit_login` is false: Creates a device and returns device id and
|
||||
/// access_token
|
||||
#[allow(clippy::doc_markdown)]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "register")]
|
||||
pub(crate) async fn register_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<register::v3::Request>,
|
||||
) -> Result<register::v3::Response> {
|
||||
let is_guest = body.kind == RegistrationKind::Guest;
|
||||
let emergency_mode_enabled = services.config.emergency_password.is_some();
|
||||
|
||||
if !services.config.allow_registration && body.appservice_info.is_none() {
|
||||
match (body.username.as_ref(), body.initial_device_display_name.as_ref()) {
|
||||
| (Some(username), Some(device_display_name)) => {
|
||||
info!(
|
||||
%is_guest,
|
||||
user = %username,
|
||||
device_name = %device_display_name,
|
||||
"Rejecting registration attempt as registration is disabled"
|
||||
);
|
||||
},
|
||||
| (Some(username), _) => {
|
||||
info!(
|
||||
%is_guest,
|
||||
user = %username,
|
||||
"Rejecting registration attempt as registration is disabled"
|
||||
);
|
||||
},
|
||||
| (_, Some(device_display_name)) => {
|
||||
info!(
|
||||
%is_guest,
|
||||
device_name = %device_display_name,
|
||||
"Rejecting registration attempt as registration is disabled"
|
||||
);
|
||||
},
|
||||
| (None, _) => {
|
||||
info!(
|
||||
%is_guest,
|
||||
"Rejecting registration attempt as registration is disabled"
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
return Err!(Request(Forbidden("Registration has been disabled.")));
|
||||
}
|
||||
|
||||
if is_guest
|
||||
&& (!services.config.allow_guest_registration
|
||||
|| (services.config.allow_registration
|
||||
&& services.globals.registration_token.is_some()))
|
||||
{
|
||||
info!(
|
||||
"Guest registration disabled / registration enabled with token configured, \
|
||||
rejecting guest registration attempt, initial device name: \"{}\"",
|
||||
body.initial_device_display_name
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
);
|
||||
return Err!(Request(GuestAccessForbidden("Guest registration is disabled.")));
|
||||
}
|
||||
|
||||
// forbid guests from registering if there is not a real admin user yet. give
|
||||
// generic user error.
|
||||
if is_guest && services.users.count().await < 2 {
|
||||
warn!(
|
||||
"Guest account attempted to register before a real admin user has been registered, \
|
||||
rejecting registration. Guest's initial device name: \"{}\"",
|
||||
body.initial_device_display_name
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
);
|
||||
return Err!(Request(Forbidden("Registration is temporarily disabled.")));
|
||||
}
|
||||
|
||||
let user_id = match (body.username.as_ref(), is_guest) {
|
||||
| (Some(username), false) => {
|
||||
// workaround for https://github.com/matrix-org/matrix-appservice-irc/issues/1780 due to inactivity of fixing the issue
|
||||
let is_matrix_appservice_irc =
|
||||
body.appservice_info
|
||||
.as_ref()
|
||||
.is_some_and(|appservice| {
|
||||
appservice.registration.id == "irc"
|
||||
|| appservice
|
||||
.registration
|
||||
.id
|
||||
.contains("matrix-appservice-irc")
|
||||
|| appservice
|
||||
.registration
|
||||
.id
|
||||
.contains("matrix_appservice_irc")
|
||||
});
|
||||
|
||||
if services
|
||||
.globals
|
||||
.forbidden_usernames()
|
||||
.is_match(username)
|
||||
&& !emergency_mode_enabled
|
||||
{
|
||||
return Err!(Request(Forbidden("Username is forbidden")));
|
||||
}
|
||||
|
||||
// don't force the username lowercase if it's from matrix-appservice-irc
|
||||
let body_username = if is_matrix_appservice_irc {
|
||||
username.clone()
|
||||
} else {
|
||||
username.to_lowercase()
|
||||
};
|
||||
|
||||
let proposed_user_id = match UserId::parse_with_server_name(
|
||||
&body_username,
|
||||
services.globals.server_name(),
|
||||
) {
|
||||
| Ok(user_id) => {
|
||||
if let Err(e) = user_id.validate_strict() {
|
||||
// unless the username is from the broken matrix appservice IRC bridge, or
|
||||
// we are in emergency mode, we should follow synapse's behaviour on
|
||||
// not allowing things like spaces and UTF-8 characters in usernames
|
||||
if !is_matrix_appservice_irc && !emergency_mode_enabled {
|
||||
return Err!(Request(InvalidUsername(debug_warn!(
|
||||
"Username {body_username} contains disallowed characters or \
|
||||
spaces: {e}"
|
||||
))));
|
||||
}
|
||||
}
|
||||
|
||||
user_id
|
||||
},
|
||||
| Err(e) => {
|
||||
return Err!(Request(InvalidUsername(debug_warn!(
|
||||
"Username {body_username} is not valid: {e}"
|
||||
))));
|
||||
},
|
||||
};
|
||||
|
||||
if services.users.exists(&proposed_user_id).await {
|
||||
return Err!(Request(UserInUse("User ID is not available.")));
|
||||
}
|
||||
|
||||
proposed_user_id
|
||||
},
|
||||
| _ => loop {
|
||||
let proposed_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(&proposed_user_id).await {
|
||||
break proposed_user_id;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if body.body.login_type == Some(LoginType::ApplicationService) {
|
||||
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
|
||||
{
|
||||
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
|
||||
}
|
||||
|
||||
// UIAA
|
||||
let mut uiaainfo;
|
||||
let skip_auth = if services.globals.registration_token.is_some() {
|
||||
// Registration token required
|
||||
uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow {
|
||||
stages: vec![AuthType::RegistrationToken],
|
||||
}],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
body.appservice_info.is_some()
|
||||
} else {
|
||||
// No registration token necessary, but clients must still go through the flow
|
||||
uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Dummy] }],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
body.appservice_info.is_some() || is_guest
|
||||
};
|
||||
|
||||
if !skip_auth {
|
||||
match &body.auth {
|
||||
| Some(auth) => {
|
||||
let (worked, uiaainfo) = services
|
||||
.uiaa
|
||||
.try_auth(
|
||||
&UserId::parse_with_server_name("", services.globals.server_name())
|
||||
.unwrap(),
|
||||
"".into(),
|
||||
auth,
|
||||
&uiaainfo,
|
||||
)
|
||||
.await?;
|
||||
if !worked {
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
}
|
||||
// Success!
|
||||
},
|
||||
| _ => match body.json_body {
|
||||
| Some(ref json) => {
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
services.uiaa.create(
|
||||
&UserId::parse_with_server_name("", services.globals.server_name())
|
||||
.unwrap(),
|
||||
"".into(),
|
||||
&uiaainfo,
|
||||
json,
|
||||
);
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
},
|
||||
| _ => {
|
||||
return Err!(Request(NotJson("JSON body is not valid")));
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let password = if is_guest { None } else { body.password.as_deref() };
|
||||
|
||||
// Create user
|
||||
services
|
||||
.users
|
||||
.create(&user_id, password, None)
|
||||
.await?;
|
||||
|
||||
// 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 !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(ruma::events::push_rules::PushRulesEvent {
|
||||
content: ruma::events::push_rules::PushRulesEventContent {
|
||||
global: push::Ruleset::server_default(&user_id),
|
||||
},
|
||||
})?,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if (!is_guest && body.inhibit_login)
|
||||
|| body
|
||||
.appservice_info
|
||||
.as_ref()
|
||||
.is_some_and(|appservice| appservice.registration.device_management)
|
||||
{
|
||||
return Ok(register::v3::Response {
|
||||
access_token: None,
|
||||
user_id,
|
||||
device_id: None,
|
||||
refresh_token: None,
|
||||
expires_in: None,
|
||||
});
|
||||
}
|
||||
|
||||
// Generate new device id if the user didn't specify one
|
||||
let device_id = if is_guest { None } else { body.device_id.clone() }
|
||||
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
|
||||
|
||||
// Generate new token for the device
|
||||
let token = utils::random_string(TOKEN_LENGTH);
|
||||
|
||||
// Create device for this account
|
||||
services
|
||||
.users
|
||||
.create_device(
|
||||
&user_id,
|
||||
&device_id,
|
||||
&token,
|
||||
body.initial_device_display_name.clone(),
|
||||
Some(client.to_string()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
debug_info!(%user_id, %device_id, "User account was created");
|
||||
|
||||
let device_display_name = body
|
||||
.initial_device_display_name
|
||||
.as_deref()
|
||||
.unwrap_or("");
|
||||
|
||||
// log in conduit admin channel if a non-guest user registered
|
||||
if body.appservice_info.is_none() && !is_guest {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log in conduit admin channel if a guest registered
|
||||
if body.appservice_info.is_none() && is_guest && services.config.log_guest_registrations {
|
||||
debug_info!("New guest user \"{user_id}\" registered on this server.");
|
||||
|
||||
if !device_display_name.is_empty() {
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.notice(&format!(
|
||||
"Guest user \"{user_id}\" with device display name \
|
||||
\"{device_display_name}\" registered on this server from IP {client}"
|
||||
))
|
||||
.await;
|
||||
}
|
||||
} else {
|
||||
#[allow(clippy::collapsible_else_if)]
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.notice(&format!(
|
||||
"Guest user \"{user_id}\" with no device display name registered on \
|
||||
this server from IP {client}",
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the first real user, grant them admin privileges except for guest
|
||||
// users
|
||||
// Note: the server user is generated first
|
||||
if !is_guest {
|
||||
if let Ok(admin_room) = services.admin.get_admin_room().await {
|
||||
if services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_joined_count(&admin_room)
|
||||
.await
|
||||
.is_ok_and(is_equal_to!(1))
|
||||
{
|
||||
services.admin.make_user_admin(&user_id).await?;
|
||||
warn!("Granting {user_id} admin privileges as the first user");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if body.appservice_info.is_none()
|
||||
&& !services.server.config.auto_join_rooms.is_empty()
|
||||
&& (services.config.allow_guests_auto_join_rooms || !is_guest)
|
||||
{
|
||||
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 join_room_by_id_helper(
|
||||
&services,
|
||||
&user_id,
|
||||
&room_id,
|
||||
Some("Automatically joining this room upon registration".to_owned()),
|
||||
&[services.globals.server_name().to_owned(), room_server_name.to_owned()],
|
||||
None,
|
||||
&body.appservice_info,
|
||||
)
|
||||
.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(register::v3::Response {
|
||||
access_token: Some(token),
|
||||
user_id,
|
||||
device_id: Some(device_id),
|
||||
refresh_token: None,
|
||||
expires_in: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/r0/account/password`
|
||||
///
|
||||
/// Changes the password of this account.
|
||||
@@ -861,23 +294,6 @@ pub(crate) async fn request_3pid_management_token_via_msisdn_route(
|
||||
Err!(Request(ThreepidDenied("Third party identifiers are not implemented")))
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/v1/register/m.login.registration_token/validity`
|
||||
///
|
||||
/// Checks if the provided registration token is valid at the time of checking
|
||||
///
|
||||
/// Currently does not have any ratelimiting, and this isn't very practical as
|
||||
/// there is only one registration token allowed.
|
||||
pub(crate) async fn check_registration_token_validity(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<check_registration_token_validity::v1::Request>,
|
||||
) -> Result<check_registration_token_validity::v1::Response> {
|
||||
let Some(reg_token) = services.globals.registration_token.clone() else {
|
||||
return Err!(Request(Forbidden("Server does not allow token registration")));
|
||||
};
|
||||
|
||||
Ok(check_registration_token_validity::v1::Response { valid: reg_token == body.token })
|
||||
}
|
||||
|
||||
/// Runs through all the deactivation steps:
|
||||
///
|
||||
/// - Mark as deactivated
|
||||
@@ -889,7 +305,7 @@ pub async fn full_user_deactivate(
|
||||
services: &Services,
|
||||
user_id: &UserId,
|
||||
all_joined_rooms: &[OwnedRoomId],
|
||||
) -> Result<()> {
|
||||
) -> Result {
|
||||
services
|
||||
.users
|
||||
.deactivate_account(user_id)
|
||||
|
||||
@@ -132,7 +132,7 @@ pub(crate) async fn get_context_route(
|
||||
let state_at = events_after
|
||||
.last()
|
||||
.map(ref_at!(1))
|
||||
.map_or(body.event_id.as_ref(), |pdu| pdu.event_id.as_ref());
|
||||
.map_or_else(|| body.event_id.as_ref(), |pdu| pdu.event_id.as_ref());
|
||||
|
||||
let state_ids = services
|
||||
.rooms
|
||||
|
||||
@@ -100,7 +100,8 @@ pub(crate) async fn update_device_route(
|
||||
.create_device(
|
||||
sender_user,
|
||||
&device_id,
|
||||
&appservice.registration.as_token,
|
||||
(&appservice.registration.as_token, None),
|
||||
None,
|
||||
None,
|
||||
Some(client.to_string()),
|
||||
)
|
||||
|
||||
+60
-32
@@ -33,7 +33,7 @@
|
||||
TryFutureExtExt,
|
||||
math::Expected,
|
||||
result::FlatOk,
|
||||
stream::{ReadyExt, WidebandExt},
|
||||
stream::{IterStream, ReadyExt, WidebandExt},
|
||||
},
|
||||
};
|
||||
use tuwunel_service::Services;
|
||||
@@ -51,19 +51,7 @@ pub(crate) async fn get_public_rooms_filtered_route(
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_public_rooms_filtered::v3::Request>,
|
||||
) -> Result<get_public_rooms_filtered::v3::Response> {
|
||||
if let Some(server) = &body.server {
|
||||
if services
|
||||
.config
|
||||
.forbidden_remote_room_directory_server_names
|
||||
.is_match(server.host())
|
||||
|| services
|
||||
.config
|
||||
.forbidden_remote_server_names
|
||||
.is_match(server.host())
|
||||
{
|
||||
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
|
||||
}
|
||||
}
|
||||
check_banned(&services, body.server.as_deref())?;
|
||||
|
||||
let response = get_public_rooms_filtered_helper(
|
||||
&services,
|
||||
@@ -92,19 +80,7 @@ pub(crate) async fn get_public_rooms_route(
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_public_rooms::v3::Request>,
|
||||
) -> Result<get_public_rooms::v3::Response> {
|
||||
if let Some(server) = &body.server {
|
||||
if services
|
||||
.config
|
||||
.forbidden_remote_room_directory_server_names
|
||||
.is_match(server.host())
|
||||
|| services
|
||||
.config
|
||||
.forbidden_remote_server_names
|
||||
.is_match(server.host())
|
||||
{
|
||||
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
|
||||
}
|
||||
}
|
||||
check_banned(&services, body.server.as_deref())?;
|
||||
|
||||
let response = get_public_rooms_filtered_helper(
|
||||
&services,
|
||||
@@ -308,32 +284,62 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
||||
}
|
||||
}
|
||||
|
||||
let search_term = filter
|
||||
.generic_search_term
|
||||
.as_deref()
|
||||
.map(str::to_lowercase);
|
||||
|
||||
let search_room_id = filter
|
||||
.generic_search_term
|
||||
.as_deref()
|
||||
.filter(|_| services.config.allow_public_room_search_by_id)
|
||||
.filter(|s| s.starts_with('!'))
|
||||
.filter(|s| s.len() > 5); // require some characters to limit scope.
|
||||
|
||||
let meta_public_rooms = search_room_id
|
||||
.filter(|_| services.config.allow_unlisted_room_search_by_id)
|
||||
.map(|prefix| services.rooms.metadata.public_ids_prefix(prefix))
|
||||
.into_iter()
|
||||
.stream()
|
||||
.flatten();
|
||||
|
||||
let mut all_rooms: Vec<PublicRoomsChunk> = services
|
||||
.rooms
|
||||
.directory
|
||||
.public_rooms()
|
||||
.map(ToOwned::to_owned)
|
||||
.chain(meta_public_rooms)
|
||||
.wide_then(|room_id| public_rooms_chunk(services, room_id))
|
||||
.ready_filter_map(|chunk| {
|
||||
if !filter.room_types.is_empty() && !filter.room_types.contains(&RoomTypeFilter::from(chunk.room_type.clone())) {
|
||||
if !filter.room_types.is_empty()
|
||||
&& !filter
|
||||
.room_types
|
||||
.contains(&RoomTypeFilter::from(chunk.room_type.clone()))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(query) = filter.generic_search_term.as_ref().map(|q| q.to_lowercase()) {
|
||||
if let Some(query) = search_room_id {
|
||||
if chunk.room_id.as_str().contains(query) {
|
||||
return Some(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(query) = search_term.as_deref() {
|
||||
if let Some(name) = &chunk.name {
|
||||
if name.as_str().to_lowercase().contains(&query) {
|
||||
if name.as_str().to_lowercase().contains(query) {
|
||||
return Some(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(topic) = &chunk.topic {
|
||||
if topic.to_lowercase().contains(&query) {
|
||||
if topic.to_lowercase().contains(query) {
|
||||
return Some(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(canonical_alias) = &chunk.canonical_alias {
|
||||
if canonical_alias.as_str().to_lowercase().contains(&query) {
|
||||
if canonical_alias.as_str().to_lowercase().contains(query) {
|
||||
return Some(chunk);
|
||||
}
|
||||
}
|
||||
@@ -490,3 +496,25 @@ async fn public_rooms_chunk(services: &Services, room_id: OwnedRoomId) -> Public
|
||||
world_readable,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_banned(services: &Services, server: Option<&ServerName>) -> Result {
|
||||
let Some(server) = server else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let forbidden_remote_directory = services
|
||||
.config
|
||||
.forbidden_remote_room_directory_server_names
|
||||
.is_match(server.host());
|
||||
|
||||
let forbidden_remote_server = services
|
||||
.config
|
||||
.forbidden_remote_server_names
|
||||
.is_match(server.host());
|
||||
|
||||
if forbidden_remote_directory || forbidden_remote_server {
|
||||
Err!(Request(Forbidden("Server is banned on this homeserver.")))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,7 +526,7 @@ pub(crate) async fn get_keys_helper<F>(
|
||||
|
||||
let mut futures: FuturesUnordered<_> = get_over_federation
|
||||
.into_iter()
|
||||
.map(|(server, vec)| async move {
|
||||
.map(async |(server, vec)| {
|
||||
let mut device_keys_input_fed = BTreeMap::new();
|
||||
for (user_id, keys) in vec {
|
||||
device_keys_input_fed.insert(user_id.to_owned(), keys.clone());
|
||||
@@ -597,7 +597,7 @@ fn add_unsigned_device_display_name(
|
||||
keys: &mut Raw<ruma::encryption::DeviceKeys>,
|
||||
metadata: ruma::api::client::device::Device,
|
||||
include_display_names: bool,
|
||||
) -> serde_json::Result<()> {
|
||||
) -> Result {
|
||||
if let Some(display_name) = metadata.display_name {
|
||||
let mut object = keys.deserialize_as::<serde_json::Map<String, serde_json::Value>>()?;
|
||||
|
||||
@@ -656,7 +656,7 @@ pub(crate) async fn claim_keys_helper(
|
||||
|
||||
let mut futures: FuturesUnordered<_> = get_over_federation
|
||||
.into_iter()
|
||||
.map(|(server, vec)| async move {
|
||||
.map(async |(server, vec)| {
|
||||
let mut one_time_keys_input_fed = BTreeMap::new();
|
||||
for (user_id, keys) in vec {
|
||||
one_time_keys_input_fed.insert(user_id.clone(), keys.clone());
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use futures::{FutureExt, StreamExt, pin_mut};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId,
|
||||
RoomVersionId, UserId,
|
||||
@@ -550,7 +550,7 @@ async fn join_room_by_id_helper_remote(
|
||||
.validate_and_add_event_id_no_fetch(pdu, &room_version_id)
|
||||
})
|
||||
.ready_filter_map(Result::ok)
|
||||
.fold(HashMap::new(), |mut state, (event_id, value)| async move {
|
||||
.fold(HashMap::new(), async |mut state, (event_id, value)| {
|
||||
let pdu = match PduEvent::from_id_val(&event_id, value.clone()) {
|
||||
| Ok(pdu) => pdu,
|
||||
| Err(e) => {
|
||||
@@ -561,8 +561,9 @@ async fn join_room_by_id_helper_remote(
|
||||
|
||||
services
|
||||
.rooms
|
||||
.outlier
|
||||
.timeline
|
||||
.add_pdu_outlier(&event_id, &value);
|
||||
|
||||
if let Some(state_key) = &pdu.state_key {
|
||||
let shortstatekey = services
|
||||
.rooms
|
||||
@@ -595,7 +596,7 @@ async fn join_room_by_id_helper_remote(
|
||||
.ready_for_each(|(event_id, value)| {
|
||||
services
|
||||
.rooms
|
||||
.outlier
|
||||
.timeline
|
||||
.add_pdu_outlier(&event_id, &value);
|
||||
})
|
||||
.await;
|
||||
@@ -747,7 +748,7 @@ async fn join_room_by_id_helper_local(
|
||||
})
|
||||
.await
|
||||
{
|
||||
services
|
||||
let users = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.local_users_in_room(room_id)
|
||||
@@ -759,10 +760,10 @@ async fn join_room_by_id_helper_local(
|
||||
&state_lock,
|
||||
)
|
||||
})
|
||||
.boxed()
|
||||
.next()
|
||||
.await
|
||||
.map(ToOwned::to_owned)
|
||||
.map(ToOwned::to_owned);
|
||||
|
||||
pin_mut!(users);
|
||||
users.next().await
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -541,8 +541,9 @@ async fn knock_room_helper_remote(
|
||||
|
||||
services
|
||||
.rooms
|
||||
.outlier
|
||||
.timeline
|
||||
.add_pdu_outlier(&event_id, &event);
|
||||
|
||||
state_map.insert(shortstatekey, event_id.clone());
|
||||
}
|
||||
|
||||
|
||||
@@ -230,11 +230,7 @@ pub async fn leave_room(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remote_leave_room(
|
||||
services: &Services,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
) -> Result<()> {
|
||||
async fn remote_leave_room(services: &Services, user_id: &UserId, room_id: &RoomId) -> Result {
|
||||
let mut make_leave_response_and_server =
|
||||
Err!(BadServerResponse("No remote server available to assist in leaving {room_id}."));
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ pub(crate) async fn joined_members_route(
|
||||
.state_cache
|
||||
.room_members(&body.room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.broad_then(|user_id| async move {
|
||||
.broad_then(async |user_id| {
|
||||
let (display_name, avatar_url) = join(
|
||||
services.users.displayname(&user_id).ok(),
|
||||
services.users.avatar_url(&user_id).ok(),
|
||||
|
||||
@@ -100,7 +100,6 @@ pub(crate) async fn get_message_events_route(
|
||||
.rooms
|
||||
.timeline
|
||||
.backfill_if_required(room_id, from)
|
||||
.boxed()
|
||||
.await
|
||||
.log_err()
|
||||
.ok();
|
||||
@@ -151,9 +150,7 @@ pub(crate) async fn get_message_events_route(
|
||||
.map(IterStream::stream)
|
||||
.into_stream()
|
||||
.flatten()
|
||||
.broad_filter_map(|user_id| async move {
|
||||
get_member_event(&services, room_id, &user_id).await
|
||||
})
|
||||
.broad_filter_map(async |user_id| get_member_event(&services, room_id, &user_id).await)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
@@ -195,10 +192,11 @@ pub(crate) async fn lazy_loading_witness<'a, I>(
|
||||
.max()
|
||||
.unwrap_or_else(PduCount::max);
|
||||
|
||||
let receipts = services
|
||||
.rooms
|
||||
.read_receipt
|
||||
.readreceipts_since(lazy_loading_context.room_id, oldest.into_unsigned());
|
||||
let receipts = services.rooms.read_receipt.readreceipts_since(
|
||||
lazy_loading_context.room_id,
|
||||
oldest.into_unsigned(),
|
||||
Some(newest.into_unsigned()),
|
||||
);
|
||||
|
||||
pin_mut!(receipts);
|
||||
let witness: Witness = events
|
||||
@@ -256,7 +254,7 @@ pub(crate) async fn is_ignored_pdu<Pdu>(
|
||||
user_id: &UserId,
|
||||
) -> bool
|
||||
where
|
||||
Pdu: Event + Send + Sync,
|
||||
Pdu: Event,
|
||||
{
|
||||
// exclude Synapse's dummy events from bloating up response bodies. clients
|
||||
// don't need to see this.
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
pub(super) mod push;
|
||||
pub(super) mod read_marker;
|
||||
pub(super) mod redact;
|
||||
pub(super) mod register;
|
||||
pub(super) mod relations;
|
||||
pub(super) mod report;
|
||||
pub(super) mod room;
|
||||
@@ -63,6 +64,7 @@
|
||||
pub(super) use push::*;
|
||||
pub(super) use read_marker::*;
|
||||
pub(super) use redact::*;
|
||||
pub(super) use register::*;
|
||||
pub(super) use relations::*;
|
||||
pub(super) use report::*;
|
||||
pub(super) use room::*;
|
||||
@@ -87,7 +89,7 @@
|
||||
const DEVICE_ID_LENGTH: usize = 10;
|
||||
|
||||
/// generated user access token length
|
||||
const TOKEN_LENGTH: usize = 32;
|
||||
const TOKEN_LENGTH: usize = tuwunel_service::users::device::TOKEN_LENGTH;
|
||||
|
||||
/// generated user session ID length
|
||||
const SESSION_ID_LENGTH: usize = tuwunel_service::uiaa::SESSION_ID_LENGTH;
|
||||
|
||||
@@ -350,7 +350,7 @@ pub async fn update_displayname(
|
||||
let all_joined_rooms: Vec<_> = all_joined_rooms
|
||||
.iter()
|
||||
.try_stream()
|
||||
.and_then(|room_id: &OwnedRoomId| async move {
|
||||
.and_then(async |room_id: &OwnedRoomId| {
|
||||
let pdu = PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
|
||||
displayname: displayname.clone(),
|
||||
membership: MembershipState::Join,
|
||||
@@ -403,7 +403,7 @@ pub async fn update_avatar_url(
|
||||
let all_joined_rooms: Vec<_> = all_joined_rooms
|
||||
.iter()
|
||||
.try_stream()
|
||||
.and_then(|room_id: &OwnedRoomId| async move {
|
||||
.and_then(async |room_id: &OwnedRoomId| {
|
||||
let pdu = PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
|
||||
avatar_url: avatar_url.clone(),
|
||||
blurhash: blurhash.clone(),
|
||||
|
||||
@@ -0,0 +1,606 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use futures::FutureExt;
|
||||
use register::RegistrationKind;
|
||||
use ruma::{
|
||||
UserId,
|
||||
api::client::{
|
||||
account::{
|
||||
check_registration_token_validity, get_username_availability,
|
||||
register::{self, LoginType},
|
||||
},
|
||||
uiaa::{AuthFlow, AuthType, UiaaInfo},
|
||||
},
|
||||
events::GlobalAccountDataEventType,
|
||||
push,
|
||||
};
|
||||
use tuwunel_core::{Err, Error, Result, debug_info, error, info, is_equal_to, utils, warn};
|
||||
use tuwunel_service::users::device::generate_refresh_token;
|
||||
|
||||
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH, join_room_by_id_helper};
|
||||
use crate::Ruma;
|
||||
|
||||
const RANDOM_USER_ID_LENGTH: usize = 10;
|
||||
|
||||
/// # `GET /_matrix/client/v3/register/available`
|
||||
///
|
||||
/// Checks if a username is valid and available on this server.
|
||||
///
|
||||
/// Conditions for returning true:
|
||||
/// - The user id is not historical
|
||||
/// - The server name of the user id matches this server
|
||||
/// - No user or appservice on this server already claimed this username
|
||||
///
|
||||
/// Note: This will not reserve the username, so the username might become
|
||||
/// invalid when trying to register
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "register_available")]
|
||||
pub(crate) async fn get_register_available_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_username_availability::v3::Request>,
|
||||
) -> Result<get_username_availability::v3::Response> {
|
||||
// workaround for https://github.com/matrix-org/matrix-appservice-irc/issues/1780 due to inactivity of fixing the issue
|
||||
let is_matrix_appservice_irc = body
|
||||
.appservice_info
|
||||
.as_ref()
|
||||
.is_some_and(|appservice| {
|
||||
appservice.registration.id == "irc"
|
||||
|| appservice
|
||||
.registration
|
||||
.id
|
||||
.contains("matrix-appservice-irc")
|
||||
|| appservice
|
||||
.registration
|
||||
.id
|
||||
.contains("matrix_appservice_irc")
|
||||
});
|
||||
|
||||
if services
|
||||
.globals
|
||||
.forbidden_usernames()
|
||||
.is_match(&body.username)
|
||||
{
|
||||
return Err!(Request(Forbidden("Username is forbidden")));
|
||||
}
|
||||
|
||||
// don't force the username lowercase if it's from matrix-appservice-irc
|
||||
let body_username = if is_matrix_appservice_irc {
|
||||
body.username.clone()
|
||||
} else {
|
||||
body.username.to_lowercase()
|
||||
};
|
||||
|
||||
// Validate user id
|
||||
let user_id =
|
||||
match UserId::parse_with_server_name(&body_username, services.globals.server_name()) {
|
||||
| Ok(user_id) => {
|
||||
if let Err(e) = user_id.validate_strict() {
|
||||
// unless the username is from the broken matrix appservice IRC bridge, we
|
||||
// should follow synapse's behaviour on not allowing things like spaces
|
||||
// and UTF-8 characters in usernames
|
||||
if !is_matrix_appservice_irc {
|
||||
return Err!(Request(InvalidUsername(debug_warn!(
|
||||
"Username {body_username} contains disallowed characters or spaces: \
|
||||
{e}"
|
||||
))));
|
||||
}
|
||||
}
|
||||
|
||||
user_id
|
||||
},
|
||||
| Err(e) => {
|
||||
return Err!(Request(InvalidUsername(debug_warn!(
|
||||
"Username {body_username} is not valid: {e}"
|
||||
))));
|
||||
},
|
||||
};
|
||||
|
||||
// Check if username is creative enough
|
||||
if services.users.exists(&user_id).await {
|
||||
return Err!(Request(UserInUse("User ID is not available.")));
|
||||
}
|
||||
|
||||
if let Some(ref info) = body.appservice_info {
|
||||
if !info.is_user_match(&user_id) {
|
||||
return Err!(Request(Exclusive("Username is not in an appservice namespace.")));
|
||||
}
|
||||
}
|
||||
|
||||
if services
|
||||
.appservice
|
||||
.is_exclusive_user_id(&user_id)
|
||||
.await
|
||||
{
|
||||
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
|
||||
}
|
||||
|
||||
Ok(get_username_availability::v3::Response { available: true })
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/v3/register`
|
||||
///
|
||||
/// Register an account on this homeserver.
|
||||
///
|
||||
/// You can use [`GET
|
||||
/// /_matrix/client/v3/register/available`](fn.get_register_available_route.
|
||||
/// html) to check if the user id is valid and available.
|
||||
///
|
||||
/// - Only works if registration is enabled
|
||||
/// - If type is guest: ignores all parameters except
|
||||
/// initial_device_display_name
|
||||
/// - If sender is not appservice: Requires UIAA (but we only use a dummy stage)
|
||||
/// - If type is not guest and no username is given: Always fails after UIAA
|
||||
/// check
|
||||
/// - Creates a new account and populates it with default account data
|
||||
/// - If `inhibit_login` is false: Creates a device and returns device id and
|
||||
/// access_token
|
||||
#[allow(clippy::doc_markdown)]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "register")]
|
||||
pub(crate) async fn register_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<register::v3::Request>,
|
||||
) -> Result<register::v3::Response> {
|
||||
let is_guest = body.kind == RegistrationKind::Guest;
|
||||
let emergency_mode_enabled = services.config.emergency_password.is_some();
|
||||
|
||||
if !services.config.allow_registration && body.appservice_info.is_none() {
|
||||
match (body.username.as_ref(), body.initial_device_display_name.as_ref()) {
|
||||
| (Some(username), Some(device_display_name)) => {
|
||||
info!(
|
||||
%is_guest,
|
||||
user = %username,
|
||||
device_name = %device_display_name,
|
||||
"Rejecting registration attempt as registration is disabled"
|
||||
);
|
||||
},
|
||||
| (Some(username), _) => {
|
||||
info!(
|
||||
%is_guest,
|
||||
user = %username,
|
||||
"Rejecting registration attempt as registration is disabled"
|
||||
);
|
||||
},
|
||||
| (_, Some(device_display_name)) => {
|
||||
info!(
|
||||
%is_guest,
|
||||
device_name = %device_display_name,
|
||||
"Rejecting registration attempt as registration is disabled"
|
||||
);
|
||||
},
|
||||
| (None, _) => {
|
||||
info!(
|
||||
%is_guest,
|
||||
"Rejecting registration attempt as registration is disabled"
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
return Err!(Request(Forbidden("Registration has been disabled.")));
|
||||
}
|
||||
|
||||
if is_guest
|
||||
&& (!services.config.allow_guest_registration
|
||||
|| (services.config.allow_registration
|
||||
&& services.globals.registration_token.is_some()))
|
||||
{
|
||||
info!(
|
||||
"Guest registration disabled / registration enabled with token configured, \
|
||||
rejecting guest registration attempt, initial device name: \"{}\"",
|
||||
body.initial_device_display_name
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
);
|
||||
return Err!(Request(GuestAccessForbidden("Guest registration is disabled.")));
|
||||
}
|
||||
|
||||
// forbid guests from registering if there is not a real admin user yet. give
|
||||
// generic user error.
|
||||
if is_guest && services.users.count().await < 2 {
|
||||
warn!(
|
||||
"Guest account attempted to register before a real admin user has been registered, \
|
||||
rejecting registration. Guest's initial device name: \"{}\"",
|
||||
body.initial_device_display_name
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
);
|
||||
return Err!(Request(Forbidden("Registration is temporarily disabled.")));
|
||||
}
|
||||
|
||||
let user_id = match (body.username.as_ref(), is_guest) {
|
||||
| (Some(username), false) => {
|
||||
// workaround for https://github.com/matrix-org/matrix-appservice-irc/issues/1780 due to inactivity of fixing the issue
|
||||
let is_matrix_appservice_irc =
|
||||
body.appservice_info
|
||||
.as_ref()
|
||||
.is_some_and(|appservice| {
|
||||
appservice.registration.id == "irc"
|
||||
|| appservice
|
||||
.registration
|
||||
.id
|
||||
.contains("matrix-appservice-irc")
|
||||
|| appservice
|
||||
.registration
|
||||
.id
|
||||
.contains("matrix_appservice_irc")
|
||||
});
|
||||
|
||||
if services
|
||||
.globals
|
||||
.forbidden_usernames()
|
||||
.is_match(username)
|
||||
&& !emergency_mode_enabled
|
||||
{
|
||||
return Err!(Request(Forbidden("Username is forbidden")));
|
||||
}
|
||||
|
||||
// don't force the username lowercase if it's from matrix-appservice-irc
|
||||
let body_username = if is_matrix_appservice_irc {
|
||||
username.clone()
|
||||
} else {
|
||||
username.to_lowercase()
|
||||
};
|
||||
|
||||
let proposed_user_id = match UserId::parse_with_server_name(
|
||||
&body_username,
|
||||
services.globals.server_name(),
|
||||
) {
|
||||
| Ok(user_id) => {
|
||||
if let Err(e) = user_id.validate_strict() {
|
||||
// unless the username is from the broken matrix appservice IRC bridge, or
|
||||
// we are in emergency mode, we should follow synapse's behaviour on
|
||||
// not allowing things like spaces and UTF-8 characters in usernames
|
||||
if !is_matrix_appservice_irc && !emergency_mode_enabled {
|
||||
return Err!(Request(InvalidUsername(debug_warn!(
|
||||
"Username {body_username} contains disallowed characters or \
|
||||
spaces: {e}"
|
||||
))));
|
||||
}
|
||||
}
|
||||
|
||||
user_id
|
||||
},
|
||||
| Err(e) => {
|
||||
return Err!(Request(InvalidUsername(debug_warn!(
|
||||
"Username {body_username} is not valid: {e}"
|
||||
))));
|
||||
},
|
||||
};
|
||||
|
||||
if services.users.exists(&proposed_user_id).await {
|
||||
return Err!(Request(UserInUse("User ID is not available.")));
|
||||
}
|
||||
|
||||
proposed_user_id
|
||||
},
|
||||
| _ => loop {
|
||||
let proposed_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(&proposed_user_id).await {
|
||||
break proposed_user_id;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if body.body.login_type == Some(LoginType::ApplicationService) {
|
||||
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
|
||||
{
|
||||
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
|
||||
}
|
||||
|
||||
// UIAA
|
||||
let mut uiaainfo;
|
||||
let skip_auth = if services.globals.registration_token.is_some() {
|
||||
// Registration token required
|
||||
uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow {
|
||||
stages: vec![AuthType::RegistrationToken],
|
||||
}],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
body.appservice_info.is_some()
|
||||
} else {
|
||||
// No registration token necessary, but clients must still go through the flow
|
||||
uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Dummy] }],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
body.appservice_info.is_some() || is_guest
|
||||
};
|
||||
|
||||
if !skip_auth {
|
||||
match &body.auth {
|
||||
| Some(auth) => {
|
||||
let (worked, uiaainfo) = services
|
||||
.uiaa
|
||||
.try_auth(
|
||||
&UserId::parse_with_server_name("", services.globals.server_name())
|
||||
.unwrap(),
|
||||
"".into(),
|
||||
auth,
|
||||
&uiaainfo,
|
||||
)
|
||||
.await?;
|
||||
if !worked {
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
}
|
||||
// Success!
|
||||
},
|
||||
| _ => match body.json_body {
|
||||
| Some(ref json) => {
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
services.uiaa.create(
|
||||
&UserId::parse_with_server_name("", services.globals.server_name())
|
||||
.unwrap(),
|
||||
"".into(),
|
||||
&uiaainfo,
|
||||
json,
|
||||
);
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
},
|
||||
| _ => {
|
||||
return Err!(Request(NotJson("JSON body is not valid")));
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let password = if is_guest { None } else { body.password.as_deref() };
|
||||
|
||||
// Create user
|
||||
services
|
||||
.users
|
||||
.create(&user_id, password, None)
|
||||
.await?;
|
||||
|
||||
// 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 !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(ruma::events::push_rules::PushRulesEvent {
|
||||
content: ruma::events::push_rules::PushRulesEventContent {
|
||||
global: push::Ruleset::server_default(&user_id),
|
||||
},
|
||||
})?,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if (!is_guest && body.inhibit_login)
|
||||
|| body
|
||||
.appservice_info
|
||||
.as_ref()
|
||||
.is_some_and(|appservice| appservice.registration.device_management)
|
||||
{
|
||||
return Ok(register::v3::Response {
|
||||
access_token: None,
|
||||
user_id,
|
||||
device_id: None,
|
||||
refresh_token: None,
|
||||
expires_in: None,
|
||||
});
|
||||
}
|
||||
|
||||
// Generate new device id if the user didn't specify one
|
||||
let device_id = if is_guest { None } else { body.device_id.clone() }
|
||||
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
|
||||
|
||||
// Generate new token for the device
|
||||
let (access_token, expires_in) = services
|
||||
.users
|
||||
.generate_access_token(body.body.refresh_token);
|
||||
|
||||
// Generate a new refresh_token if requested by client
|
||||
let refresh_token = expires_in.is_some().then(generate_refresh_token);
|
||||
|
||||
// Create device for this account
|
||||
services
|
||||
.users
|
||||
.create_device(
|
||||
&user_id,
|
||||
&device_id,
|
||||
(&access_token, expires_in),
|
||||
refresh_token.as_deref(),
|
||||
body.initial_device_display_name.clone(),
|
||||
Some(client.to_string()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
debug_info!(%user_id, %device_id, "User account was created");
|
||||
|
||||
let device_display_name = body
|
||||
.initial_device_display_name
|
||||
.as_deref()
|
||||
.unwrap_or("");
|
||||
|
||||
// log in conduit admin channel if a non-guest user registered
|
||||
if body.appservice_info.is_none() && !is_guest {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log in conduit admin channel if a guest registered
|
||||
if body.appservice_info.is_none() && is_guest && services.config.log_guest_registrations {
|
||||
debug_info!("New guest user \"{user_id}\" registered on this server.");
|
||||
|
||||
if !device_display_name.is_empty() {
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.notice(&format!(
|
||||
"Guest user \"{user_id}\" with device display name \
|
||||
\"{device_display_name}\" registered on this server from IP {client}"
|
||||
))
|
||||
.await;
|
||||
}
|
||||
} else {
|
||||
#[allow(clippy::collapsible_else_if)]
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.notice(&format!(
|
||||
"Guest user \"{user_id}\" with no device display name registered on \
|
||||
this server from IP {client}",
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the first real user, grant them admin privileges except for guest
|
||||
// users
|
||||
// Note: the server user is generated first
|
||||
if !is_guest
|
||||
&& services.config.grant_admin_to_first_user
|
||||
&& let Ok(admin_room) = services.admin.get_admin_room().await
|
||||
&& services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_joined_count(&admin_room)
|
||||
.await
|
||||
.is_ok_and(is_equal_to!(1))
|
||||
{
|
||||
services.admin.make_user_admin(&user_id).await?;
|
||||
warn!("Granting {user_id} admin privileges as the first user");
|
||||
}
|
||||
|
||||
if body.appservice_info.is_none()
|
||||
&& !services.server.config.auto_join_rooms.is_empty()
|
||||
&& (services.config.allow_guests_auto_join_rooms || !is_guest)
|
||||
{
|
||||
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 join_room_by_id_helper(
|
||||
&services,
|
||||
&user_id,
|
||||
&room_id,
|
||||
Some("Automatically joining this room upon registration".to_owned()),
|
||||
&[services.globals.server_name().to_owned(), room_server_name.to_owned()],
|
||||
None,
|
||||
&body.appservice_info,
|
||||
)
|
||||
.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(register::v3::Response {
|
||||
user_id,
|
||||
device_id: Some(device_id),
|
||||
access_token: Some(access_token),
|
||||
refresh_token,
|
||||
expires_in,
|
||||
})
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/v1/register/m.login.registration_token/validity`
|
||||
///
|
||||
/// Checks if the provided registration token is valid at the time of checking
|
||||
///
|
||||
/// Currently does not have any ratelimiting, and this isn't very practical as
|
||||
/// there is only one registration token allowed.
|
||||
pub(crate) async fn check_registration_token_validity(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<check_registration_token_validity::v1::Request>,
|
||||
) -> Result<check_registration_token_validity::v1::Response> {
|
||||
let Some(reg_token) = services.globals.registration_token.clone() else {
|
||||
return Err!(Request(Forbidden("Server does not allow token registration")));
|
||||
};
|
||||
|
||||
Ok(check_registration_token_validity::v1::Response { valid: reg_token == body.token })
|
||||
}
|
||||
@@ -175,7 +175,7 @@ async fn paginate_relations_with_filter(
|
||||
})
|
||||
}
|
||||
|
||||
async fn visibility_filter<Pdu: Event + Send + Sync>(
|
||||
async fn visibility_filter<Pdu: Event>(
|
||||
services: &Services,
|
||||
sender_user: &UserId,
|
||||
item: (PduCount, Pdu),
|
||||
|
||||
@@ -149,7 +149,7 @@ async fn is_event_report_valid(
|
||||
reason: Option<&String>,
|
||||
score: Option<ruma::Int>,
|
||||
pdu: &PduEvent,
|
||||
) -> Result<()> {
|
||||
) -> Result {
|
||||
debug_info!(
|
||||
"Checking if report from user {sender_user} for event {event_id} in room {room_id} is \
|
||||
valid"
|
||||
|
||||
@@ -111,6 +111,7 @@ pub(crate) async fn create_room_route(
|
||||
.short
|
||||
.get_or_create_shortroomid(&room_id)
|
||||
.await;
|
||||
|
||||
let state_lock = services.rooms.state.mutex.lock(&room_id).await;
|
||||
|
||||
let alias: Option<OwnedRoomAliasId> = match body.room_alias_name.as_ref() {
|
||||
@@ -187,6 +188,10 @@ pub(crate) async fn create_room_route(
|
||||
},
|
||||
};
|
||||
|
||||
// Increment and hold the counter; the room will sync atomically to clients
|
||||
// which is preferable.
|
||||
let next_count = services.globals.next_count();
|
||||
|
||||
// 1. The room create event
|
||||
services
|
||||
.rooms
|
||||
@@ -425,8 +430,10 @@ pub(crate) async fn create_room_route(
|
||||
.await?;
|
||||
}
|
||||
|
||||
// 8. Events implied by invite (and TODO: invite_3pid)
|
||||
drop(next_count);
|
||||
drop(state_lock);
|
||||
|
||||
// 8. Events implied by invite (and TODO: invite_3pid)
|
||||
for user_id in &body.invite {
|
||||
if services
|
||||
.users
|
||||
@@ -487,43 +494,32 @@ fn default_power_levels_content(
|
||||
visibility: &room::Visibility,
|
||||
users: BTreeMap<OwnedUserId, Int>,
|
||||
) -> Result<serde_json::Value> {
|
||||
use serde_json::to_value;
|
||||
|
||||
let mut power_levels_content =
|
||||
serde_json::to_value(RoomPowerLevelsEventContent { users, ..Default::default() })
|
||||
.expect("event is valid, we just created it");
|
||||
to_value(RoomPowerLevelsEventContent { users, ..Default::default() })?;
|
||||
|
||||
// secure proper defaults of sensitive/dangerous permissions that moderators
|
||||
// (power level 50) should not have easy access to
|
||||
power_levels_content["events"]["m.room.power_levels"] =
|
||||
serde_json::to_value(100).expect("100 is valid Value");
|
||||
power_levels_content["events"]["m.room.server_acl"] =
|
||||
serde_json::to_value(100).expect("100 is valid Value");
|
||||
power_levels_content["events"]["m.room.tombstone"] =
|
||||
serde_json::to_value(100).expect("100 is valid Value");
|
||||
power_levels_content["events"]["m.room.encryption"] =
|
||||
serde_json::to_value(100).expect("100 is valid Value");
|
||||
power_levels_content["events"]["m.room.history_visibility"] =
|
||||
serde_json::to_value(100).expect("100 is valid Value");
|
||||
power_levels_content["events"]["m.room.power_levels"] = to_value(100)?;
|
||||
power_levels_content["events"]["m.room.server_acl"] = to_value(100)?;
|
||||
power_levels_content["events"]["m.room.tombstone"] = to_value(100)?;
|
||||
power_levels_content["events"]["m.room.encryption"] = to_value(100)?;
|
||||
power_levels_content["events"]["m.room.history_visibility"] = to_value(100)?;
|
||||
|
||||
// always allow users to respond (not post new) to polls. this is primarily
|
||||
// useful in read-only announcement rooms that post a public poll.
|
||||
power_levels_content["events"]["org.matrix.msc3381.poll.response"] =
|
||||
serde_json::to_value(0).expect("0 is valid Value");
|
||||
power_levels_content["events"]["m.poll.response"] =
|
||||
serde_json::to_value(0).expect("0 is valid Value");
|
||||
power_levels_content["events"]["org.matrix.msc3381.poll.response"] = to_value(0)?;
|
||||
power_levels_content["events"]["m.poll.response"] = to_value(0)?;
|
||||
|
||||
// synapse does this too. clients do not expose these permissions. it prevents
|
||||
// default users from calling public rooms, for obvious reasons.
|
||||
if *visibility == room::Visibility::Public {
|
||||
power_levels_content["events"]["m.call.invite"] =
|
||||
serde_json::to_value(50).expect("50 is valid Value");
|
||||
power_levels_content["events"]["m.call"] =
|
||||
serde_json::to_value(50).expect("50 is valid Value");
|
||||
power_levels_content["events"]["m.call.member"] =
|
||||
serde_json::to_value(50).expect("50 is valid Value");
|
||||
power_levels_content["events"]["org.matrix.msc3401.call"] =
|
||||
serde_json::to_value(50).expect("50 is valid Value");
|
||||
power_levels_content["events"]["org.matrix.msc3401.call.member"] =
|
||||
serde_json::to_value(50).expect("50 is valid Value");
|
||||
power_levels_content["events"]["m.call.invite"] = to_value(50)?;
|
||||
power_levels_content["events"]["m.call"] = to_value(50)?;
|
||||
power_levels_content["events"]["m.call.member"] = to_value(50)?;
|
||||
power_levels_content["events"]["org.matrix.msc3401.call"] = to_value(50)?;
|
||||
power_levels_content["events"]["org.matrix.msc3401.call.member"] = to_value(50)?;
|
||||
}
|
||||
|
||||
if let Some(power_level_content_override) = power_level_content_override {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
};
|
||||
use serde_json::{json, value::to_raw_value};
|
||||
use tuwunel_core::{
|
||||
Error, Result, err, info,
|
||||
Err, Error, Result, err,
|
||||
matrix::{Event, StateKey, pdu::PduBuilder},
|
||||
};
|
||||
|
||||
@@ -54,10 +54,8 @@ pub(crate) async fn upgrade_room_route(
|
||||
TRANSFERABLE_STATE_EVENTS.is_sorted(),
|
||||
"TRANSFERABLE_STATE_EVENTS is not sorted"
|
||||
);
|
||||
let sender_user = body
|
||||
.sender_user
|
||||
.as_ref()
|
||||
.expect("user is authenticated");
|
||||
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
if !services
|
||||
.server
|
||||
@@ -134,8 +132,7 @@ pub(crate) async fn upgrade_room_route(
|
||||
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")
|
||||
err!(Request(BadJson(error!("Error forming creation event: {e}"))))
|
||||
})?,
|
||||
);
|
||||
},
|
||||
@@ -150,24 +147,20 @@ pub(crate) async fn upgrade_room_route(
|
||||
"room_version".into(),
|
||||
json!(&body.new_version)
|
||||
.try_into()
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?,
|
||||
.map_err(|_| err!(Request(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"))?,
|
||||
.map_err(|_| err!(Request(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()
|
||||
if serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&create_event_content)?.get())
|
||||
.is_err()
|
||||
{
|
||||
return Err(Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"));
|
||||
return Err!(Request(BadJson("Error forming creation event")));
|
||||
}
|
||||
|
||||
services
|
||||
@@ -176,8 +169,7 @@ pub(crate) async fn upgrade_room_route(
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomCreate,
|
||||
content: to_raw_value(&create_event_content)
|
||||
.expect("event is valid, we just created it"),
|
||||
content: to_raw_value(&create_event_content)?,
|
||||
unsigned: None,
|
||||
state_key: Some(StateKey::new()),
|
||||
redacts: None,
|
||||
@@ -205,8 +197,7 @@ pub(crate) async fn upgrade_room_route(
|
||||
blurhash: services.users.blurhash(sender_user).await.ok(),
|
||||
reason: None,
|
||||
join_authorized_via_users_server: None,
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
})?,
|
||||
unsigned: None,
|
||||
state_key: Some(sender_user.as_str().into()),
|
||||
redacts: None,
|
||||
|
||||
@@ -95,13 +95,13 @@ async fn category_room_events(
|
||||
});
|
||||
|
||||
let results: Vec<_> = rooms
|
||||
.filter_map(|room_id| async move {
|
||||
.filter_map(async |room_id| {
|
||||
check_room_visible(services, sender_user, &room_id, criteria)
|
||||
.await
|
||||
.is_ok()
|
||||
.then_some(room_id)
|
||||
})
|
||||
.filter_map(|room_id| async move {
|
||||
.filter_map(async |room_id| {
|
||||
let query = RoomQuery {
|
||||
room_id: &room_id,
|
||||
user_id: Some(sender_user),
|
||||
@@ -135,7 +135,7 @@ async fn category_room_events(
|
||||
.iter()
|
||||
.stream()
|
||||
.ready_filter(|_| criteria.include_state.is_some_and(is_true!()))
|
||||
.filter_map(|(room_id, ..)| async move {
|
||||
.filter_map(async |(room_id, ..)| {
|
||||
procure_room_state(services, room_id)
|
||||
.map_ok(|state| (room_id.clone(), state))
|
||||
.await
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
mod ldap;
|
||||
mod logout;
|
||||
mod password;
|
||||
mod refresh;
|
||||
mod token;
|
||||
|
||||
use axum::extract::State;
|
||||
@@ -21,10 +22,12 @@
|
||||
},
|
||||
};
|
||||
use tuwunel_core::{Err, Result, info, utils, utils::stream::ReadyExt};
|
||||
use tuwunel_service::users::device::generate_refresh_token;
|
||||
|
||||
use self::{ldap::ldap_login, password::password_login};
|
||||
pub(crate) use self::{
|
||||
logout::{logout_all_route, logout_route},
|
||||
refresh::refresh_token_route,
|
||||
token::login_token_route,
|
||||
};
|
||||
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
|
||||
@@ -87,7 +90,12 @@ pub(crate) async fn login_route(
|
||||
};
|
||||
|
||||
// Generate a new token for the device
|
||||
let access_token = utils::random_string(TOKEN_LENGTH);
|
||||
let (access_token, expires_in) = services
|
||||
.users
|
||||
.generate_access_token(body.body.refresh_token);
|
||||
|
||||
// Generate a new refresh_token if requested by client
|
||||
let refresh_token = expires_in.is_some().then(generate_refresh_token);
|
||||
|
||||
// Generate new device id if the user didn't specify one
|
||||
let device_id = body
|
||||
@@ -108,7 +116,8 @@ pub(crate) async fn login_route(
|
||||
.create_device(
|
||||
&user_id,
|
||||
&device_id,
|
||||
&access_token,
|
||||
(&access_token, expires_in),
|
||||
refresh_token.as_deref(),
|
||||
body.initial_device_display_name.clone(),
|
||||
Some(client.to_string()),
|
||||
)
|
||||
@@ -116,7 +125,13 @@ pub(crate) async fn login_route(
|
||||
} else {
|
||||
services
|
||||
.users
|
||||
.set_access_token(&user_id, &device_id, &access_token)
|
||||
.set_access_token(
|
||||
&user_id,
|
||||
&device_id,
|
||||
&access_token,
|
||||
expires_in,
|
||||
refresh_token.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
@@ -141,7 +156,7 @@ pub(crate) async fn login_route(
|
||||
device_id,
|
||||
home_server,
|
||||
well_known,
|
||||
expires_in: None,
|
||||
refresh_token: None,
|
||||
expires_in,
|
||||
refresh_token,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ pub(super) async fn password_login(
|
||||
.password_hash(lowercased_user_id)
|
||||
.map_ok(|hash| (hash, lowercased_user_id))
|
||||
})
|
||||
.map_err(|_| err!(Request(Forbidden("Wrong username or password."))))
|
||||
.await?;
|
||||
|
||||
if hash.is_empty() {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use ruma::api::client::session::refresh_token::v3::{Request, Response};
|
||||
use tuwunel_core::{Err, Result, debug_info, err};
|
||||
use tuwunel_service::users::device::generate_refresh_token;
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `POST /_matrix/client/v3/refresh`
|
||||
///
|
||||
/// Refresh an access token.
|
||||
///
|
||||
/// <https://spec.matrix.org/v1.15/client-server-api/#post_matrixclientv3refresh>
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "refresh_token")]
|
||||
pub(crate) async fn refresh_token_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<Request>,
|
||||
) -> Result<Response> {
|
||||
let refresh_token_claim = body.body.refresh_token;
|
||||
|
||||
if !refresh_token_claim.starts_with("refresh_") {
|
||||
return Err!(Request(Forbidden("Refresh token is malformed.")));
|
||||
}
|
||||
|
||||
let (user_id, device_id, ..) = services
|
||||
.users
|
||||
.find_from_token(&refresh_token_claim)
|
||||
.await
|
||||
.map_err(|e| err!(Request(Forbidden("Refresh token is unrecognized: {e}"))))?;
|
||||
|
||||
// New tokens
|
||||
let refresh_token = Some(generate_refresh_token());
|
||||
let (access_token, expires_in_ms) = services.users.generate_access_token(true);
|
||||
|
||||
services
|
||||
.users
|
||||
.set_access_token(
|
||||
&user_id,
|
||||
&device_id,
|
||||
&access_token,
|
||||
expires_in_ms,
|
||||
refresh_token.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
debug_info!(?user_id, ?device_id, ?expires_in_ms, "refreshed their access_token",);
|
||||
|
||||
Ok(Response {
|
||||
access_token,
|
||||
refresh_token,
|
||||
expires_in_ms,
|
||||
})
|
||||
}
|
||||
@@ -166,7 +166,7 @@ async fn get_client_hierarchy<'a, ShortRoomIds>(
|
||||
|
||||
let next_batch: OptionFuture<_> = queue
|
||||
.pop_front()
|
||||
.map(|(room, _)| async move {
|
||||
.map(async |(room, _)| {
|
||||
parents.insert(room);
|
||||
|
||||
let next_short_room_ids: Vec<_> = parents
|
||||
|
||||
+14
-23
@@ -1,5 +1,5 @@
|
||||
use axum::extract::State;
|
||||
use futures::{FutureExt, TryStreamExt};
|
||||
use futures::{FutureExt, TryFutureExt, TryStreamExt};
|
||||
use ruma::{
|
||||
OwnedEventId, RoomId, UserId,
|
||||
api::client::state::{get_state_events, get_state_events_for_key, send_state_event},
|
||||
@@ -17,7 +17,7 @@
|
||||
};
|
||||
use serde_json::json;
|
||||
use tuwunel_core::{
|
||||
Err, Result, err,
|
||||
Err, Result, err, is_false,
|
||||
matrix::{Event, pdu::PduBuilder},
|
||||
utils::BoolExt,
|
||||
};
|
||||
@@ -337,7 +337,7 @@ async fn allowed_to_send_state_event(
|
||||
.resolve_alias(&alias, None)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
err!(Request(Unknown("Failed resolving alias \"{alias}\": {e}")))
|
||||
err!(Request(BadAlias("Failed resolving alias \"{alias}\": {e}")))
|
||||
})?;
|
||||
|
||||
if alias_room_id != room_id {
|
||||
@@ -356,7 +356,7 @@ async fn allowed_to_send_state_event(
|
||||
},
|
||||
| StateEventType::RoomMember => match json.deserialize_as::<RoomMemberEventContent>() {
|
||||
| Ok(membership_content) => {
|
||||
let Ok(state_key) = UserId::parse(state_key) else {
|
||||
let Ok(_state_key) = UserId::parse(state_key) else {
|
||||
return Err!(Request(BadJson(
|
||||
"Membership event has invalid or non-existent state key"
|
||||
)));
|
||||
@@ -371,17 +371,6 @@ async fn allowed_to_send_state_event(
|
||||
)));
|
||||
}
|
||||
|
||||
if services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(state_key, room_id)
|
||||
.await
|
||||
{
|
||||
return Err!(Request(InvalidParam(
|
||||
"{state_key} is already joined, an authorising user is not required."
|
||||
)));
|
||||
}
|
||||
|
||||
if !services.globals.user_is_local(&authorising_user) {
|
||||
return Err!(Request(InvalidParam(
|
||||
"Authorising user {authorising_user} does not belong to this \
|
||||
@@ -389,17 +378,19 @@ async fn allowed_to_send_state_event(
|
||||
)));
|
||||
}
|
||||
|
||||
if !services
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(&authorising_user, room_id)
|
||||
.await
|
||||
{
|
||||
return Err!(Request(InvalidParam(
|
||||
"Authorising user {authorising_user} is not in the room, they \
|
||||
cannot authorise the join."
|
||||
)));
|
||||
}
|
||||
.map(is_false!())
|
||||
.map(BoolExt::into_result)
|
||||
.map_err(|()| {
|
||||
err!(Request(InvalidParam(
|
||||
"Authorising user {authorising_user} is not in the room. They \
|
||||
cannot authorise the join."
|
||||
)))
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
},
|
||||
| Err(e) => {
|
||||
|
||||
@@ -75,7 +75,7 @@ async fn share_encrypted_room(
|
||||
.get_shared_rooms(sender_user, user_id)
|
||||
.ready_filter(|&room_id| Some(room_id) != ignore_room)
|
||||
.map(ToOwned::to_owned)
|
||||
.broad_any(|other_room_id| async move {
|
||||
.broad_any(async |other_room_id| {
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
|
||||
+392
-346
File diff suppressed because it is too large
Load Diff
+184
-121
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
cmp::{self, Ordering},
|
||||
cmp::Ordering,
|
||||
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
|
||||
ops::Deref,
|
||||
time::Duration,
|
||||
@@ -8,7 +8,7 @@
|
||||
use axum::extract::State;
|
||||
use futures::{
|
||||
FutureExt, Stream, StreamExt, TryFutureExt,
|
||||
future::{OptionFuture, join3, try_join4},
|
||||
future::{OptionFuture, join3, try_join5},
|
||||
pin_mut,
|
||||
};
|
||||
use ruma::{
|
||||
@@ -18,6 +18,7 @@
|
||||
events::{
|
||||
AnyRawAccountDataEvent, AnySyncEphemeralRoomEvent, StateEventType, TimelineEventType,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
typing::TypingEventContent,
|
||||
},
|
||||
serde::Raw,
|
||||
uint,
|
||||
@@ -27,7 +28,7 @@
|
||||
matrix::{Event, TypeStateKey, pdu::PduCount},
|
||||
trace,
|
||||
utils::{
|
||||
BoolExt, FutureBoolExt, IterStream, ReadyExt, TryFutureExtExt,
|
||||
BoolExt, FutureBoolExt, IterStream, ReadyExt, TryFutureExtExt, TryReadyExt,
|
||||
future::ReadyEqExt,
|
||||
math::{ruma_from_usize, usize_from_ruma},
|
||||
},
|
||||
@@ -60,31 +61,16 @@ pub(crate) async fn sync_events_v5_route(
|
||||
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 sender_user = body
|
||||
.sender_user
|
||||
.as_ref()
|
||||
.expect("user is authenticated");
|
||||
let sender_device = body
|
||||
.sender_device
|
||||
.as_ref()
|
||||
.expect("user is authenticated");
|
||||
let mut body = body.body;
|
||||
|
||||
// Setup watchers, so if there's no response, we can wait for them
|
||||
let watcher = services.sync.watch(sender_user, sender_device);
|
||||
|
||||
let next_batch = services.globals.next_count()?;
|
||||
|
||||
let conn_id = body.conn_id.clone();
|
||||
|
||||
let sender_user = body.sender_user();
|
||||
let sender_device = body.sender_device();
|
||||
let snake_key = into_snake_key(sender_user, sender_device, body.conn_id.clone());
|
||||
let globalsince = body
|
||||
.pos
|
||||
.as_ref()
|
||||
.and_then(|string| string.parse().ok())
|
||||
.unwrap_or(0);
|
||||
|
||||
let snake_key = into_snake_key(sender_user, sender_device, conn_id);
|
||||
|
||||
if globalsince != 0 && !services.sync.snake_connection_cached(&snake_key) {
|
||||
return Err!(Request(UnknownPos(
|
||||
"Connection data unknown to server; restarting sync stream."
|
||||
@@ -98,10 +84,16 @@ pub(crate) async fn sync_events_v5_route(
|
||||
.forget_snake_sync_connection(&snake_key);
|
||||
}
|
||||
|
||||
// Setup watchers, so if there's no response, we can wait for them
|
||||
let watcher = services.sync.watch(sender_user, sender_device);
|
||||
|
||||
let next_batch = services.globals.wait_pending().await?;
|
||||
|
||||
// Get sticky parameters from cache
|
||||
let mut cached = body.body.clone();
|
||||
let known_rooms = services
|
||||
.sync
|
||||
.update_snake_sync_request_with_cache(&snake_key, &mut body);
|
||||
.update_snake_sync_request_with_cache(&snake_key, &mut cached);
|
||||
|
||||
let all_joined_rooms = services
|
||||
.rooms
|
||||
@@ -135,33 +127,35 @@ pub(crate) async fn sync_events_v5_route(
|
||||
.chain(all_invited_rooms.clone())
|
||||
.chain(all_knocked_rooms.clone());
|
||||
|
||||
let pos = next_batch.clone().to_string();
|
||||
let pos = next_batch.to_string();
|
||||
|
||||
let mut todo_rooms: TodoRooms = BTreeMap::new();
|
||||
|
||||
let sync_info: SyncInfo<'_> = (sender_user, sender_device, globalsince, &body);
|
||||
let sync_info: SyncInfo<'_> = (sender_user, sender_device, globalsince, &cached);
|
||||
|
||||
let account_data = collect_account_data(services, sync_info).map(Ok);
|
||||
let account_data = collect_account_data(services, sync_info, next_batch).map(Ok);
|
||||
|
||||
let e2ee = collect_e2ee(services, sync_info, all_joined_rooms.clone());
|
||||
let e2ee = collect_e2ee(services, sync_info, next_batch, all_joined_rooms.clone());
|
||||
|
||||
let to_device = collect_to_device(services, sync_info, next_batch).map(Ok);
|
||||
|
||||
let receipts = collect_receipts(services).map(Ok);
|
||||
let receipts = collect_receipts(services, sync_info, next_batch).map(Ok);
|
||||
|
||||
let (account_data, e2ee, to_device, receipts) =
|
||||
try_join4(account_data, e2ee, to_device, receipts).await?;
|
||||
let typing = collect_typing_events(services, sync_info, next_batch, all_joined_rooms.clone());
|
||||
|
||||
let (account_data, e2ee, to_device, receipts, typing) =
|
||||
try_join5(account_data, e2ee, to_device, receipts, typing).await?;
|
||||
|
||||
let extensions = sync_events::v5::response::Extensions {
|
||||
account_data,
|
||||
e2ee,
|
||||
to_device,
|
||||
receipts,
|
||||
typing: sync_events::v5::response::Typing::default(),
|
||||
typing,
|
||||
};
|
||||
|
||||
let mut response = sync_events::v5::Response {
|
||||
txn_id: body.txn_id.clone(),
|
||||
txn_id: cached.txn_id.clone(),
|
||||
pos,
|
||||
lists: BTreeMap::new(),
|
||||
rooms: BTreeMap::new(),
|
||||
@@ -173,7 +167,7 @@ pub(crate) async fn sync_events_v5_route(
|
||||
sync_info,
|
||||
all_invited_rooms.clone(),
|
||||
all_joined_rooms.clone(),
|
||||
all_rooms,
|
||||
all_rooms.clone(),
|
||||
&mut todo_rooms,
|
||||
&known_rooms,
|
||||
&mut response,
|
||||
@@ -189,7 +183,7 @@ pub(crate) async fn sync_events_v5_route(
|
||||
all_invited_rooms.clone(),
|
||||
&todo_rooms,
|
||||
&mut response,
|
||||
&body,
|
||||
&cached,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -206,11 +200,18 @@ pub(crate) async fn sync_events_v5_route(
|
||||
.to_device
|
||||
.clone()
|
||||
.is_none_or(|to| to.events.is_empty())
|
||||
&& response.extensions.typing.is_empty()
|
||||
{
|
||||
// Hang a few seconds so requests are not spammed
|
||||
// Stop hanging if new info arrives
|
||||
let default = Duration::from_secs(30);
|
||||
let duration = cmp::min(body.timeout.unwrap_or(default), default);
|
||||
let timeout_default = services.config.client_sync_timeout_default;
|
||||
let timeout_min = services.config.client_sync_timeout_min;
|
||||
let timeout_max = services.config.client_sync_timeout_max;
|
||||
let duration = body
|
||||
.timeout
|
||||
.unwrap_or_else(|| Duration::from_millis(timeout_default))
|
||||
.clamp(Duration::from_millis(timeout_min), Duration::from_millis(timeout_max));
|
||||
|
||||
_ = tokio::time::timeout(duration, watcher).await;
|
||||
}
|
||||
|
||||
@@ -338,7 +339,6 @@ async fn handle_lists<'a, Rooms, AllRooms>(
|
||||
.collect();
|
||||
|
||||
new_known_rooms.extend(new_rooms);
|
||||
//new_known_rooms.extend(room_ids..cloned());
|
||||
for room_id in room_ids {
|
||||
let todo_room = todo_rooms.entry(room_id.to_owned()).or_insert((
|
||||
BTreeSet::new(),
|
||||
@@ -469,8 +469,8 @@ async fn process_rooms<'a, Rooms>(
|
||||
let mut receipts: Vec<Raw<AnySyncEphemeralRoomEvent>> = services
|
||||
.rooms
|
||||
.read_receipt
|
||||
.readreceipts_since(room_id, *roomsince)
|
||||
.filter_map(|(read_user, _ts, v)| async move {
|
||||
.readreceipts_since(room_id, *roomsince, Some(next_batch))
|
||||
.filter_map(async |(read_user, _ts, v)| {
|
||||
services
|
||||
.users
|
||||
.user_is_ignored(read_user, sender_user)
|
||||
@@ -494,15 +494,15 @@ async fn process_rooms<'a, Rooms>(
|
||||
.insert(room_id.clone(), pack_receipts(Box::new(receipts.into_iter())));
|
||||
}
|
||||
|
||||
if roomsince != &0
|
||||
if *roomsince != 0
|
||||
&& timeline_pdus.is_empty()
|
||||
&& receipt_size == 0
|
||||
&& response
|
||||
.extensions
|
||||
.account_data
|
||||
.rooms
|
||||
.get(room_id)
|
||||
.is_none_or(Vec::is_empty)
|
||||
&& receipt_size == 0
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -519,7 +519,7 @@ async fn process_rooms<'a, Rooms>(
|
||||
}))
|
||||
})?
|
||||
.or_else(|| {
|
||||
if roomsince != &0 {
|
||||
if *roomsince != 0 {
|
||||
Some(roomsince.to_string())
|
||||
} else {
|
||||
None
|
||||
@@ -548,7 +548,7 @@ async fn process_rooms<'a, Rooms>(
|
||||
let required_state = required_state_request
|
||||
.iter()
|
||||
.stream()
|
||||
.filter_map(|state| async move {
|
||||
.filter_map(async |state| {
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
@@ -560,29 +560,40 @@ async fn process_rooms<'a, Rooms>(
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
// Heroes
|
||||
let heroes: Vec<_> = services
|
||||
let room_name = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(room_id)
|
||||
.ready_filter(|member| *member != sender_user)
|
||||
.filter_map(|user_id| {
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_member(room_id, user_id)
|
||||
.map_ok(|memberevent| sync_events::v5::response::Hero {
|
||||
user_id: user_id.into(),
|
||||
name: memberevent.displayname,
|
||||
avatar: memberevent.avatar_url,
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.take(5)
|
||||
.collect()
|
||||
.await;
|
||||
.state_accessor
|
||||
.get_name(room_id)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
let name = match heroes.len().cmp(&(1_usize)) {
|
||||
// Heroes
|
||||
let heroes: Vec<_> = if room_name.is_none() {
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(room_id)
|
||||
.ready_filter(|member| *member != sender_user)
|
||||
.filter_map(|user_id| {
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_member(room_id, user_id)
|
||||
.map_ok(|memberevent| sync_events::v5::response::Hero {
|
||||
user_id: user_id.into(),
|
||||
name: memberevent.displayname,
|
||||
avatar: memberevent.avatar_url,
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.take(5)
|
||||
.collect()
|
||||
.await
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let hero_name = match heroes.len().cmp(&(1_usize)) {
|
||||
| Ordering::Greater => {
|
||||
let firsts = heroes[1..]
|
||||
.iter()
|
||||
@@ -616,27 +627,24 @@ async fn process_rooms<'a, Rooms>(
|
||||
None
|
||||
};
|
||||
|
||||
let room_avatar = match services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_avatar(room_id)
|
||||
.await
|
||||
{
|
||||
| ruma::JsOption::Some(avatar) => ruma::JsOption::from_option(avatar.url),
|
||||
| ruma::JsOption::Null => ruma::JsOption::Null,
|
||||
| ruma::JsOption::Undefined => ruma::JsOption::Undefined,
|
||||
};
|
||||
|
||||
rooms.insert(room_id.clone(), sync_events::v5::response::Room {
|
||||
name: services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_name(room_id)
|
||||
.await
|
||||
.ok()
|
||||
.or(name),
|
||||
avatar: match heroes_avatar {
|
||||
| Some(heroes_avatar) => ruma::JsOption::Some(heroes_avatar),
|
||||
| _ => match services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_avatar(room_id)
|
||||
.await
|
||||
{
|
||||
| ruma::JsOption::Some(avatar) => ruma::JsOption::from_option(avatar.url),
|
||||
| ruma::JsOption::Null => ruma::JsOption::Null,
|
||||
| ruma::JsOption::Undefined => ruma::JsOption::Undefined,
|
||||
},
|
||||
avatar: if room_name.is_some() {
|
||||
room_avatar
|
||||
} else {
|
||||
ruma::JsOption::from_option(heroes_avatar)
|
||||
},
|
||||
name: room_name.or(hero_name),
|
||||
initial: Some(roomsince == &0),
|
||||
is_dm: None,
|
||||
invite_state,
|
||||
@@ -691,9 +699,11 @@ async fn process_rooms<'a, Rooms>(
|
||||
}
|
||||
Ok(rooms)
|
||||
}
|
||||
|
||||
async fn collect_account_data(
|
||||
services: &Services,
|
||||
(sender_user, _, globalsince, body): (&UserId, &DeviceId, u64, &sync_events::v5::Request),
|
||||
next_batch: u64,
|
||||
) -> sync_events::v5::response::AccountData {
|
||||
let mut account_data = sync_events::v5::response::AccountData {
|
||||
global: Vec::new(),
|
||||
@@ -711,7 +721,7 @@ async fn collect_account_data(
|
||||
|
||||
account_data.global = services
|
||||
.account_data
|
||||
.changes_since(None, sender_user, globalsince, None)
|
||||
.changes_since(None, sender_user, globalsince, Some(next_batch))
|
||||
.ready_filter_map(|e| extract_variant!(e, AnyRawAccountDataEvent::Global))
|
||||
.collect()
|
||||
.await;
|
||||
@@ -722,7 +732,7 @@ async fn collect_account_data(
|
||||
room.clone(),
|
||||
services
|
||||
.account_data
|
||||
.changes_since(Some(room), sender_user, globalsince, None)
|
||||
.changes_since(Some(room), sender_user, globalsince, Some(next_batch))
|
||||
.ready_filter_map(|e| extract_variant!(e, AnyRawAccountDataEvent::Room))
|
||||
.collect()
|
||||
.await,
|
||||
@@ -741,6 +751,7 @@ async fn collect_e2ee<'a, Rooms>(
|
||||
u64,
|
||||
&sync_events::v5::Request,
|
||||
),
|
||||
next_batch: u64,
|
||||
all_joined_rooms: Rooms,
|
||||
) -> Result<sync_events::v5::response::E2EE>
|
||||
where
|
||||
@@ -756,7 +767,7 @@ async fn collect_e2ee<'a, Rooms>(
|
||||
device_list_changes.extend(
|
||||
services
|
||||
.users
|
||||
.keys_changed(sender_user, globalsince, None)
|
||||
.keys_changed(sender_user, globalsince, Some(next_batch))
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<_>>()
|
||||
.await,
|
||||
@@ -832,44 +843,51 @@ async fn collect_e2ee<'a, Rooms>(
|
||||
.await;
|
||||
|
||||
for (key, id) in current_state_ids {
|
||||
if since_state_ids.get(&key) != Some(&id) {
|
||||
let Ok(pdu) = services.rooms.timeline.get_pdu(&id).await else {
|
||||
error!("Pdu in state not found: {id}");
|
||||
continue;
|
||||
};
|
||||
if pdu.kind == TimelineEventType::RoomMember {
|
||||
if let Some(Ok(user_id)) = pdu.state_key.as_deref().map(UserId::parse)
|
||||
{
|
||||
if user_id == sender_user {
|
||||
continue;
|
||||
}
|
||||
if since_state_ids.get(&key) == Some(&id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let content: RoomMemberEventContent = pdu.get_content()?;
|
||||
match content.membership {
|
||||
| MembershipState::Join => {
|
||||
// A new user joined an encrypted room
|
||||
if !share_encrypted_room(
|
||||
services,
|
||||
sender_user,
|
||||
user_id,
|
||||
Some(room_id),
|
||||
)
|
||||
.await
|
||||
{
|
||||
device_list_changes.insert(user_id.to_owned());
|
||||
}
|
||||
},
|
||||
| MembershipState::Leave => {
|
||||
// Write down users that have left encrypted rooms we
|
||||
// are in
|
||||
left_encrypted_users.insert(user_id.to_owned());
|
||||
},
|
||||
| _ => {},
|
||||
}
|
||||
let Ok(pdu) = services.rooms.timeline.get_pdu(&id).await else {
|
||||
error!("Pdu in state not found: {id}");
|
||||
continue;
|
||||
};
|
||||
|
||||
if pdu.kind != TimelineEventType::RoomMember {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(Ok(user_id)) = pdu.state_key.as_deref().map(UserId::parse) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if user_id == sender_user {
|
||||
continue;
|
||||
}
|
||||
|
||||
let content: RoomMemberEventContent = pdu.get_content()?;
|
||||
match content.membership {
|
||||
| MembershipState::Join => {
|
||||
// A new user joined an encrypted room
|
||||
if !share_encrypted_room(
|
||||
services,
|
||||
sender_user,
|
||||
user_id,
|
||||
Some(room_id),
|
||||
)
|
||||
.await
|
||||
{
|
||||
device_list_changes.insert(user_id.to_owned());
|
||||
}
|
||||
}
|
||||
},
|
||||
| MembershipState::Leave => {
|
||||
// Write down users that have left encrypted rooms we
|
||||
// are in
|
||||
left_encrypted_users.insert(user_id.to_owned());
|
||||
},
|
||||
| _ => {},
|
||||
}
|
||||
}
|
||||
|
||||
if joined_since_last_sync || new_encrypted_room {
|
||||
// If the user is in a new encrypted room, give them all joined users
|
||||
device_list_changes.extend(
|
||||
@@ -895,7 +913,7 @@ async fn collect_e2ee<'a, Rooms>(
|
||||
device_list_changes.extend(
|
||||
services
|
||||
.users
|
||||
.room_keys_changed(room_id, globalsince, None)
|
||||
.room_keys_changed(room_id, globalsince, Some(next_batch))
|
||||
.map(|(user_id, _)| user_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<_>>()
|
||||
@@ -953,7 +971,52 @@ async fn collect_to_device(
|
||||
})
|
||||
}
|
||||
|
||||
async fn collect_receipts(_services: &Services) -> sync_events::v5::response::Receipts {
|
||||
async fn collect_typing_events<'a, Rooms>(
|
||||
services: &Services,
|
||||
(sender_user, _, _, body): SyncInfo<'_>,
|
||||
_next_batch: u64,
|
||||
rooms: Rooms,
|
||||
) -> Result<sync_events::v5::response::Typing>
|
||||
where
|
||||
Rooms: Iterator<Item = &'a RoomId> + Send + 'a,
|
||||
{
|
||||
use sync_events::v5::response::Typing;
|
||||
|
||||
if !body.extensions.typing.enabled.unwrap_or(false) {
|
||||
return Ok(Typing::default());
|
||||
}
|
||||
|
||||
rooms
|
||||
.stream()
|
||||
.filter_map(async |room_id| {
|
||||
services
|
||||
.rooms
|
||||
.typing
|
||||
.typing_users_for_user(room_id, sender_user)
|
||||
.inspect_err(|e| warn!(%room_id, "Failed to get typing events for room: {e}"))
|
||||
.await
|
||||
.ok()
|
||||
.filter(|users| !users.is_empty())
|
||||
.map(|users| Ok((room_id, users)))
|
||||
})
|
||||
.ready_try_fold_default(|mut response: Typing, (room_id, users)| {
|
||||
response.rooms.insert(
|
||||
room_id.to_owned(),
|
||||
Raw::new(&ruma::events::typing::SyncTypingEvent {
|
||||
content: TypingEventContent::new(users),
|
||||
})?,
|
||||
);
|
||||
|
||||
Ok(response)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn collect_receipts(
|
||||
_services: &Services,
|
||||
(_sender_user, _, _globalsince, _body): SyncInfo<'_>,
|
||||
_next_batch: u64,
|
||||
) -> sync_events::v5::response::Receipts {
|
||||
sync_events::v5::response::Receipts { rooms: BTreeMap::new() }
|
||||
// TODO: get explicitly requested read receipts
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use axum::extract::State;
|
||||
use futures::StreamExt;
|
||||
use futures::{StreamExt, TryStreamExt};
|
||||
use ruma::{api::client::threads::get_threads, uint};
|
||||
use tuwunel_core::{
|
||||
Result, at,
|
||||
@@ -35,18 +35,17 @@ pub(crate) async fn get_threads_route(
|
||||
.rooms
|
||||
.threads
|
||||
.threads_until(body.sender_user(), &body.room_id, from, &body.include)
|
||||
.await?
|
||||
.take(limit)
|
||||
.filter_map(|(count, pdu)| async move {
|
||||
services
|
||||
.try_filter_map(async |(count, pdu)| {
|
||||
Ok(services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.user_can_see_event(body.sender_user(), &body.room_id, &pdu.event_id)
|
||||
.await
|
||||
.then_some((count, pdu))
|
||||
.then_some((count, pdu)))
|
||||
})
|
||||
.collect()
|
||||
.await;
|
||||
.try_collect()
|
||||
.await?;
|
||||
|
||||
Ok(get_threads::v1::Response {
|
||||
next_batch: threads
|
||||
|
||||
@@ -41,7 +41,6 @@ pub(crate) async fn send_event_to_device_route(
|
||||
map.insert(target_device_id_maybe.clone(), event.clone());
|
||||
let mut messages = BTreeMap::new();
|
||||
messages.insert(target_user_id.clone(), map);
|
||||
let count = services.globals.next_count()?;
|
||||
|
||||
let mut buf = EduBuf::new();
|
||||
serde_json::to_writer(
|
||||
@@ -49,7 +48,7 @@ pub(crate) async fn send_event_to_device_route(
|
||||
&federation::transactions::edu::Edu::DirectToDevice(DirectDeviceContent {
|
||||
sender: sender_user.to_owned(),
|
||||
ev_type: body.event_type.clone(),
|
||||
message_id: count.to_string().into(),
|
||||
message_id: services.globals.next_count().to_string().into(),
|
||||
messages,
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Result,
|
||||
utils::{future::BoolExt, stream::BroadbandExt},
|
||||
utils::{
|
||||
future::BoolExt,
|
||||
stream::{BroadbandExt, ReadyExt},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::Ruma;
|
||||
@@ -34,6 +37,7 @@ pub(crate) async fn search_users_route(
|
||||
let mut users = services
|
||||
.users
|
||||
.stream()
|
||||
.ready_filter(|&user_id| user_id != sender_user)
|
||||
.map(ToOwned::to_owned)
|
||||
.broad_filter_map(async |user_id| {
|
||||
let display_name = services.users.displayname(&user_id).await.ok();
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#![type_length_limit = "16384"] //TODO: reduce me
|
||||
#![type_length_limit = "65536"] //TODO: reduce me
|
||||
#![allow(clippy::toplevel_ref_arg)]
|
||||
|
||||
pub mod client;
|
||||
|
||||
@@ -35,6 +35,7 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||
.ruma_route(&client::get_login_types_route)
|
||||
.ruma_route(&client::login_route)
|
||||
.ruma_route(&client::login_token_route)
|
||||
.ruma_route(&client::refresh_token_route)
|
||||
.ruma_route(&client::whoami_route)
|
||||
.ruma_route(&client::logout_route)
|
||||
.ruma_route(&client::logout_all_route)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{mem, ops::Deref};
|
||||
use std::{fmt::Debug, mem, ops::Deref};
|
||||
|
||||
use axum::{body::Body, extract::FromRequest};
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
@@ -13,6 +13,7 @@
|
||||
use crate::State;
|
||||
|
||||
/// Extractor for Ruma request structs
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Args<T> {
|
||||
/// Request struct body
|
||||
pub(crate) body: T,
|
||||
@@ -38,10 +39,7 @@ pub(crate) struct Args<T> {
|
||||
pub(crate) json_body: Option<CanonicalJsonValue>,
|
||||
}
|
||||
|
||||
impl<T> Args<T>
|
||||
where
|
||||
T: IncomingRequest + Send + Sync + 'static,
|
||||
{
|
||||
impl<T> Args<T> {
|
||||
#[inline]
|
||||
pub(crate) fn sender(&self) -> (&UserId, &DeviceId) {
|
||||
(self.sender_user(), self.sender_device())
|
||||
@@ -71,7 +69,7 @@ pub(crate) fn origin(&self) -> &ServerName {
|
||||
|
||||
impl<T> Deref for Args<T>
|
||||
where
|
||||
T: IncomingRequest + Send + Sync + 'static,
|
||||
T: Sync,
|
||||
{
|
||||
type Target = T;
|
||||
|
||||
@@ -80,10 +78,11 @@ fn deref(&self) -> &Self::Target { &self.body }
|
||||
|
||||
impl<T> FromRequest<State, Body> for Args<T>
|
||||
where
|
||||
T: IncomingRequest + Send + Sync + 'static,
|
||||
T: IncomingRequest + Debug + Send + Sync + 'static,
|
||||
{
|
||||
type Rejection = Error;
|
||||
|
||||
#[tracing::instrument(name = "ar", level = "debug", skip(services), ret, err)]
|
||||
async fn from_request(
|
||||
request: hyper::Request<Body>,
|
||||
services: &State,
|
||||
|
||||
+153
-138
@@ -1,9 +1,19 @@
|
||||
use std::{fmt::Debug, time::SystemTime};
|
||||
|
||||
use axum::RequestPartsExt;
|
||||
use axum_extra::{
|
||||
TypedHeader,
|
||||
headers::{Authorization, authorization::Bearer},
|
||||
typed_header::TypedHeaderRejectionReason,
|
||||
};
|
||||
use futures::{
|
||||
TryFutureExt,
|
||||
future::{
|
||||
Either::{Left, Right},
|
||||
select_ok,
|
||||
},
|
||||
pin_mut,
|
||||
};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue, OwnedDeviceId, OwnedServerName, OwnedUserId, UserId,
|
||||
api::{
|
||||
@@ -19,7 +29,9 @@
|
||||
federation::{authentication::XMatrix, openid::get_openid_userinfo},
|
||||
},
|
||||
};
|
||||
use tuwunel_core::{Err, Error, Result, debug_error, err, warn};
|
||||
use tuwunel_core::{
|
||||
Err, Error, Result, debug_error, err, is_less_than, utils::result::LogDebugErr, warn,
|
||||
};
|
||||
use tuwunel_service::{
|
||||
Services,
|
||||
appservice::RegistrationInfo,
|
||||
@@ -30,24 +42,38 @@
|
||||
|
||||
enum Token {
|
||||
Appservice(Box<RegistrationInfo>),
|
||||
User((OwnedUserId, OwnedDeviceId)),
|
||||
User((OwnedUserId, OwnedDeviceId, Option<SystemTime>)),
|
||||
Expired((OwnedUserId, OwnedDeviceId)),
|
||||
Invalid,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct Auth {
|
||||
pub(super) origin: Option<OwnedServerName>,
|
||||
pub(super) sender_user: Option<OwnedUserId>,
|
||||
pub(super) sender_device: Option<OwnedDeviceId>,
|
||||
pub(super) appservice_info: Option<RegistrationInfo>,
|
||||
pub(super) _expires_at: Option<SystemTime>,
|
||||
}
|
||||
|
||||
#[tracing::instrument(
|
||||
level = "trace",
|
||||
skip(services, request, json_body),
|
||||
ret,
|
||||
err
|
||||
)]
|
||||
pub(super) async fn auth(
|
||||
services: &Services,
|
||||
request: &mut Request,
|
||||
json_body: Option<&CanonicalJsonValue>,
|
||||
metadata: &Metadata,
|
||||
) -> Result<Auth> {
|
||||
use AuthScheme::{AccessToken, AccessTokenOptional, AppserviceToken, ServerSignatures};
|
||||
use Error::BadRequest;
|
||||
use ErrorKind::UnknownToken;
|
||||
use Token::{Appservice, Expired, Invalid, User};
|
||||
|
||||
let bearer: Option<TypedHeader<Authorization<Bearer>>> =
|
||||
request.parts.extract().await.unwrap_or(None);
|
||||
|
||||
@@ -56,146 +82,138 @@ pub(super) async fn auth(
|
||||
| None => request.query.access_token.as_deref(),
|
||||
};
|
||||
|
||||
let token = if let Some(token) = token {
|
||||
match services
|
||||
.appservice
|
||||
.find_from_access_token(token)
|
||||
.await
|
||||
{
|
||||
| Some(reg_info) => Token::Appservice(Box::new(reg_info)),
|
||||
| _ => match services.users.find_from_access_token(token).await {
|
||||
| Ok((user_id, device_id)) => Token::User((user_id, device_id)),
|
||||
| _ => Token::Invalid,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
Token::None
|
||||
let token = match find_token(services, token).await? {
|
||||
| User((user_id, device_id, expires_at))
|
||||
if expires_at.is_some_and(is_less_than!(SystemTime::now())) =>
|
||||
Expired((user_id, device_id)),
|
||||
|
||||
| token => token,
|
||||
};
|
||||
|
||||
if metadata.authentication == AuthScheme::None {
|
||||
match metadata {
|
||||
| &get_public_rooms::v3::Request::METADATA => {
|
||||
if !services
|
||||
.server
|
||||
.config
|
||||
.allow_public_room_directory_without_auth
|
||||
{
|
||||
match token {
|
||||
| Token::Appservice(_) | Token::User(_) => {
|
||||
// we should have validated the token above
|
||||
// already
|
||||
},
|
||||
| Token::None | Token::Invalid => {
|
||||
return Err!(Request(MissingToken(
|
||||
"Missing or invalid access token."
|
||||
)));
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
| &get_profile::v3::Request::METADATA
|
||||
| &get_profile_key::unstable::Request::METADATA
|
||||
| &get_display_name::v3::Request::METADATA
|
||||
| &get_avatar_url::v3::Request::METADATA
|
||||
| &get_timezone_key::unstable::Request::METADATA => {
|
||||
if services
|
||||
.server
|
||||
.config
|
||||
.require_auth_for_profile_requests
|
||||
{
|
||||
match token {
|
||||
| Token::Appservice(_) | Token::User(_) => {
|
||||
// we should have validated the token above
|
||||
// already
|
||||
},
|
||||
| Token::None | Token::Invalid => {
|
||||
return Err!(Request(MissingToken(
|
||||
"Missing or invalid access token."
|
||||
)));
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
| _ => {},
|
||||
}
|
||||
check_auth_still_required(services, metadata, &token)?;
|
||||
}
|
||||
|
||||
match (metadata.authentication, token) {
|
||||
| (AuthScheme::AccessToken, Token::Appservice(info)) =>
|
||||
Ok(auth_appservice(services, request, info).await?),
|
||||
| (
|
||||
AuthScheme::None | AuthScheme::AccessTokenOptional | AuthScheme::AppserviceToken,
|
||||
Token::Appservice(info),
|
||||
) => Ok(Auth {
|
||||
origin: None,
|
||||
sender_user: None,
|
||||
sender_device: None,
|
||||
appservice_info: Some(*info),
|
||||
}),
|
||||
| (AuthScheme::AccessToken, Token::None) => match metadata {
|
||||
| &get_turn_server_info::v3::Request::METADATA => {
|
||||
if services.server.config.turn_allow_guests {
|
||||
Ok(Auth {
|
||||
origin: None,
|
||||
sender_user: None,
|
||||
sender_device: None,
|
||||
appservice_info: None,
|
||||
})
|
||||
} else {
|
||||
Err!(Request(MissingToken("Missing access token.")))
|
||||
}
|
||||
},
|
||||
| _ => Err!(Request(MissingToken("Missing access token."))),
|
||||
},
|
||||
| (
|
||||
AuthScheme::AccessToken | AuthScheme::AccessTokenOptional | AuthScheme::None,
|
||||
Token::User((user_id, device_id)),
|
||||
) => Ok(Auth {
|
||||
origin: None,
|
||||
sender_user: Some(user_id),
|
||||
sender_device: Some(device_id),
|
||||
appservice_info: None,
|
||||
}),
|
||||
| (AuthScheme::ServerSignatures, Token::None) =>
|
||||
Ok(auth_server(services, request, json_body).await?),
|
||||
| (
|
||||
AuthScheme::None | AuthScheme::AppserviceToken | AuthScheme::AccessTokenOptional,
|
||||
Token::None,
|
||||
) => Ok(Auth {
|
||||
sender_user: None,
|
||||
sender_device: None,
|
||||
origin: None,
|
||||
appservice_info: None,
|
||||
}),
|
||||
| (AuthScheme::ServerSignatures, Token::Appservice(_) | Token::User(_)) =>
|
||||
Err!(Request(Unauthorized("Only server signatures should be used on this endpoint."))),
|
||||
| (AuthScheme::AppserviceToken, Token::User(_)) => Err!(Request(Unauthorized(
|
||||
"Only appservice access tokens should be used on this endpoint."
|
||||
))),
|
||||
| (AuthScheme::None, Token::Invalid) => {
|
||||
| (AuthScheme::None, Invalid)
|
||||
if request.query.access_token.is_some()
|
||||
&& metadata == &get_openid_userinfo::v1::Request::METADATA =>
|
||||
{
|
||||
// OpenID federation endpoint uses a query param with the same name, drop this
|
||||
// once query params for user auth are removed from the spec. This is
|
||||
// required to make integration manager work.
|
||||
if request.query.access_token.is_some()
|
||||
&& metadata == &get_openid_userinfo::v1::Request::METADATA
|
||||
{
|
||||
Ok(Auth {
|
||||
origin: None,
|
||||
sender_user: None,
|
||||
sender_device: None,
|
||||
appservice_info: None,
|
||||
})
|
||||
} else {
|
||||
Err(Error::BadRequest(
|
||||
ErrorKind::UnknownToken { soft_logout: false },
|
||||
"Unknown access token.",
|
||||
))
|
||||
}
|
||||
Ok(Auth::default())
|
||||
},
|
||||
| (_, Token::Invalid) => Err(Error::BadRequest(
|
||||
ErrorKind::UnknownToken { soft_logout: false },
|
||||
"Unknown access token.",
|
||||
)),
|
||||
|
||||
| (_, Invalid) =>
|
||||
Err(BadRequest(UnknownToken { soft_logout: false }, "Unknown access token.")),
|
||||
|
||||
| (_, Expired((user_id, device_id))) => {
|
||||
services
|
||||
.users
|
||||
.remove_access_token(&user_id, &device_id)
|
||||
.await
|
||||
.log_debug_err()
|
||||
.ok();
|
||||
|
||||
Err(BadRequest(UnknownToken { soft_logout: true }, "Expired access token."))
|
||||
},
|
||||
|
||||
| (AppserviceToken, User(_)) =>
|
||||
Err!(Request(Unauthorized("Appservice tokens must be used on this endpoint."))),
|
||||
|
||||
| (ServerSignatures, Appservice(_) | User(_)) =>
|
||||
Err!(Request(Unauthorized("Server signatures must be used on this endpoint."))),
|
||||
|
||||
| (ServerSignatures, Token::None) => Ok(auth_server(services, request, json_body).await?),
|
||||
|
||||
| (AccessToken, Appservice(info)) => Ok(auth_appservice(services, request, info).await?),
|
||||
|
||||
| (AccessToken, Token::None) => match metadata {
|
||||
| &get_turn_server_info::v3::Request::METADATA
|
||||
if services.server.config.turn_allow_guests =>
|
||||
Ok(Auth::default()),
|
||||
|
||||
| _ => Err!(Request(MissingToken("Missing access token."))),
|
||||
},
|
||||
|
||||
| (AccessToken | AccessTokenOptional | AuthScheme::None, User(user)) => Ok(Auth {
|
||||
sender_user: Some(user.0),
|
||||
sender_device: Some(user.1),
|
||||
_expires_at: user.2,
|
||||
..Auth::default()
|
||||
}),
|
||||
|
||||
//TODO: add AppserviceTokenOptional
|
||||
| (AccessTokenOptional | AppserviceToken | AuthScheme::None, Appservice(info)) =>
|
||||
Ok(Auth {
|
||||
appservice_info: Some(*info),
|
||||
..Auth::default()
|
||||
}),
|
||||
|
||||
//TODO: add AppserviceTokenOptional
|
||||
| (AccessTokenOptional | AppserviceToken | AuthScheme::None, Token::None) =>
|
||||
Ok(Auth::default()),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_auth_still_required(services: &Services, metadata: &Metadata, token: &Token) -> Result {
|
||||
debug_assert_eq!(
|
||||
metadata.authentication,
|
||||
AuthScheme::None,
|
||||
"Expected endpoint to be unauthenticated"
|
||||
);
|
||||
|
||||
match metadata {
|
||||
| &get_profile::v3::Request::METADATA
|
||||
| &get_profile_key::unstable::Request::METADATA
|
||||
| &get_display_name::v3::Request::METADATA
|
||||
| &get_avatar_url::v3::Request::METADATA
|
||||
| &get_timezone_key::unstable::Request::METADATA
|
||||
if services
|
||||
.server
|
||||
.config
|
||||
.require_auth_for_profile_requests =>
|
||||
match token {
|
||||
| Token::Appservice(_) | Token::User(_) => Ok(()),
|
||||
| Token::None | Token::Expired(_) | Token::Invalid =>
|
||||
Err!(Request(MissingToken("Missing or invalid access token."))),
|
||||
},
|
||||
| &get_public_rooms::v3::Request::METADATA
|
||||
if !services
|
||||
.server
|
||||
.config
|
||||
.allow_public_room_directory_without_auth =>
|
||||
match token {
|
||||
| Token::Appservice(_) | Token::User(_) => Ok(()),
|
||||
| Token::None | Token::Expired(_) | Token::Invalid =>
|
||||
Err!(Request(MissingToken("Missing or invalid access token."))),
|
||||
},
|
||||
| _ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn find_token(services: &Services, token: Option<&str>) -> Result<Token> {
|
||||
let Some(token) = token else {
|
||||
return Ok(Token::None);
|
||||
};
|
||||
|
||||
let user_token = services
|
||||
.users
|
||||
.find_from_token(token)
|
||||
.map_ok(Token::User);
|
||||
|
||||
let appservice_token = services
|
||||
.appservice
|
||||
.find_from_access_token(token)
|
||||
.map_ok(Box::new)
|
||||
.map_ok(Token::Appservice);
|
||||
|
||||
pin_mut!(user_token, appservice_token);
|
||||
match select_ok([Left(user_token), Right(appservice_token)]).await {
|
||||
| Err(e) if !e.is_not_found() => Err(e),
|
||||
| Ok((token, _)) => Ok(token),
|
||||
| _ => Ok(Token::Invalid),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,10 +243,9 @@ async fn auth_appservice(
|
||||
}
|
||||
|
||||
Ok(Auth {
|
||||
origin: None,
|
||||
sender_user: Some(user_id),
|
||||
sender_device: None,
|
||||
appservice_info: Some(*info),
|
||||
..Auth::default()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -303,13 +320,11 @@ async fn auth_server(
|
||||
|
||||
Ok(Auth {
|
||||
origin: origin.to_owned().into(),
|
||||
sender_user: None,
|
||||
sender_device: None,
|
||||
appservice_info: None,
|
||||
..Auth::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn auth_server_checks(services: &Services, x_matrix: &XMatrix) -> Result<()> {
|
||||
fn auth_server_checks(services: &Services, x_matrix: &XMatrix) -> Result {
|
||||
if !services.server.config.allow_federation {
|
||||
return Err!(Config("allow_federation", "Federation is disabled."));
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use axum::{
|
||||
Router,
|
||||
extract::FromRequestParts,
|
||||
@@ -38,7 +40,7 @@ impl<Err, Req, Fut, Fun, $($tx,)*> RumaHandler<($($tx,)* Ruma<Req>,)> for Fun
|
||||
where
|
||||
Fun: Fn($($tx,)* Ruma<Req>,) -> Fut + Send + Sync + 'static,
|
||||
Fut: Future<Output = Result<Req::OutgoingResponse, Err>> + Send,
|
||||
Req: IncomingRequest + Send + Sync + 'static,
|
||||
Req: IncomingRequest + Debug + Send + Sync + 'static,
|
||||
Err: IntoResponse + Send,
|
||||
<Req as IncomingRequest>::OutgoingResponse: Send,
|
||||
$( $tx: FromRequestParts<State> + Send + Sync + 'static, )*
|
||||
|
||||
@@ -64,7 +64,7 @@ pub(crate) async fn get_backfill_route(
|
||||
.timeline
|
||||
.pdus_rev(None, &body.room_id, Some(from.saturating_add(1)))
|
||||
.try_take(limit)
|
||||
.try_filter_map(|(_, pdu)| async move {
|
||||
.try_filter_map(async |(_, pdu)| {
|
||||
Ok(services
|
||||
.rooms
|
||||
.state_accessor
|
||||
@@ -72,7 +72,7 @@ pub(crate) async fn get_backfill_route(
|
||||
.await
|
||||
.then_some(pdu))
|
||||
})
|
||||
.try_filter_map(|pdu| async move {
|
||||
.try_filter_map(async |pdu| {
|
||||
Ok(services
|
||||
.rooms
|
||||
.timeline
|
||||
|
||||
@@ -49,7 +49,7 @@ pub(crate) async fn get_event_authorization_route(
|
||||
.auth_chain
|
||||
.event_ids_iter(room_id, once(body.event_id.borrow()))
|
||||
.ready_filter_map(Result::ok)
|
||||
.filter_map(|id| async move {
|
||||
.filter_map(async |id| {
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
|
||||
@@ -45,7 +45,7 @@ pub(crate) async fn get_hierarchy_route(
|
||||
let (children, inaccessible_children) =
|
||||
get_parent_children_via(&room, suggested_only)
|
||||
.stream()
|
||||
.broad_filter_map(|(child, _via)| async move {
|
||||
.broad_filter_map(async |(child, _via)| {
|
||||
match services
|
||||
.rooms
|
||||
.spaces
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use axum::extract::State;
|
||||
use futures::StreamExt;
|
||||
use futures::{StreamExt, pin_mut};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, OwnedUserId, RoomId, RoomVersionId, UserId,
|
||||
api::{client::error::ErrorKind, federation::membership::prepare_join_event},
|
||||
@@ -105,7 +105,7 @@ pub(crate) async fn create_join_event_template_route(
|
||||
)
|
||||
.await?
|
||||
{
|
||||
let Some(auth_user) = services
|
||||
let users = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.local_users_in_room(&body.room_id)
|
||||
@@ -117,15 +117,15 @@ pub(crate) async fn create_join_event_template_route(
|
||||
&state_lock,
|
||||
)
|
||||
})
|
||||
.boxed()
|
||||
.next()
|
||||
.await
|
||||
.map(ToOwned::to_owned)
|
||||
else {
|
||||
.map(ToOwned::to_owned);
|
||||
|
||||
pin_mut!(users);
|
||||
let Some(auth_user) = users.next().await else {
|
||||
return Err!(Request(UnableToGrantJoin(
|
||||
"No user on this server is able to assist in joining."
|
||||
)));
|
||||
};
|
||||
|
||||
Some(auth_user)
|
||||
} else {
|
||||
None
|
||||
|
||||
+14
-14
@@ -158,10 +158,11 @@ async fn handle(
|
||||
let results: ResolvedMap = pdus
|
||||
.into_iter()
|
||||
.try_stream()
|
||||
.broad_and_then(|(room_id, pdus): (_, Vec<_>)| {
|
||||
handle_room(services, client, origin, started, room_id, pdus.into_iter())
|
||||
.map_ok(Vec::into_iter)
|
||||
.broad_and_then(async |(room_id, pdus): (_, Vec<_>)| {
|
||||
handle_room(services, client, origin, &started, room_id, pdus.into_iter())
|
||||
.map_ok(ResolvedMap::into_iter)
|
||||
.map_ok(IterStream::try_stream)
|
||||
.await
|
||||
})
|
||||
.try_flatten()
|
||||
.try_collect()
|
||||
@@ -180,28 +181,27 @@ async fn handle_room(
|
||||
services: &Services,
|
||||
_client: &IpAddr,
|
||||
origin: &ServerName,
|
||||
txn_start_time: Instant,
|
||||
room_id: OwnedRoomId,
|
||||
txn_start_time: &Instant,
|
||||
ref room_id: OwnedRoomId,
|
||||
pdus: impl Iterator<Item = Pdu> + Send,
|
||||
) -> Result<Vec<(OwnedEventId, Result)>> {
|
||||
) -> Result<ResolvedMap> {
|
||||
let _room_lock = services
|
||||
.rooms
|
||||
.event_handler
|
||||
.mutex_federation
|
||||
.lock(&room_id)
|
||||
.lock(room_id)
|
||||
.await;
|
||||
|
||||
let room_id = &room_id;
|
||||
pdus.try_stream()
|
||||
.and_then(|(_, event_id, value)| async move {
|
||||
.and_then(async |(room_id, event_id, value)| {
|
||||
services.server.check_running()?;
|
||||
let pdu_start_time = Instant::now();
|
||||
let result = services
|
||||
.rooms
|
||||
.event_handler
|
||||
.handle_incoming_pdu(origin, room_id, &event_id, value, true)
|
||||
.await
|
||||
.map(|_| ());
|
||||
.handle_incoming_pdu(origin, &room_id, &event_id, value, true)
|
||||
.map_ok(|_| ())
|
||||
.await;
|
||||
|
||||
debug!(
|
||||
pdu_elapsed = ?pdu_start_time.elapsed(),
|
||||
@@ -329,7 +329,7 @@ async fn handle_edu_receipt_room(
|
||||
.read
|
||||
.into_iter()
|
||||
.stream()
|
||||
.for_each_concurrent(automatic_width(), |(user_id, user_updates)| async move {
|
||||
.for_each_concurrent(automatic_width(), async |(user_id, user_updates)| {
|
||||
handle_edu_receipt_room_user(services, origin, room_id, &user_id, user_updates).await;
|
||||
})
|
||||
.await;
|
||||
@@ -368,7 +368,7 @@ async fn handle_edu_receipt_room_user(
|
||||
.event_ids
|
||||
.into_iter()
|
||||
.stream()
|
||||
.for_each_concurrent(automatic_width(), |event_id| async move {
|
||||
.for_each_concurrent(automatic_width(), async |event_id| {
|
||||
let user_data = [(user_id.to_owned(), data.clone())];
|
||||
let receipts = [(ReceiptType::Read, BTreeMap::from(user_data))];
|
||||
let content = [(event_id.clone(), BTreeMap::from(receipts))];
|
||||
|
||||
@@ -242,7 +242,7 @@ async fn create_join_event(
|
||||
.rooms
|
||||
.auth_chain
|
||||
.event_ids_iter(room_id, starting_events)
|
||||
.broad_and_then(|event_id| async move {
|
||||
.broad_and_then(async |event_id| {
|
||||
services
|
||||
.rooms
|
||||
.timeline
|
||||
|
||||
@@ -56,7 +56,7 @@ pub(crate) async fn get_room_state_route(
|
||||
.rooms
|
||||
.auth_chain
|
||||
.event_ids_iter(&body.room_id, once(body.event_id.borrow()))
|
||||
.and_then(|id| async move { services.rooms.timeline.get_pdu_json(&id).await })
|
||||
.and_then(async |id| services.rooms.timeline.get_pdu_json(&id).await)
|
||||
.and_then(|pdu| {
|
||||
services
|
||||
.sending
|
||||
|
||||
@@ -40,7 +40,7 @@ pub(crate) async fn get_devices_route(
|
||||
devices: services
|
||||
.users
|
||||
.all_devices_metadata(user_id)
|
||||
.filter_map(|metadata| async move {
|
||||
.filter_map(async |metadata| {
|
||||
let device_id = metadata.device_id.clone();
|
||||
let device_id_clone = device_id.clone();
|
||||
let device_id_string = device_id.as_str().to_owned();
|
||||
|
||||
@@ -79,7 +79,6 @@ http.workspace = true
|
||||
ipaddress.workspace = true
|
||||
itertools.workspace = true
|
||||
jsonwebtoken.workspace = true
|
||||
ldap3.workspace = true
|
||||
libc.workspace = true
|
||||
libloading.workspace = true
|
||||
libloading.optional = true
|
||||
|
||||
@@ -68,6 +68,10 @@ pub fn check(config: &Config) -> Result {
|
||||
return Err!(Config("port", "No ports were specified to listen on"));
|
||||
}
|
||||
|
||||
if !config.listening {
|
||||
warn!("Configuration item `listening` is set to `false`. Cannot hear anyone.");
|
||||
}
|
||||
|
||||
if config.unix_socket_path.is_none() {
|
||||
config.get_bind_addrs().iter().for_each(|addr| {
|
||||
use std::path::Path;
|
||||
@@ -317,7 +321,7 @@ fn warn_unknown_key(config: &Config) {
|
||||
|
||||
/// Checks the presence of the `address` and `unix_socket_path` keys in the
|
||||
/// raw_config, exiting the process if both keys were detected.
|
||||
pub(super) fn is_dual_listening(raw_config: &Figment) -> Result<()> {
|
||||
pub(super) fn is_dual_listening(raw_config: &Figment) -> Result {
|
||||
let contains_address = raw_config.contains("address");
|
||||
let contains_unix_socket = raw_config.contains("unix_socket_path");
|
||||
if contains_address && contains_unix_socket {
|
||||
|
||||
+232
-21
@@ -25,7 +25,11 @@
|
||||
|
||||
use self::proxy::ProxyConfig;
|
||||
pub use self::{check::check, manager::Manager};
|
||||
use crate::{Result, err, error::Error, utils::sys};
|
||||
use crate::{
|
||||
Result, err,
|
||||
error::Error,
|
||||
utils::{string::EMPTY, sys},
|
||||
};
|
||||
|
||||
/// All the config options for tuwunel.
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
@@ -52,7 +56,8 @@
|
||||
### For more information, see:
|
||||
### https://tuwunel.chat/configuration.html
|
||||
"#,
|
||||
ignore = "catchall well_known tls blurhashing allow_invalid_tls_certificates ldap jwt"
|
||||
ignore = "catchall well_known tls blurhashing allow_invalid_tls_certificates ldap jwt \
|
||||
appservice"
|
||||
)]
|
||||
pub struct Config {
|
||||
/// The server_name is the pretty name of this server. It is used as a
|
||||
@@ -73,6 +78,24 @@ pub struct Config {
|
||||
/// example: "girlboss.ceo"
|
||||
pub server_name: OwnedServerName,
|
||||
|
||||
/// This is the only directory where tuwunel will save its data, including
|
||||
/// media. Note: this was previously "/var/lib/matrix-conduit".
|
||||
///
|
||||
/// YOU NEED TO EDIT THIS.
|
||||
///
|
||||
/// example: "/var/lib/tuwunel"
|
||||
pub database_path: PathBuf,
|
||||
|
||||
/// Text which will be added to the end of the user's displayname upon
|
||||
/// registration with a space before the text. In Conduit, this was the
|
||||
/// lightning bolt emoji.
|
||||
///
|
||||
/// To disable, set this to "" (an empty string).
|
||||
///
|
||||
/// default: "💕"
|
||||
#[serde(default = "default_new_user_displayname_suffix")]
|
||||
pub new_user_displayname_suffix: String,
|
||||
|
||||
#[allow(clippy::doc_link_with_quotes)]
|
||||
/// The default address (IPv4 or IPv6) tuwunel will listen on.
|
||||
///
|
||||
@@ -122,14 +145,6 @@ pub struct Config {
|
||||
#[serde(default = "default_unix_socket_perms")]
|
||||
pub unix_socket_perms: u32,
|
||||
|
||||
/// This is the only directory where tuwunel will save its data, including
|
||||
/// media. Note: this was previously "/var/lib/matrix-conduit".
|
||||
///
|
||||
/// YOU NEED TO EDIT THIS.
|
||||
///
|
||||
/// example: "/var/lib/tuwunel"
|
||||
pub database_path: PathBuf,
|
||||
|
||||
/// tuwunel supports online database backups using RocksDB's Backup engine
|
||||
/// API. To use this, set a database backup path that tuwunel can write
|
||||
/// to.
|
||||
@@ -147,16 +162,6 @@ pub struct Config {
|
||||
#[serde(default = "default_database_backups_to_keep")]
|
||||
pub database_backups_to_keep: i16,
|
||||
|
||||
/// Text which will be added to the end of the user's displayname upon
|
||||
/// registration with a space before the text. In Conduit, this was the
|
||||
/// lightning bolt emoji.
|
||||
///
|
||||
/// To disable, set this to "" (an empty string).
|
||||
///
|
||||
/// default: "🎔"
|
||||
#[serde(default = "default_new_user_displayname_suffix")]
|
||||
pub new_user_displayname_suffix: String,
|
||||
|
||||
/// Set this to any float value to multiply tuwunel's in-memory LRU caches
|
||||
/// with such as "auth_chain_cache_capacity".
|
||||
///
|
||||
@@ -247,6 +252,27 @@ pub struct Config {
|
||||
#[serde(default = "default_roomid_spacehierarchy_cache_capacity")]
|
||||
pub roomid_spacehierarchy_cache_capacity: u32,
|
||||
|
||||
/// Minimum timeout a client can request for long-polling sync. Requests
|
||||
/// will be clamped up to this value if smaller.
|
||||
///
|
||||
/// default: 5000
|
||||
#[serde(default = "default_client_sync_timeout_min")]
|
||||
pub client_sync_timeout_min: u64,
|
||||
|
||||
/// Default timeout for long-polling sync if a client does not request
|
||||
/// another in their query-string.
|
||||
///
|
||||
/// default: 30000
|
||||
#[serde(default = "default_client_sync_timeout_default")]
|
||||
pub client_sync_timeout_default: u64,
|
||||
|
||||
/// Maximum timeout a client can request for long-polling sync. Requests
|
||||
/// will be clamped down to this value if larger.
|
||||
///
|
||||
/// default: 90000
|
||||
#[serde(default = "default_client_sync_timeout_max")]
|
||||
pub client_sync_timeout_max: u64,
|
||||
|
||||
/// Maximum entries stored in DNS memory-cache. The size of an entry may
|
||||
/// vary so please take care if raising this value excessively. Only
|
||||
/// decrease this when using an external DNS cache. Please note that
|
||||
@@ -572,6 +598,26 @@ pub struct Config {
|
||||
#[serde(default)]
|
||||
pub allow_public_room_directory_without_auth: bool,
|
||||
|
||||
/// Allows room directory searches to match on partial room_id's when the
|
||||
/// search term starts with '!'.
|
||||
///
|
||||
/// default: true
|
||||
#[serde(default = "true_fn")]
|
||||
pub allow_public_room_search_by_id: bool,
|
||||
|
||||
/// Set this to false to limit results of rooms when searching by ID to
|
||||
/// those that would be found by an alias or other query; specifically
|
||||
/// those listed in the public rooms directory. By default this is set to
|
||||
/// true allowing any joinable room to match. This satisfies the Principle
|
||||
/// of Least Expectation when pasting a room_id into a search box with
|
||||
/// intent to join; many rooms simply opt-out of public listings. Therefor
|
||||
/// to prevent this feature from abuse, knowledge of several characters of
|
||||
/// the room_id is required before any results are returned.
|
||||
///
|
||||
/// default: true
|
||||
#[serde(default = "true_fn")]
|
||||
pub allow_unlisted_room_search_by_id: bool,
|
||||
|
||||
/// Allow guests/unauthenticated users to access TURN credentials.
|
||||
///
|
||||
/// This is the equivalent of Synapse's `turn_allow_guests` config option.
|
||||
@@ -814,6 +860,16 @@ pub struct Config {
|
||||
#[serde(default = "default_login_token_ttl")]
|
||||
pub login_token_ttl: u64,
|
||||
|
||||
/// Access token TTL in seconds.
|
||||
///
|
||||
/// For clients that support refresh-tokens, the access-token provided on
|
||||
/// login will be invalidated after this amount of time and the client will
|
||||
/// be soft-logged-out until refreshing it.
|
||||
///
|
||||
/// default: 604800
|
||||
#[serde(default = "default_access_token_ttl")]
|
||||
pub access_token_ttl: u64,
|
||||
|
||||
/// Static TURN username to provide the client if not using a shared secret
|
||||
/// ("turn_secret"), It is recommended to use a shared secret over static
|
||||
/// credentials.
|
||||
@@ -1313,6 +1369,12 @@ pub struct Config {
|
||||
#[serde(default)]
|
||||
pub allow_legacy_media: bool,
|
||||
|
||||
/// Fallback to requesting legacy unauthenticated media from remote servers.
|
||||
/// Unauthenticated media was removed in ~2024Q3; enabling this adds
|
||||
/// considerable federation requests which are unlikely to succeed.
|
||||
#[serde(default)]
|
||||
pub request_legacy_media: bool,
|
||||
|
||||
#[serde(default = "true_fn")]
|
||||
pub freeze_legacy_media: bool,
|
||||
|
||||
@@ -1621,6 +1683,23 @@ pub struct Config {
|
||||
#[serde(default = "default_admin_room_tag")]
|
||||
pub admin_room_tag: String,
|
||||
|
||||
/// Whether to grant the first user to register admin privileges by joining
|
||||
/// them to the admin room. Note that technically the next user to register
|
||||
/// when the admin room is empty (or only contains the server-user) is
|
||||
/// granted, and only when the admin room is enabled.
|
||||
///
|
||||
/// default: true
|
||||
#[serde(default = "true_fn")]
|
||||
pub grant_admin_to_first_user: bool,
|
||||
|
||||
/// Whether the admin room is created on first startup. Users should not set
|
||||
/// this to false. Developers can set this to false during integration tests
|
||||
/// to reduce activity and output.
|
||||
///
|
||||
/// default: true
|
||||
#[serde(default = "true_fn")]
|
||||
pub create_admin_room: bool,
|
||||
|
||||
/// Sentry.io crash/panic reporting, performance monitoring/metrics, etc.
|
||||
/// This is NOT enabled by default. tuwunel's default Sentry reporting
|
||||
/// endpoint domain is `o4509498990067712.ingest.us.sentry.io`.
|
||||
@@ -1818,6 +1897,10 @@ pub struct Config {
|
||||
#[serde(default)]
|
||||
pub jwt: JwtConfig,
|
||||
|
||||
// external structure; separate section
|
||||
#[serde(default)]
|
||||
pub appservice: BTreeMap<String, AppService>,
|
||||
|
||||
#[serde(flatten)]
|
||||
#[allow(clippy::zero_sized_map_values)]
|
||||
// this is a catchall, the map shouldn't be zero at runtime
|
||||
@@ -2088,6 +2171,126 @@ pub struct JwtConfig {
|
||||
pub validate_signature: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
#[config_example_generator(
|
||||
filename = "tuwunel-example.toml",
|
||||
section = "global.appservice.<ID>",
|
||||
ignore = "id users aliases rooms"
|
||||
)]
|
||||
pub struct AppService {
|
||||
#[serde(default)]
|
||||
pub id: String,
|
||||
|
||||
/// The URL for the application service.
|
||||
///
|
||||
/// Optionally set to `null` if no traffic is required.
|
||||
pub url: Option<String>,
|
||||
|
||||
/// A unique token for application services to use to authenticate requests
|
||||
/// to Homeservers.
|
||||
///
|
||||
/// default:
|
||||
/// display: sensitive
|
||||
pub as_token: String,
|
||||
|
||||
/// A unique token for Homeservers to use to authenticate requests to
|
||||
/// application services.
|
||||
///
|
||||
/// default:
|
||||
/// display: sensitive
|
||||
pub hs_token: String,
|
||||
|
||||
/// The localpart of the user associated with the application service.
|
||||
pub sender_localpart: Option<String>,
|
||||
|
||||
/// Events which are sent from certain users.
|
||||
#[serde(default)]
|
||||
pub users: Vec<AppServiceNamespace>,
|
||||
|
||||
/// Events which are sent in rooms with certain room aliases.
|
||||
#[serde(default)]
|
||||
pub aliases: Vec<AppServiceNamespace>,
|
||||
|
||||
/// Events which are sent in rooms with certain room IDs.
|
||||
#[serde(default)]
|
||||
pub rooms: Vec<AppServiceNamespace>,
|
||||
|
||||
/// Whether requests from masqueraded users are rate-limited.
|
||||
///
|
||||
/// The sender is excluded.
|
||||
#[serde(default)]
|
||||
pub rate_limited: bool,
|
||||
|
||||
/// The external protocols which the application service provides (e.g.
|
||||
/// IRC).
|
||||
///
|
||||
/// default: []
|
||||
#[serde(default)]
|
||||
pub protocols: Vec<String>,
|
||||
|
||||
/// Whether the application service wants to receive ephemeral data.
|
||||
///
|
||||
/// default: false
|
||||
#[serde(default)]
|
||||
pub receive_ephemeral: bool,
|
||||
|
||||
/// Whether the application service wants to do device management, as part
|
||||
/// of MSC4190.
|
||||
///
|
||||
/// default: false
|
||||
#[serde(default)]
|
||||
pub device_management: bool,
|
||||
}
|
||||
|
||||
impl From<AppService> for ruma::api::appservice::Registration {
|
||||
fn from(conf: AppService) -> Self {
|
||||
use ruma::api::appservice::Namespaces;
|
||||
|
||||
Self {
|
||||
id: conf.id,
|
||||
url: conf.url,
|
||||
as_token: conf.as_token,
|
||||
hs_token: conf.hs_token,
|
||||
receive_ephemeral: conf.receive_ephemeral,
|
||||
device_management: conf.device_management,
|
||||
protocols: conf.protocols.into(),
|
||||
rate_limited: conf.rate_limited.into(),
|
||||
sender_localpart: conf
|
||||
.sender_localpart
|
||||
.unwrap_or_else(|| EMPTY.into()),
|
||||
namespaces: Namespaces {
|
||||
users: conf.users.into_iter().map(Into::into).collect(),
|
||||
aliases: conf.aliases.into_iter().map(Into::into).collect(),
|
||||
rooms: conf.rooms.into_iter().map(Into::into).collect(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
#[config_example_generator(
|
||||
filename = "tuwunel-example.toml",
|
||||
section = "[global.appservice.<ID>.<users|rooms|aliases>]"
|
||||
)]
|
||||
pub struct AppServiceNamespace {
|
||||
/// Whether this application service has exclusive access to events within
|
||||
/// this namespace.
|
||||
#[serde(default)]
|
||||
pub exclusive: bool,
|
||||
|
||||
/// A regular expression defining which values this namespace includes.
|
||||
pub regex: String,
|
||||
}
|
||||
|
||||
impl From<AppServiceNamespace> for ruma::api::appservice::Namespace {
|
||||
fn from(conf: AppServiceNamespace) -> Self {
|
||||
Self {
|
||||
exclusive: conf.exclusive,
|
||||
regex: conf.regex,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[serde(transparent)]
|
||||
struct ListeningPort {
|
||||
@@ -2407,7 +2610,7 @@ fn default_url_preview_max_spider_size() -> usize {
|
||||
256_000 // 256KB
|
||||
}
|
||||
|
||||
fn default_new_user_displayname_suffix() -> String { "🎔".to_owned() }
|
||||
fn default_new_user_displayname_suffix() -> String { "💕".to_owned() }
|
||||
|
||||
fn default_sentry_endpoint() -> Option<Url> {
|
||||
let url = "https://8994b1762a6a95af9502a7900edabc4c@o4509498990067712.ingest.us.sentry.io/4509498993213440"
|
||||
@@ -2493,3 +2696,11 @@ fn default_ldap_name_attribute() -> String { String::from("givenName") }
|
||||
fn default_jwt_algorithm() -> String { "HS256".to_owned() }
|
||||
|
||||
fn default_jwt_format() -> String { "HMAC".to_owned() }
|
||||
|
||||
fn default_client_sync_timeout_min() -> u64 { 5000 }
|
||||
|
||||
fn default_client_sync_timeout_default() -> u64 { 30000 }
|
||||
|
||||
fn default_client_sync_timeout_max() -> u64 { 90000 }
|
||||
|
||||
fn default_access_token_ttl() -> u64 { 604_800 }
|
||||
|
||||
@@ -29,6 +29,8 @@ pub enum Error {
|
||||
#[error(transparent)]
|
||||
Std(#[from] Box<dyn std::error::Error + Send>),
|
||||
#[error(transparent)]
|
||||
SystemTime(#[from] std::time::SystemTimeError),
|
||||
#[error(transparent)]
|
||||
ThreadAccessError(#[from] std::thread::AccessError),
|
||||
#[error(transparent)]
|
||||
TryFromInt(#[from] std::num::TryFromIntError),
|
||||
|
||||
@@ -78,7 +78,7 @@ fn init_features() -> Result<Vec<String>> {
|
||||
Ok(features)
|
||||
}
|
||||
|
||||
fn append_features(features: &mut Vec<String>, manifest: &str) -> Result<()> {
|
||||
fn append_features(features: &mut Vec<String>, manifest: &str) -> Result {
|
||||
let manifest = Manifest::from_str(manifest)?;
|
||||
features.extend(manifest.features.keys().cloned());
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ pub fn fmt_markdown<S>(out: Arc<Mutex<S>>) -> Box<Closure>
|
||||
|
||||
pub fn fmt<F, S>(fun: F, out: Arc<Mutex<S>>) -> Box<Closure>
|
||||
where
|
||||
F: Fn(&mut S, &Level, &str, &str) -> Result<()> + Send + Sync + Copy + 'static,
|
||||
F: Fn(&mut S, &Level, &str, &str) -> Result + Send + Sync + Copy + 'static,
|
||||
S: std::fmt::Write + Send + 'static,
|
||||
{
|
||||
Box::new(move |data| call(fun, &mut *out.lock().expect("locked"), &data))
|
||||
@@ -30,7 +30,7 @@ pub fn fmt<F, S>(fun: F, out: Arc<Mutex<S>>) -> Box<Closure>
|
||||
|
||||
fn call<F, S>(fun: F, out: &mut S, data: &Data<'_>)
|
||||
where
|
||||
F: Fn(&mut S, &Level, &str, &str) -> Result<()>,
|
||||
F: Fn(&mut S, &Level, &str, &str) -> Result,
|
||||
S: std::fmt::Write,
|
||||
{
|
||||
fun(out, &data.level(), data.span_name(), data.message()).expect("log line appended");
|
||||
|
||||
@@ -73,7 +73,9 @@ impl ConsoleFormat {
|
||||
#[must_use]
|
||||
pub fn new(config: &Config) -> Self {
|
||||
Self {
|
||||
_compact: fmt::format().compact(),
|
||||
_compact: fmt::format()
|
||||
.compact()
|
||||
.with_ansi(config.log_colors),
|
||||
|
||||
full: Format::<Full>::default()
|
||||
.with_thread_ids(config.log_thread_ids)
|
||||
|
||||
+4
-4
@@ -3,7 +3,7 @@
|
||||
use super::{Level, color};
|
||||
use crate::Result;
|
||||
|
||||
pub fn html<S>(out: &mut S, level: &Level, span: &str, msg: &str) -> Result<()>
|
||||
pub fn html<S>(out: &mut S, level: &Level, span: &str, msg: &str) -> Result
|
||||
where
|
||||
S: Write + ?Sized,
|
||||
{
|
||||
@@ -18,7 +18,7 @@ pub fn html<S>(out: &mut S, level: &Level, span: &str, msg: &str) -> Result<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn markdown<S>(out: &mut S, level: &Level, span: &str, msg: &str) -> Result<()>
|
||||
pub fn markdown<S>(out: &mut S, level: &Level, span: &str, msg: &str) -> Result
|
||||
where
|
||||
S: Write + ?Sized,
|
||||
{
|
||||
@@ -28,7 +28,7 @@ pub fn markdown<S>(out: &mut S, level: &Level, span: &str, msg: &str) -> Result<
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn markdown_table<S>(out: &mut S, level: &Level, span: &str, msg: &str) -> Result<()>
|
||||
pub fn markdown_table<S>(out: &mut S, level: &Level, span: &str, msg: &str) -> Result
|
||||
where
|
||||
S: Write + ?Sized,
|
||||
{
|
||||
@@ -38,7 +38,7 @@ pub fn markdown_table<S>(out: &mut S, level: &Level, span: &str, msg: &str) -> R
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn markdown_table_head<S>(out: &mut S) -> Result<()>
|
||||
pub fn markdown_table_head<S>(out: &mut S) -> Result
|
||||
where
|
||||
S: Write + ?Sized,
|
||||
{
|
||||
|
||||
@@ -49,7 +49,7 @@ pub fn add(&self, name: &str, handle: Handle) {
|
||||
.insert(name.into(), handle);
|
||||
}
|
||||
|
||||
pub fn reload(&self, new_value: &EnvFilter, names: Option<&[&str]>) -> Result<()> {
|
||||
pub fn reload(&self, new_value: &EnvFilter, names: Option<&[&str]>) -> Result {
|
||||
self.handles
|
||||
.lock()
|
||||
.expect("locked")
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
use crate::{Result, utils};
|
||||
|
||||
/// Abstraction of a PDU so users can have their own PDU types.
|
||||
pub trait Event: Clone + Debug {
|
||||
pub trait Event: Clone + Debug + Send + Sync {
|
||||
/// Serialize into a Ruma JSON format, consuming.
|
||||
#[inline]
|
||||
fn into_format<T>(self) -> T
|
||||
|
||||
+10
-9
@@ -1,5 +1,6 @@
|
||||
mod builder;
|
||||
mod count;
|
||||
mod hashes;
|
||||
mod id;
|
||||
mod raw_id;
|
||||
mod redact;
|
||||
@@ -20,6 +21,7 @@
|
||||
Count as PduCount, Id as PduId, Pdu as PduEvent, RawId as RawPduId,
|
||||
builder::{Builder, Builder as PduBuilder},
|
||||
count::Count,
|
||||
hashes::EventHashes as EventHash,
|
||||
id::{ShortId, *},
|
||||
raw_id::*,
|
||||
};
|
||||
@@ -67,13 +69,6 @@ pub struct Pdu {
|
||||
pub signatures: Option<Box<RawJsonValue>>,
|
||||
}
|
||||
|
||||
/// Content hashes of a PDU.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct EventHash {
|
||||
/// The SHA-256 hash.
|
||||
pub sha256: String,
|
||||
}
|
||||
|
||||
impl Pdu {
|
||||
pub fn from_id_val(event_id: &EventId, mut json: CanonicalJsonObject) -> Result<Self> {
|
||||
let event_id = CanonicalJsonValue::String(event_id.into());
|
||||
@@ -84,7 +79,10 @@ pub fn from_id_val(event_id: &EventId, mut json: CanonicalJsonObject) -> Result<
|
||||
}
|
||||
}
|
||||
|
||||
impl Event for Pdu {
|
||||
impl Event for Pdu
|
||||
where
|
||||
Self: Send + Sync + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_ {
|
||||
self.auth_events.iter().map(AsRef::as_ref)
|
||||
@@ -137,7 +135,10 @@ fn into_pdu(self) -> Pdu { self }
|
||||
fn is_owned(&self) -> bool { true }
|
||||
}
|
||||
|
||||
impl Event for &Pdu {
|
||||
impl Event for &Pdu
|
||||
where
|
||||
Self: Send,
|
||||
{
|
||||
#[inline]
|
||||
fn auth_events(&self) -> impl DoubleEndedIterator<Item = &EventId> + Clone + Send + '_ {
|
||||
self.auth_events.iter().map(AsRef::as_ref)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::arrayvec::ArrayString;
|
||||
|
||||
/// Content hashes of a PDU.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct EventHashes {
|
||||
/// The SHA-256 hash.
|
||||
pub sha256: ArrayString<SHA256_LEN>,
|
||||
}
|
||||
|
||||
const SHA256_LEN: usize = 43;
|
||||
@@ -1,8 +1,10 @@
|
||||
use std::fmt;
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
|
||||
use super::{Count, Id, ShortEventId, ShortId, ShortRoomId};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
||||
pub enum RawId {
|
||||
Normal(RawIdNormal),
|
||||
Backfilled(RawIdBackfilled),
|
||||
@@ -61,6 +63,13 @@ pub fn as_bytes(&self) -> &[u8] {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for RawId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let id: Id = (*self).into();
|
||||
write!(f, "{id:?}")
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for RawId {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &[u8] { self.as_bytes() }
|
||||
|
||||
@@ -418,7 +418,7 @@ fn to_pdu_event<S>(
|
||||
auth_events,
|
||||
prev_events,
|
||||
depth: uint!(0),
|
||||
hashes: EventHash { sha256: String::new() },
|
||||
hashes: EventHash::default(),
|
||||
signatures: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,10 +571,11 @@ struct GetThirdPartyInvite {
|
||||
(target_membership == MembershipState::Join).then_some(&power_levels.users_default)
|
||||
});
|
||||
|
||||
let mut join_rules = JoinRule::Invite;
|
||||
if let Some(jr) = &join_rules_event {
|
||||
join_rules = from_json_str::<RoomJoinRulesEventContent>(jr.content().get())?.join_rule;
|
||||
}
|
||||
let join_rules = if let Some(jr) = &join_rules_event {
|
||||
from_json_str::<RoomJoinRulesEventContent>(jr.content().get())?.join_rule
|
||||
} else {
|
||||
JoinRule::Invite
|
||||
};
|
||||
|
||||
let power_levels_event_id = power_levels_event.as_ref().map(Event::event_id);
|
||||
let sender_membership_event_id = sender_membership_event
|
||||
@@ -649,7 +650,7 @@ struct GetThirdPartyInvite {
|
||||
warn!(?target_user_membership_event_id, "Banned user can't join");
|
||||
false
|
||||
} else if (join_rules == JoinRule::Invite
|
||||
|| room_version.allow_knocking && join_rules == JoinRule::Knock)
|
||||
|| room_version.allow_knocking && (join_rules == JoinRule::Knock || matches!(join_rules, JoinRule::KnockRestricted(_))))
|
||||
// If the join_rule is invite then allow if membership state is invite or join
|
||||
&& (target_user_current_membership == MembershipState::Join
|
||||
|| target_user_current_membership == MembershipState::Invite)
|
||||
|
||||
@@ -426,7 +426,7 @@ pub(crate) fn to_init_pdu_event(
|
||||
auth_events: vec![],
|
||||
prev_events: vec![],
|
||||
depth: uint!(0),
|
||||
hashes: EventHash { sha256: "".to_owned() },
|
||||
hashes: EventHash::default(),
|
||||
signatures: None,
|
||||
}
|
||||
}
|
||||
@@ -474,7 +474,7 @@ pub(crate) fn to_pdu_event<S>(
|
||||
auth_events,
|
||||
prev_events,
|
||||
depth: uint!(0),
|
||||
hashes: EventHash { sha256: "".to_owned() },
|
||||
hashes: EventHash::default(),
|
||||
signatures: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use std::sync::atomic::{AtomicU32, AtomicU64};
|
||||
|
||||
use tokio::runtime;
|
||||
use tokio_metrics::TaskMonitor;
|
||||
@@ -19,8 +19,9 @@ pub struct Metrics {
|
||||
runtime_intervals: std::sync::Mutex<Option<RuntimeIntervals>>,
|
||||
|
||||
// TODO: move stats
|
||||
pub requests_count: AtomicU64,
|
||||
pub requests_handle_finished: AtomicU64,
|
||||
pub requests_handle_active: AtomicU32,
|
||||
pub requests_handle_finished: AtomicU32,
|
||||
pub requests_panic: AtomicU32,
|
||||
}
|
||||
|
||||
@@ -48,8 +49,9 @@ pub fn new(runtime: Option<runtime::Handle>) -> Self {
|
||||
#[cfg(tokio_unstable)]
|
||||
runtime_intervals: std::sync::Mutex::new(runtime_intervals),
|
||||
|
||||
requests_count: AtomicU64::new(0),
|
||||
requests_handle_finished: AtomicU64::new(0),
|
||||
requests_handle_active: AtomicU32::new(0),
|
||||
requests_handle_finished: AtomicU32::new(0),
|
||||
requests_panic: AtomicU32::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user