Merge pull request 'Key backups and cross signing' (#132) from cross-signing into master
Reviewed-on: https://git.koesters.xyz/timo/conduit/pulls/132next
commit
e809d819ac
|
@ -1,5 +1,14 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "602d785912f476e480434627e8732e6766b760c045bbf897d9dfaa9f4fbd399c"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler32"
|
||||
version = "1.1.0"
|
||||
|
@ -26,13 +35,13 @@ checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
|
|||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.35"
|
||||
version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89cb5d814ab2a47fd66d3266e9efccb53ca4c740b7451043b8ffcf9a6208f3f8"
|
||||
checksum = "a265e3abeffdce30b2e26b7a11b222fe37c6067404001b434101457d0385eb92"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.18",
|
||||
"quote 1.0.7",
|
||||
"syn 1.0.31",
|
||||
"syn 1.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -43,7 +52,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
|||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi 0.3.8",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -52,6 +61,20 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05100821de9e028f12ae3d189176b41ee198341eb8f369956407fea2f5cc666c"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base16"
|
||||
version = "0.2.1"
|
||||
|
@ -75,9 +98,9 @@ checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
|
|||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.12.2"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e223af0dc48c96d4f8342ec01a4974f139df863896b316681efd36742f22cc67"
|
||||
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
|
@ -116,15 +139,18 @@ checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
|||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "0.5.4"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1"
|
||||
checksum = "118cf036fbb97d0816e3c34b2d7a1e8cfc60f68fcf63d550ddbe9bd5f59c213b"
|
||||
dependencies = [
|
||||
"loom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.54"
|
||||
version = "1.0.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311"
|
||||
checksum = "b1be3409f94d7bdceeb5f5fac551039d9b3f00e25da7a74fc4d33400a0d96368"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
@ -151,7 +177,7 @@ checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd"
|
|||
name = "conduit"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.12.2",
|
||||
"base64 0.12.3",
|
||||
"directories",
|
||||
"http",
|
||||
"image",
|
||||
|
@ -273,7 +299,7 @@ dependencies = [
|
|||
"bitflags",
|
||||
"proc-macro2 1.0.18",
|
||||
"quote 1.0.7",
|
||||
"syn 1.0.31",
|
||||
"syn 1.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -294,14 +320,14 @@ checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a"
|
|||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi 0.3.8",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
|
||||
checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
|
@ -340,7 +366,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi 0.3.8",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -416,7 +442,7 @@ dependencies = [
|
|||
"proc-macro-hack",
|
||||
"proc-macro2 1.0.18",
|
||||
"quote 1.0.7",
|
||||
"syn 1.0.31",
|
||||
"syn 1.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -463,6 +489,19 @@ dependencies = [
|
|||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generator"
|
||||
version = "0.6.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "add72f17bb81521258fcc8a7a3245b1e184e916bfbe34f0ea89558f440df5c68"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"log",
|
||||
"rustc_version",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.14"
|
||||
|
@ -484,6 +523,12 @@ dependencies = [
|
|||
"lzw",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.2.5"
|
||||
|
@ -598,9 +643,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.23.5"
|
||||
version = "0.23.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d534e95ad8b9d5aa614322d02352b4f1bf962254adcf02ac6f2def8be18498e8"
|
||||
checksum = "b5b0553fec6407d63fe2975b794dfb099f3f790bdc958823851af37b26404ab4"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
|
@ -632,9 +677,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
|
||||
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
|
||||
|
||||
[[package]]
|
||||
name = "jpeg-decoder"
|
||||
|
@ -656,9 +701,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "js_int"
|
||||
version = "0.1.5"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ab7bb370a788ad675863e035fd9bfa56a66a030a16a88ab80aeb6b18cbdf31"
|
||||
checksum = "1b2b63d60564122f2a7d6592c2f1d6c1c60e7a266b4d24715950a1ddad784f66"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
@ -703,6 +748,17 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "loom"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ecc775857611e1df29abba5c41355cdf540e7e9d4acfdf0f355eefee82330b7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"generator",
|
||||
"scoped-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lzw"
|
||||
version = "0.10.0"
|
||||
|
@ -829,7 +885,7 @@ checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7"
|
|||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"winapi 0.3.8",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -855,9 +911,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.2.4"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
|
||||
checksum = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
|
@ -883,6 +939,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.4.0"
|
||||
|
@ -891,9 +953,9 @@ checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d"
|
|||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.29"
|
||||
version = "0.10.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd"
|
||||
checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
|
@ -943,7 +1005,7 @@ dependencies = [
|
|||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"winapi 0.3.8",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -997,7 +1059,7 @@ checksum = "6a0ffd45cf79d88737d7cc85bfd5d2894bee1139b356e616fe85dc389c61aaf7"
|
|||
dependencies = [
|
||||
"proc-macro2 1.0.18",
|
||||
"quote 1.0.7",
|
||||
"syn 1.0.31",
|
||||
"syn 1.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1063,7 +1125,7 @@ version = "1.0.18"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
|
||||
dependencies = [
|
||||
"unicode-xid 0.2.0",
|
||||
"unicode-xid 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1148,7 +1210,7 @@ version = "0.5.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||
dependencies = [
|
||||
"winapi 0.3.8",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1157,7 +1219,7 @@ version = "0.10.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b82c9238b305f26f53443e3a4bc8528d64b8d0bee408ec949eb7bf5635ec680"
|
||||
dependencies = [
|
||||
"base64 0.12.2",
|
||||
"base64 0.12.3",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
|
@ -1187,9 +1249,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.14"
|
||||
version = "0.16.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06b3fefa4f12272808f809a0af618501fdaba41a58963c5fb72238ab0be09603"
|
||||
checksum = "952cd6b98c85bbc30efa1ba5783b8abf12fec8b3287ffa52605b9432313e34e4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
@ -1197,7 +1259,7 @@ dependencies = [
|
|||
"spin",
|
||||
"untrusted",
|
||||
"web-sys",
|
||||
"winapi 0.3.8",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1255,13 +1317,13 @@ dependencies = [
|
|||
"time",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"unicode-xid 0.2.0",
|
||||
"unicode-xid 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruma"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/ruma/ruma?rev=baa87104569b45dc07a9a7a16d3c7592ab8f4d6b#baa87104569b45dc07a9a7a16d3c7592ab8f4d6b"
|
||||
source = "git+https://github.com/timokoesters/ruma#5a30f9cfc6c168f25cfcf51f3d80b3594c0f59b1"
|
||||
dependencies = [
|
||||
"ruma-api",
|
||||
"ruma-client-api",
|
||||
|
@ -1275,7 +1337,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-api"
|
||||
version = "0.16.1"
|
||||
source = "git+https://github.com/ruma/ruma?rev=baa87104569b45dc07a9a7a16d3c7592ab8f4d6b#baa87104569b45dc07a9a7a16d3c7592ab8f4d6b"
|
||||
source = "git+https://github.com/timokoesters/ruma#5a30f9cfc6c168f25cfcf51f3d80b3594c0f59b1"
|
||||
dependencies = [
|
||||
"http",
|
||||
"percent-encoding 2.1.0",
|
||||
|
@ -1290,17 +1352,17 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-api-macros"
|
||||
version = "0.16.1"
|
||||
source = "git+https://github.com/ruma/ruma?rev=baa87104569b45dc07a9a7a16d3c7592ab8f4d6b#baa87104569b45dc07a9a7a16d3c7592ab8f4d6b"
|
||||
source = "git+https://github.com/timokoesters/ruma#5a30f9cfc6c168f25cfcf51f3d80b3594c0f59b1"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.18",
|
||||
"quote 1.0.7",
|
||||
"syn 1.0.31",
|
||||
"syn 1.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruma-client-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://github.com/ruma/ruma?rev=baa87104569b45dc07a9a7a16d3c7592ab8f4d6b#baa87104569b45dc07a9a7a16d3c7592ab8f4d6b"
|
||||
source = "git+https://github.com/timokoesters/ruma#5a30f9cfc6c168f25cfcf51f3d80b3594c0f59b1"
|
||||
dependencies = [
|
||||
"http",
|
||||
"js_int",
|
||||
|
@ -1317,7 +1379,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-common"
|
||||
version = "0.1.3"
|
||||
source = "git+https://github.com/ruma/ruma?rev=baa87104569b45dc07a9a7a16d3c7592ab8f4d6b#baa87104569b45dc07a9a7a16d3c7592ab8f4d6b"
|
||||
source = "git+https://github.com/timokoesters/ruma#5a30f9cfc6c168f25cfcf51f3d80b3594c0f59b1"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"ruma-serde",
|
||||
|
@ -1348,13 +1410,13 @@ source = "git+https://github.com/ruma/ruma-events?rev=c1ee72d#c1ee72db0f3107a97f
|
|||
dependencies = [
|
||||
"proc-macro2 1.0.18",
|
||||
"quote 1.0.7",
|
||||
"syn 1.0.31",
|
||||
"syn 1.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruma-federation-api"
|
||||
version = "0.0.2"
|
||||
source = "git+https://github.com/ruma/ruma?rev=baa87104569b45dc07a9a7a16d3c7592ab8f4d6b#baa87104569b45dc07a9a7a16d3c7592ab8f4d6b"
|
||||
source = "git+https://github.com/timokoesters/ruma#5a30f9cfc6c168f25cfcf51f3d80b3594c0f59b1"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"matches",
|
||||
|
@ -1369,7 +1431,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-identifiers"
|
||||
version = "0.16.2"
|
||||
source = "git+https://github.com/ruma/ruma?rev=baa87104569b45dc07a9a7a16d3c7592ab8f4d6b#baa87104569b45dc07a9a7a16d3c7592ab8f4d6b"
|
||||
source = "git+https://github.com/timokoesters/ruma#5a30f9cfc6c168f25cfcf51f3d80b3594c0f59b1"
|
||||
dependencies = [
|
||||
"rand",
|
||||
"serde",
|
||||
|
@ -1379,7 +1441,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-serde"
|
||||
version = "0.2.2"
|
||||
source = "git+https://github.com/ruma/ruma?rev=baa87104569b45dc07a9a7a16d3c7592ab8f4d6b#baa87104569b45dc07a9a7a16d3c7592ab8f4d6b"
|
||||
source = "git+https://github.com/timokoesters/ruma#5a30f9cfc6c168f25cfcf51f3d80b3594c0f59b1"
|
||||
dependencies = [
|
||||
"dtoa",
|
||||
"itoa",
|
||||
|
@ -1392,9 +1454,9 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-signatures"
|
||||
version = "0.6.0-dev.1"
|
||||
source = "git+https://github.com/ruma/ruma?rev=baa87104569b45dc07a9a7a16d3c7592ab8f4d6b#baa87104569b45dc07a9a7a16d3c7592ab8f4d6b"
|
||||
source = "git+https://github.com/timokoesters/ruma#5a30f9cfc6c168f25cfcf51f3d80b3594c0f59b1"
|
||||
dependencies = [
|
||||
"base64 0.12.2",
|
||||
"base64 0.12.3",
|
||||
"ring",
|
||||
"serde_json",
|
||||
"untrusted",
|
||||
|
@ -1418,12 +1480,27 @@ version = "0.8.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19"
|
||||
dependencies = [
|
||||
"base64 0.12.2",
|
||||
"base64 0.12.3",
|
||||
"blake2b_simd",
|
||||
"constant_time_eq",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.16.0"
|
||||
|
@ -1450,9 +1527,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"winapi 0.3.8",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
|
@ -1493,23 +1576,38 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.112"
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "736aac72d1eafe8e5962d1d1c3d99b0df526015ba40915cb3c49d042e92ec243"
|
||||
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||
dependencies = [
|
||||
"semver-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.112"
|
||||
version = "1.0.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf0343ce212ac0d3d6afd9391ac8e9c9efe06b533c8d33f660f6390cc4093f57"
|
||||
checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.18",
|
||||
"quote 1.0.7",
|
||||
"syn 1.0.31",
|
||||
"syn 1.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1553,10 +1651,11 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
|||
|
||||
[[package]]
|
||||
name = "sled"
|
||||
version = "0.31.0"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fb6824dde66ad33bf20c6e8476f5b82b871bc8bc3c129a10ea2f7dae5060fa3"
|
||||
checksum = "cdad3dc85d888056d3bd9954ffdf22d8a22701b6cd3aca4f6df4c436111898c4"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"crc32fast",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
|
@ -1582,7 +1681,7 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"winapi 0.3.8",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1615,7 +1714,7 @@ dependencies = [
|
|||
"heck",
|
||||
"proc-macro2 1.0.18",
|
||||
"quote 1.0.7",
|
||||
"syn 1.0.31",
|
||||
"syn 1.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1631,13 +1730,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.31"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5304cfdf27365b7585c25d4af91b35016ed21ef88f17ced89c7093b43dba8b6"
|
||||
checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.18",
|
||||
"quote 1.0.7",
|
||||
"unicode-xid 0.2.0",
|
||||
"unicode-xid 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1651,7 +1750,7 @@ dependencies = [
|
|||
"rand",
|
||||
"redox_syscall",
|
||||
"remove_dir_all",
|
||||
"winapi 0.3.8",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1671,7 +1770,7 @@ checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793"
|
|||
dependencies = [
|
||||
"proc-macro2 1.0.18",
|
||||
"quote 1.0.7",
|
||||
"syn 1.0.31",
|
||||
"syn 1.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1681,9 +1780,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi 0.3.8",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "0.2.21"
|
||||
|
@ -1704,7 +1809,7 @@ dependencies = [
|
|||
"signal-hook-registry",
|
||||
"slab",
|
||||
"tokio-macros",
|
||||
"winapi 0.3.8",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1715,7 +1820,7 @@ checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389"
|
|||
dependencies = [
|
||||
"proc-macro2 1.0.18",
|
||||
"quote 1.0.7",
|
||||
"syn 1.0.31",
|
||||
"syn 1.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1795,11 +1900,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.12"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4"
|
||||
checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1816,9 +1921,9 @@ checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
|||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
|
@ -1888,7 +1993,7 @@ dependencies = [
|
|||
"log",
|
||||
"proc-macro2 1.0.18",
|
||||
"quote 1.0.7",
|
||||
"syn 1.0.31",
|
||||
"syn 1.0.33",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
|
@ -1922,7 +2027,7 @@ checksum = "3156052d8ec77142051a533cdd686cba889537b213f948cd1d20869926e68e92"
|
|||
dependencies = [
|
||||
"proc-macro2 1.0.18",
|
||||
"quote 1.0.7",
|
||||
"syn 1.0.31",
|
||||
"syn 1.0.33",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
@ -1961,9 +2066,9 @@ checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
|||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.8"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
|
@ -1993,7 +2098,7 @@ version = "0.7.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
|
||||
dependencies = [
|
||||
"winapi 0.3.8",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
13
Cargo.toml
13
Cargo.toml
|
@ -15,7 +15,7 @@ edition = "2018"
|
|||
rocket = { git = "https://github.com/SergioBenitez/Rocket.git", rev = "4928e35ec5c4b9242f50d644282d9896d0160a10", features = ["tls"] }
|
||||
http = "0.2.1"
|
||||
log = "0.4.8"
|
||||
sled = "0.31.0"
|
||||
sled = "0.32.0"
|
||||
directories = "2.0.2"
|
||||
js_int = "0.1.5"
|
||||
serde_json = { version = "1.0.53", features = ["raw_value"] }
|
||||
|
@ -29,15 +29,16 @@ thiserror = "1.0.19"
|
|||
image = { version = "0.23.4", default-features = false, features = ["jpeg", "png", "gif"] }
|
||||
|
||||
[dependencies.ruma]
|
||||
git = "https://github.com/ruma/ruma"
|
||||
rev = "baa87104569b45dc07a9a7a16d3c7592ab8f4d6b"
|
||||
git = "https://github.com/timokoesters/ruma"
|
||||
#rev = "baa87104569b45dc07a9a7a16d3c7592ab8f4d6b"
|
||||
#path = "../ruma/ruma"
|
||||
features = ["rand", "client-api", "federation-api"]
|
||||
|
||||
# These are required only until ruma-events and ruma-federation-api are merged into ruma/ruma
|
||||
[patch.crates-io]
|
||||
ruma-common = { git = "https://github.com/ruma/ruma", rev = "baa87104569b45dc07a9a7a16d3c7592ab8f4d6b" }
|
||||
ruma-serde = { git = "https://github.com/ruma/ruma", rev = "baa87104569b45dc07a9a7a16d3c7592ab8f4d6b" }
|
||||
ruma-identifiers = { git = "https://github.com/ruma/ruma", rev = "baa87104569b45dc07a9a7a16d3c7592ab8f4d6b" }
|
||||
ruma-common = { git = "https://github.com/timokoesters/ruma" }
|
||||
ruma-serde = { git = "https://github.com/timokoesters/ruma" }
|
||||
ruma-identifiers = { git = "https://github.com/timokoesters/ruma" }
|
||||
#ruma-common = { path = "../ruma/ruma-common" }
|
||||
#ruma-serde = { path = "../ruma/ruma-serde" }
|
||||
#ruma-identifiers = { path = "../ruma/ruma-identifiers" }
|
||||
|
|
|
@ -5,6 +5,7 @@ use std::{
|
|||
};
|
||||
|
||||
use crate::{utils, ConduitResult, Database, Error, Ruma};
|
||||
use keys::{upload_signatures, upload_signing_keys};
|
||||
use log::warn;
|
||||
use rocket::{delete, get, options, post, put, State};
|
||||
use ruma::{
|
||||
|
@ -13,6 +14,10 @@ use ruma::{
|
|||
r0::{
|
||||
account::{get_username_availability, register},
|
||||
alias::{create_alias, delete_alias, get_alias},
|
||||
backup::{
|
||||
add_backup_keys, create_backup, get_backup, get_backup_keys, get_latest_backup,
|
||||
update_backup,
|
||||
},
|
||||
capabilities::get_capabilities,
|
||||
config::{get_global_account_data, set_global_account_data},
|
||||
context::get_context,
|
||||
|
@ -33,7 +38,7 @@ use ruma::{
|
|||
profile::{
|
||||
get_avatar_url, get_display_name, get_profile, set_avatar_url, set_display_name,
|
||||
},
|
||||
push::{get_pushrules_all, set_pushrule, set_pushrule_enabled},
|
||||
push::{get_pushers, get_pushrules_all, set_pushrule, set_pushrule_enabled},
|
||||
read_marker::set_read_marker,
|
||||
redact::redact_event,
|
||||
room::{self, create_room},
|
||||
|
@ -71,9 +76,13 @@ const SESSION_ID_LENGTH: usize = 256;
|
|||
|
||||
#[get("/_matrix/client/versions")]
|
||||
pub fn get_supported_versions_route() -> ConduitResult<get_supported_versions::Response> {
|
||||
let mut unstable_features = BTreeMap::new();
|
||||
|
||||
unstable_features.insert("org.matrix.e2e_cross_signing".to_owned(), true);
|
||||
|
||||
Ok(get_supported_versions::Response {
|
||||
versions: vec!["r0.5.0".to_owned(), "r0.6.0".to_owned()],
|
||||
unstable_features: BTreeMap::new(),
|
||||
unstable_features,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
@ -204,33 +213,7 @@ pub fn register_route(
|
|||
&EventType::PushRules,
|
||||
serde_json::to_value(ruma::events::push_rules::PushRulesEvent {
|
||||
content: ruma::events::push_rules::PushRulesEventContent {
|
||||
global: ruma::events::push_rules::Ruleset {
|
||||
content: vec![],
|
||||
override_: vec![ruma::events::push_rules::ConditionalPushRule {
|
||||
actions: vec![ruma::events::push_rules::Action::DontNotify],
|
||||
default: true,
|
||||
enabled: false,
|
||||
rule_id: ".m.rule.master".to_owned(),
|
||||
conditions: vec![],
|
||||
}],
|
||||
room: vec![],
|
||||
sender: vec![],
|
||||
underride: vec![ruma::events::push_rules::ConditionalPushRule {
|
||||
actions: vec![
|
||||
ruma::events::push_rules::Action::Notify,
|
||||
ruma::events::push_rules::Action::SetTweak(ruma::push::Tweak::Sound(
|
||||
"default".to_owned(),
|
||||
)),
|
||||
],
|
||||
default: true,
|
||||
enabled: true,
|
||||
rule_id: ".m.rule.message".to_owned(),
|
||||
conditions: vec![ruma::events::push_rules::PushCondition::EventMatch {
|
||||
key: "type".to_owned(),
|
||||
pattern: "m.room.message".to_owned(),
|
||||
}],
|
||||
}],
|
||||
},
|
||||
global: crate::push_rules::default_pushrules(&user_id),
|
||||
},
|
||||
})
|
||||
.expect("data is valid, we just created it")
|
||||
|
@ -375,11 +358,11 @@ pub fn get_pushrules_all_route(
|
|||
|
||||
#[put(
|
||||
"/_matrix/client/r0/pushrules/<_scope>/<_kind>/<_rule_id>",
|
||||
data = "<body>"
|
||||
//data = "<body>"
|
||||
)]
|
||||
pub fn set_pushrule_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<set_pushrule::Request>,
|
||||
//db: State<'_, Database>,
|
||||
//body: Ruma<set_pushrule::Request>,
|
||||
_scope: String,
|
||||
_kind: String,
|
||||
_rule_id: String,
|
||||
|
@ -502,8 +485,7 @@ pub fn set_displayname_route(
|
|||
displayname: body.displayname.clone(),
|
||||
..serde_json::from_value::<EventJson<_>>(
|
||||
db.rooms
|
||||
.room_state(&room_id)?
|
||||
.get(&(EventType::RoomMember, user_id.to_string()))
|
||||
.room_state_get(&room_id, &EventType::RoomMember, &user_id.to_string())?
|
||||
.ok_or_else(|| {
|
||||
Error::bad_database(
|
||||
"Tried to send displayname update for user not in the room.",
|
||||
|
@ -593,8 +575,7 @@ pub fn set_avatar_url_route(
|
|||
avatar_url: body.avatar_url.clone(),
|
||||
..serde_json::from_value::<EventJson<_>>(
|
||||
db.rooms
|
||||
.room_state(&room_id)?
|
||||
.get(&(EventType::RoomMember, user_id.to_string()))
|
||||
.room_state_get(&room_id, &EventType::RoomMember, &user_id.to_string())?
|
||||
.ok_or_else(|| {
|
||||
Error::bad_database(
|
||||
"Tried to send avatar url update for user not in the room.",
|
||||
|
@ -722,9 +703,12 @@ pub fn upload_keys_route(
|
|||
}
|
||||
|
||||
if let Some(device_keys) = &body.device_keys {
|
||||
// This check is needed to assure that signatures are kept
|
||||
if db.users.get_device_keys(user_id, device_id)?.is_none() {
|
||||
db.users
|
||||
.add_device_keys(user_id, device_id, device_keys, &db.globals)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(upload_keys::Response {
|
||||
one_time_key_counts: db.users.count_one_time_keys(user_id, device_id)?,
|
||||
|
@ -737,14 +721,19 @@ pub fn get_keys_route(
|
|||
db: State<'_, Database>,
|
||||
body: Ruma<get_keys::Request>,
|
||||
) -> ConduitResult<get_keys::Response> {
|
||||
let sender_id = body.user_id.as_ref().expect("user is authenticated");
|
||||
|
||||
let mut master_keys = BTreeMap::new();
|
||||
let mut self_signing_keys = BTreeMap::new();
|
||||
let mut user_signing_keys = BTreeMap::new();
|
||||
let mut device_keys = BTreeMap::new();
|
||||
|
||||
for (user_id, device_ids) in &body.device_keys {
|
||||
if device_ids.is_empty() {
|
||||
let mut container = BTreeMap::new();
|
||||
for result in db.users.all_device_keys(&user_id.clone()) {
|
||||
let (device_id, mut keys) = result?;
|
||||
|
||||
for device_id in db.users.all_device_ids(user_id) {
|
||||
let device_id = device_id?;
|
||||
if let Some(mut keys) = db.users.get_device_keys(user_id, &device_id)? {
|
||||
let metadata = db
|
||||
.users
|
||||
.get_device_metadata(user_id, &device_id)?
|
||||
|
@ -756,14 +745,14 @@ pub fn get_keys_route(
|
|||
device_display_name: metadata.display_name,
|
||||
});
|
||||
|
||||
container.insert(device_id, keys);
|
||||
container.insert(device_id.to_owned(), keys);
|
||||
}
|
||||
}
|
||||
device_keys.insert(user_id.clone(), container);
|
||||
} else {
|
||||
for device_id in device_ids {
|
||||
let mut container = BTreeMap::new();
|
||||
for keys in db.users.get_device_keys(&user_id.clone(), &device_id) {
|
||||
let mut keys = keys?;
|
||||
if let Some(mut keys) = db.users.get_device_keys(&user_id.clone(), &device_id)? {
|
||||
let metadata = db.users.get_device_metadata(user_id, &device_id)?.ok_or(
|
||||
Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
|
@ -780,11 +769,26 @@ pub fn get_keys_route(
|
|||
device_keys.insert(user_id.clone(), container);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(master_key) = db.users.get_master_key(user_id, sender_id)? {
|
||||
master_keys.insert(user_id.clone(), master_key);
|
||||
}
|
||||
if let Some(self_signing_key) = db.users.get_self_signing_key(user_id, sender_id)? {
|
||||
self_signing_keys.insert(user_id.clone(), self_signing_key);
|
||||
}
|
||||
if user_id == sender_id {
|
||||
if let Some(user_signing_key) = db.users.get_user_signing_key(sender_id)? {
|
||||
user_signing_keys.insert(user_id.clone(), user_signing_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(get_keys::Response {
|
||||
failures: BTreeMap::new(),
|
||||
master_keys,
|
||||
self_signing_keys,
|
||||
user_signing_keys,
|
||||
device_keys,
|
||||
failures: BTreeMap::new(),
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
@ -817,6 +821,125 @@ pub fn claim_keys_route(
|
|||
.into())
|
||||
}
|
||||
|
||||
#[post("/_matrix/client/unstable/room_keys/version", data = "<body>")]
|
||||
pub fn create_backup_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<create_backup::Request>,
|
||||
) -> ConduitResult<create_backup::Response> {
|
||||
let user_id = body.user_id.as_ref().expect("user is authenticated");
|
||||
let version = db
|
||||
.key_backups
|
||||
.create_backup(&user_id, &body.algorithm, &db.globals)?;
|
||||
|
||||
Ok(create_backup::Response { version }.into())
|
||||
}
|
||||
|
||||
#[put(
|
||||
"/_matrix/client/unstable/room_keys/version/<_version>",
|
||||
data = "<body>"
|
||||
)]
|
||||
pub fn update_backup_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<update_backup::Request>,
|
||||
_version: String,
|
||||
) -> ConduitResult<update_backup::Response> {
|
||||
let user_id = body.user_id.as_ref().expect("user is authenticated");
|
||||
db.key_backups
|
||||
.update_backup(&user_id, &body.version, &body.algorithm, &db.globals)?;
|
||||
|
||||
Ok(update_backup::Response.into())
|
||||
}
|
||||
|
||||
#[get("/_matrix/client/unstable/room_keys/version", data = "<body>")]
|
||||
pub fn get_latest_backup_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_latest_backup::Request>,
|
||||
) -> ConduitResult<get_latest_backup::Response> {
|
||||
let user_id = body.user_id.as_ref().expect("user is authenticated");
|
||||
|
||||
let (version, algorithm) =
|
||||
db.key_backups
|
||||
.get_latest_backup(&user_id)?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"Key backup does not exist.",
|
||||
))?;
|
||||
|
||||
Ok(get_latest_backup::Response {
|
||||
algorithm,
|
||||
count: (db.key_backups.count_keys(user_id, &version)? as u32).into(),
|
||||
etag: db.key_backups.get_etag(user_id, &version)?,
|
||||
version,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
#[get(
|
||||
"/_matrix/client/unstable/room_keys/version/<_version>",
|
||||
data = "<body>"
|
||||
)]
|
||||
pub fn get_backup_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_backup::Request>,
|
||||
_version: String,
|
||||
) -> ConduitResult<get_backup::Response> {
|
||||
let user_id = body.user_id.as_ref().expect("user is authenticated");
|
||||
let algorithm =
|
||||
db.key_backups
|
||||
.get_backup(&user_id, &body.version)?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"Key backup does not exist.",
|
||||
))?;
|
||||
|
||||
Ok(get_backup::Response {
|
||||
algorithm,
|
||||
count: (db.key_backups.count_keys(user_id, &body.version)? as u32).into(),
|
||||
etag: db.key_backups.get_etag(user_id, &body.version)?,
|
||||
version: body.version.clone(),
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
#[put("/_matrix/client/unstable/room_keys/keys", data = "<body>")]
|
||||
pub fn add_backup_keys_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<add_backup_keys::Request>,
|
||||
) -> ConduitResult<add_backup_keys::Response> {
|
||||
let user_id = body.user_id.as_ref().expect("user is authenticated");
|
||||
|
||||
for (room_id, room) in &body.rooms {
|
||||
for (session_id, key_data) in &room.sessions {
|
||||
db.key_backups.add_key(
|
||||
&user_id,
|
||||
&body.version,
|
||||
&room_id,
|
||||
&session_id,
|
||||
&key_data,
|
||||
&db.globals,
|
||||
)?
|
||||
}
|
||||
}
|
||||
|
||||
Ok(add_backup_keys::Response {
|
||||
count: (db.key_backups.count_keys(user_id, &body.version)? as u32).into(),
|
||||
etag: db.key_backups.get_etag(user_id, &body.version)?,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
#[get("/_matrix/client/unstable/room_keys/keys", data = "<body>")]
|
||||
pub fn get_backup_keys_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_backup_keys::Request>,
|
||||
) -> ConduitResult<get_backup_keys::Response> {
|
||||
let user_id = body.user_id.as_ref().expect("user is authenticated");
|
||||
|
||||
let rooms = db.key_backups.get_all(&user_id, &body.version)?;
|
||||
|
||||
Ok(get_backup_keys::Response { rooms }.into())
|
||||
}
|
||||
|
||||
#[post("/_matrix/client/r0/rooms/<_room_id>/read_markers", data = "<body>")]
|
||||
pub fn set_read_marker_route(
|
||||
db: State<'_, Database>,
|
||||
|
@ -1265,35 +1388,13 @@ pub fn join_room_by_id_route(
|
|||
|
||||
// TODO: Ask a remote server if we don't have this room
|
||||
|
||||
let event = db
|
||||
.rooms
|
||||
.room_state(&body.room_id)?
|
||||
.get(&(EventType::RoomMember, user_id.to_string()))
|
||||
.map_or_else(
|
||||
|| {
|
||||
// There was no existing membership event
|
||||
Ok::<_, Error>(member::MemberEventContent {
|
||||
let event = member::MemberEventContent {
|
||||
membership: member::MembershipState::Join,
|
||||
displayname: db.users.displayname(&user_id)?,
|
||||
avatar_url: db.users.avatar_url(&user_id)?,
|
||||
is_direct: None,
|
||||
third_party_invite: None,
|
||||
})
|
||||
},
|
||||
|pdu| {
|
||||
// We change the existing membership event
|
||||
let mut event = serde_json::from_value::<EventJson<member::MemberEventContent>>(
|
||||
pdu.content.clone(),
|
||||
)
|
||||
.map_err(|_| Error::bad_database("Invalid member event in db."))?
|
||||
.deserialize()
|
||||
.map_err(|_| Error::bad_database("Invalid member event in db."))?;
|
||||
event.membership = member::MembershipState::Join;
|
||||
event.displayname = db.users.displayname(&user_id)?;
|
||||
event.avatar_url = db.users.avatar_url(&user_id)?;
|
||||
Ok(event)
|
||||
},
|
||||
)?;
|
||||
};
|
||||
|
||||
db.rooms.append_pdu(
|
||||
body.room_id.clone(),
|
||||
|
@ -1348,11 +1449,10 @@ pub fn leave_room_route(
|
|||
_room_id: String,
|
||||
) -> ConduitResult<leave_room::Response> {
|
||||
let user_id = body.user_id.as_ref().expect("user is authenticated");
|
||||
let state = db.rooms.room_state(&body.room_id)?;
|
||||
|
||||
let mut event = serde_json::from_value::<EventJson<member::MemberEventContent>>(
|
||||
state
|
||||
.get(&(EventType::RoomMember, user_id.to_string()))
|
||||
db.rooms
|
||||
.room_state_get(&body.room_id, &EventType::RoomMember, &user_id.to_string())?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::BadState,
|
||||
"Cannot leave a room you are not a member of.",
|
||||
|
@ -1387,12 +1487,11 @@ pub fn kick_user_route(
|
|||
_room_id: String,
|
||||
) -> ConduitResult<kick_user::Response> {
|
||||
let user_id = body.user_id.as_ref().expect("user is authenticated");
|
||||
let state = db.rooms.room_state(&body.room_id)?;
|
||||
|
||||
let mut event =
|
||||
serde_json::from_value::<EventJson<ruma::events::room::member::MemberEventContent>>(
|
||||
state
|
||||
.get(&(EventType::RoomMember, user_id.to_string()))
|
||||
db.rooms
|
||||
.room_state_get(&body.room_id, &EventType::RoomMember, &user_id.to_string())?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::BadState,
|
||||
"Cannot kick member that's not in the room.",
|
||||
|
@ -1428,12 +1527,12 @@ pub fn ban_user_route(
|
|||
_room_id: String,
|
||||
) -> ConduitResult<ban_user::Response> {
|
||||
let user_id = body.user_id.as_ref().expect("user is authenticated");
|
||||
let state = db.rooms.room_state(&body.room_id)?;
|
||||
|
||||
// TODO: reason
|
||||
|
||||
let event = state
|
||||
.get(&(EventType::RoomMember, user_id.to_string()))
|
||||
let event = db
|
||||
.rooms
|
||||
.room_state_get(&body.room_id, &EventType::RoomMember, &user_id.to_string())?
|
||||
.map_or(
|
||||
Ok::<_, Error>(member::MemberEventContent {
|
||||
membership: member::MembershipState::Ban,
|
||||
|
@ -1475,12 +1574,11 @@ pub fn unban_user_route(
|
|||
_room_id: String,
|
||||
) -> ConduitResult<unban_user::Response> {
|
||||
let user_id = body.user_id.as_ref().expect("user is authenticated");
|
||||
let state = db.rooms.room_state(&body.room_id)?;
|
||||
|
||||
let mut event =
|
||||
serde_json::from_value::<EventJson<ruma::events::room::member::MemberEventContent>>(
|
||||
state
|
||||
.get(&(EventType::RoomMember, user_id.to_string()))
|
||||
db.rooms
|
||||
.room_state_get(&body.room_id, &EventType::RoomMember, &user_id.to_string())?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::BadState,
|
||||
"Cannot unban a user who is not banned.",
|
||||
|
@ -1642,7 +1740,8 @@ pub async fn get_public_rooms_filtered_route(
|
|||
.map(|room_id| {
|
||||
let room_id = room_id?;
|
||||
|
||||
let state = db.rooms.room_state(&room_id)?;
|
||||
// TODO: Do not load full state?
|
||||
let state = db.rooms.room_state_full(&room_id)?;
|
||||
|
||||
let chunk = directory::PublicRoomsChunk {
|
||||
aliases: Vec::new(),
|
||||
|
@ -1774,10 +1873,30 @@ pub fn search_users_route(
|
|||
.into())
|
||||
}
|
||||
|
||||
#[get("/_matrix/client/r0/rooms/<_room_id>/members")]
|
||||
pub fn get_member_events_route(_room_id: String) -> ConduitResult<get_member_events::Response> {
|
||||
warn!("TODO: get_member_events_route");
|
||||
Ok(get_member_events::Response { chunk: Vec::new() }.into())
|
||||
#[get("/_matrix/client/r0/rooms/<_room_id>/members", data = "<body>")]
|
||||
pub fn get_member_events_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<get_member_events::Request>,
|
||||
_room_id: String,
|
||||
) -> ConduitResult<get_member_events::Response> {
|
||||
let user_id = body.user_id.as_ref().expect("user is authenticated");
|
||||
|
||||
if !db.rooms.is_joined(user_id, &body.room_id)? {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"You don't have permission to view this room.",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(get_member_events::Response {
|
||||
chunk: db
|
||||
.rooms
|
||||
.room_state_type(&body.room_id, &EventType::RoomMember)?
|
||||
.values()
|
||||
.map(|pdu| pdu.to_member_event())
|
||||
.collect(),
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
#[get("/_matrix/client/r0/thirdparty/protocols")]
|
||||
|
@ -1951,7 +2070,7 @@ pub fn get_state_events_route(
|
|||
Ok(get_state_events::Response {
|
||||
room_state: db
|
||||
.rooms
|
||||
.room_state(&body.room_id)?
|
||||
.room_state_full(&body.room_id)?
|
||||
.values()
|
||||
.map(|pdu| pdu.to_state_event())
|
||||
.collect(),
|
||||
|
@ -1979,10 +2098,9 @@ pub fn get_state_events_for_key_route(
|
|||
));
|
||||
}
|
||||
|
||||
let state = db.rooms.room_state(&body.room_id)?;
|
||||
|
||||
let event = state
|
||||
.get(&(body.event_type.clone(), body.state_key.clone()))
|
||||
let event = db
|
||||
.rooms
|
||||
.room_state_get(&body.room_id, &body.event_type, &body.state_key)?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"State event not found.",
|
||||
|
@ -2014,17 +2132,16 @@ pub fn get_state_events_for_empty_key_route(
|
|||
));
|
||||
}
|
||||
|
||||
let state = db.rooms.room_state(&body.room_id)?;
|
||||
|
||||
let event = state
|
||||
.get(&(body.event_type.clone(), "".to_owned()))
|
||||
let event = db
|
||||
.rooms
|
||||
.room_state_get(&body.room_id, &body.event_type, "")?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"State event not found.",
|
||||
))?;
|
||||
|
||||
Ok(get_state_events_for_empty_key::Response {
|
||||
content: serde_json::value::to_raw_value(event)
|
||||
content: serde_json::value::to_raw_value(&event)
|
||||
.map_err(|_| Error::bad_database("Invalid event content in database"))?,
|
||||
}
|
||||
.into())
|
||||
|
@ -2053,7 +2170,7 @@ pub fn sync_route(
|
|||
|
||||
let mut pdus = db
|
||||
.rooms
|
||||
.pdus_since(&room_id, since)?
|
||||
.pdus_since(&user_id, &room_id, since)?
|
||||
.filter_map(|r| r.ok()) // Filter out buggy events
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
@ -2068,7 +2185,7 @@ pub fn sync_route(
|
|||
let content = serde_json::from_value::<
|
||||
EventJson<ruma::events::room::member::MemberEventContent>,
|
||||
>(pdu.content.clone())
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in database."))?
|
||||
.expect("EventJson::from_value always works")
|
||||
.deserialize()
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in database."))?;
|
||||
if content.membership == ruma::events::room::member::MembershipState::Join {
|
||||
|
@ -2081,7 +2198,7 @@ pub fn sync_route(
|
|||
}
|
||||
}
|
||||
|
||||
let state = db.rooms.room_state(&room_id)?;
|
||||
let members = db.rooms.room_state_type(&room_id, &EventType::RoomMember)?;
|
||||
|
||||
let (joined_member_count, invited_member_count, heroes) = if send_member_count {
|
||||
let joined_member_count = db.rooms.room_members(&room_id).count();
|
||||
|
@ -2096,7 +2213,7 @@ pub fn sync_route(
|
|||
|
||||
for hero in db
|
||||
.rooms
|
||||
.all_pdus(&room_id)?
|
||||
.all_pdus(&user_id, &room_id)?
|
||||
.filter_map(|pdu| pdu.ok()) // Ignore all broken pdus
|
||||
.filter(|pdu| pdu.kind == EventType::RoomMember)
|
||||
.map(|pdu| {
|
||||
|
@ -2111,8 +2228,8 @@ pub fn sync_route(
|
|||
let current_content = serde_json::from_value::<
|
||||
EventJson<ruma::events::room::member::MemberEventContent>,
|
||||
>(
|
||||
state
|
||||
.get(&(EventType::RoomMember, state_key.clone()))
|
||||
members
|
||||
.get(state_key)
|
||||
.ok_or_else(|| {
|
||||
Error::bad_database(
|
||||
"A user that joined once has no member event anymore.",
|
||||
|
@ -2170,7 +2287,7 @@ pub fn sync_route(
|
|||
if let Some(last_read) = db.rooms.edus.room_read_get(&room_id, &user_id)? {
|
||||
Some(
|
||||
(db.rooms
|
||||
.pdus_since(&room_id, last_read)?
|
||||
.pdus_since(&user_id, &room_id, last_read)?
|
||||
.filter_map(|pdu| pdu.ok()) // Filter out buggy events
|
||||
.filter(|pdu| {
|
||||
matches!(
|
||||
|
@ -2264,7 +2381,8 @@ pub fn sync_route(
|
|||
// TODO: state before timeline
|
||||
state: sync_events::State {
|
||||
events: if joined_since_last_sync {
|
||||
state
|
||||
db.rooms
|
||||
.room_state_full(&room_id)?
|
||||
.into_iter()
|
||||
.map(|(_, pdu)| pdu.to_state_event())
|
||||
.collect()
|
||||
|
@ -2283,7 +2401,7 @@ pub fn sync_route(
|
|||
let mut left_rooms = BTreeMap::new();
|
||||
for room_id in db.rooms.rooms_left(&user_id) {
|
||||
let room_id = room_id?;
|
||||
let pdus = db.rooms.pdus_since(&room_id, since)?;
|
||||
let pdus = db.rooms.pdus_since(&user_id, &room_id, since)?;
|
||||
let room_events = pdus
|
||||
.filter_map(|pdu| pdu.ok()) // Filter out buggy events
|
||||
.map(|pdu| pdu.to_room_event())
|
||||
|
@ -2337,7 +2455,7 @@ pub fn sync_route(
|
|||
invite_state: sync_events::InviteState {
|
||||
events: db
|
||||
.rooms
|
||||
.room_state(&room_id)?
|
||||
.room_state_full(&room_id)?
|
||||
.into_iter()
|
||||
.map(|(_, pdu)| pdu.to_stripped_state_event())
|
||||
.collect(),
|
||||
|
@ -2387,7 +2505,7 @@ pub fn sync_route(
|
|||
device_lists: sync_events::DeviceLists {
|
||||
changed: if since != 0 {
|
||||
db.users
|
||||
.device_keys_changed(since)
|
||||
.keys_changed(since)
|
||||
.filter_map(|u| u.ok())
|
||||
.collect() // Filter out buggy events
|
||||
} else {
|
||||
|
@ -2438,7 +2556,7 @@ pub fn get_context_route(
|
|||
|
||||
let events_before = db
|
||||
.rooms
|
||||
.pdus_until(&body.room_id, base_token)
|
||||
.pdus_until(&user_id, &body.room_id, base_token)
|
||||
.take(
|
||||
u32::try_from(body.limit).map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::InvalidParam, "Limit value is invalid.")
|
||||
|
@ -2464,7 +2582,7 @@ pub fn get_context_route(
|
|||
|
||||
let events_after = db
|
||||
.rooms
|
||||
.pdus_after(&body.room_id, base_token)
|
||||
.pdus_after(&user_id, &body.room_id, base_token)
|
||||
.take(
|
||||
u32::try_from(body.limit).map_err(|_| {
|
||||
Error::BadRequest(ErrorKind::InvalidParam, "Limit value is invalid.")
|
||||
|
@ -2496,7 +2614,7 @@ pub fn get_context_route(
|
|||
events_after,
|
||||
state: db // TODO: State at event
|
||||
.rooms
|
||||
.room_state(&body.room_id)?
|
||||
.room_state_full(&body.room_id)?
|
||||
.values()
|
||||
.map(|pdu| pdu.to_state_event())
|
||||
.collect(),
|
||||
|
@ -2528,7 +2646,7 @@ pub fn get_message_events_route(
|
|||
get_message_events::Direction::Forward => {
|
||||
let events_after = db
|
||||
.rooms
|
||||
.pdus_after(&body.room_id, from)
|
||||
.pdus_after(&user_id, &body.room_id, from)
|
||||
// Use limit or else 10
|
||||
.take(body.limit.map_or(Ok::<_, Error>(10_usize), |l| {
|
||||
Ok(u32::try_from(l).map_err(|_| {
|
||||
|
@ -2563,7 +2681,7 @@ pub fn get_message_events_route(
|
|||
get_message_events::Direction::Backward => {
|
||||
let events_before = db
|
||||
.rooms
|
||||
.pdus_until(&body.room_id, from)
|
||||
.pdus_until(&user_id, &body.room_id, from)
|
||||
// Use limit or else 10
|
||||
.take(body.limit.map_or(Ok::<_, Error>(10_usize), |l| {
|
||||
Ok(u32::try_from(l).map_err(|_| {
|
||||
|
@ -2883,6 +3001,122 @@ pub fn delete_devices_route(
|
|||
Ok(delete_devices::Response.into())
|
||||
}
|
||||
|
||||
#[post("/_matrix/client/unstable/keys/device_signing/upload", data = "<body>")]
|
||||
pub fn upload_signing_keys_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<upload_signing_keys::Request>,
|
||||
) -> ConduitResult<upload_signing_keys::Response> {
|
||||
let user_id = body.user_id.as_ref().expect("user is authenticated");
|
||||
let device_id = body.device_id.as_ref().expect("user is authenticated");
|
||||
|
||||
// UIAA
|
||||
let mut uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow {
|
||||
stages: vec!["m.login.password".to_owned()],
|
||||
}],
|
||||
completed: Vec::new(),
|
||||
params: Default::default(),
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
|
||||
if let Some(auth) = &body.auth {
|
||||
let (worked, uiaainfo) = db.uiaa.try_auth(
|
||||
&user_id,
|
||||
&device_id,
|
||||
auth,
|
||||
&uiaainfo,
|
||||
&db.users,
|
||||
&db.globals,
|
||||
)?;
|
||||
if !worked {
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
}
|
||||
// Success!
|
||||
} else {
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
db.uiaa.create(&user_id, &device_id, &uiaainfo)?;
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
}
|
||||
|
||||
if let Some(master_key) = &body.master_key {
|
||||
db.users.add_cross_signing_keys(
|
||||
user_id,
|
||||
&master_key,
|
||||
&body.self_signing_key,
|
||||
&body.user_signing_key,
|
||||
&db.globals,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(upload_signing_keys::Response.into())
|
||||
}
|
||||
|
||||
#[post("/_matrix/client/unstable/keys/signatures/upload", data = "<body>")]
|
||||
pub fn upload_signatures_route(
|
||||
db: State<'_, Database>,
|
||||
body: Ruma<upload_signatures::Request>,
|
||||
) -> ConduitResult<upload_signatures::Response> {
|
||||
let sender_id = body.user_id.as_ref().expect("user is authenticated");
|
||||
|
||||
for (user_id, signed_keys) in &body.signed_keys {
|
||||
for (key_id, signed_key) in signed_keys {
|
||||
for signature in signed_key
|
||||
.get("signatures")
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Missing signatures field.",
|
||||
))?
|
||||
.get(sender_id.to_string())
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Invalid user in signatures field.",
|
||||
))?
|
||||
.as_object()
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Invalid signature.",
|
||||
))?
|
||||
.clone()
|
||||
.into_iter()
|
||||
{
|
||||
// Signature validation?
|
||||
let signature = (
|
||||
signature.0,
|
||||
signature
|
||||
.1
|
||||
.as_str()
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Invalid signature value.",
|
||||
))?
|
||||
.to_owned(),
|
||||
);
|
||||
db.users
|
||||
.sign_key(&user_id, &key_id, signature, &sender_id, &db.globals)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(upload_signatures::Response.into())
|
||||
}
|
||||
|
||||
#[get("/_matrix/client/r0/pushers")]
|
||||
pub fn pushers_route() -> ConduitResult<get_pushers::Response> {
|
||||
Ok(get_pushers::Response {
|
||||
pushers: Vec::new(),
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
#[post("/_matrix/client/r0/pushers/set")]
|
||||
pub fn set_pushers_route() -> ConduitResult<get_pushers::Response> {
|
||||
Ok(get_pushers::Response {
|
||||
pushers: Vec::new(),
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
#[options("/<_segments..>")]
|
||||
pub fn options_route(
|
||||
_segments: rocket::http::uri::Segments<'_>,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
pub(self) mod account_data;
|
||||
pub(self) mod global_edus;
|
||||
pub(self) mod globals;
|
||||
pub(self) mod key_backups;
|
||||
pub(self) mod media;
|
||||
pub(self) mod rooms;
|
||||
pub(self) mod uiaa;
|
||||
|
@ -21,6 +22,7 @@ pub struct Database {
|
|||
pub account_data: account_data::AccountData,
|
||||
pub global_edus: global_edus::GlobalEdus,
|
||||
pub media: media::Media,
|
||||
pub key_backups: key_backups::KeyBackups,
|
||||
pub _db: sled::Db,
|
||||
}
|
||||
|
||||
|
@ -73,8 +75,11 @@ impl Database {
|
|||
userdeviceid_metadata: db.open_tree("userdeviceid_metadata")?,
|
||||
token_userdeviceid: db.open_tree("token_userdeviceid")?,
|
||||
onetimekeyid_onetimekeys: db.open_tree("onetimekeyid_onetimekeys")?,
|
||||
userdeviceid_devicekeys: db.open_tree("userdeviceid_devicekeys")?,
|
||||
devicekeychangeid_userid: db.open_tree("devicekeychangeid_userid")?,
|
||||
keychangeid_userid: db.open_tree("devicekeychangeid_userid")?,
|
||||
keyid_key: db.open_tree("keyid_key")?,
|
||||
userid_masterkeyid: db.open_tree("userid_masterkeyid")?,
|
||||
userid_selfsigningkeyid: db.open_tree("userid_selfsigningkeyid")?,
|
||||
userid_usersigningkeyid: db.open_tree("userid_usersigningkeyid")?,
|
||||
todeviceid_events: db.open_tree("todeviceid_events")?,
|
||||
},
|
||||
uiaa: uiaa::Uiaa {
|
||||
|
@ -111,6 +116,11 @@ impl Database {
|
|||
media: media::Media {
|
||||
mediaid_file: db.open_tree("mediaid_file")?,
|
||||
},
|
||||
key_backups: key_backups::KeyBackups {
|
||||
backupid_algorithm: db.open_tree("backupid_algorithm")?,
|
||||
backupid_etag: db.open_tree("backupid_etag")?,
|
||||
backupkeyid_backup: db.open_tree("backupkeyid_backupmetadata")?,
|
||||
},
|
||||
_db: db,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
use crate::{utils, Error, Result};
|
||||
use ruma::{
|
||||
api::client::{
|
||||
error::ErrorKind,
|
||||
r0::backup::{get_backup_keys::Sessions, BackupAlgorithm, KeyData},
|
||||
},
|
||||
identifiers::{RoomId, UserId},
|
||||
};
|
||||
use std::{collections::BTreeMap, convert::TryFrom};
|
||||
|
||||
pub struct KeyBackups {
|
||||
pub(super) backupid_algorithm: sled::Tree, // BackupId = UserId + Version(Count)
|
||||
pub(super) backupid_etag: sled::Tree, // BackupId = UserId + Version(Count)
|
||||
pub(super) backupkeyid_backup: sled::Tree, // BackupKeyId = UserId + Version + RoomId + SessionId
|
||||
}
|
||||
|
||||
impl KeyBackups {
|
||||
pub fn create_backup(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
backup_metadata: &BackupAlgorithm,
|
||||
globals: &super::globals::Globals,
|
||||
) -> Result<String> {
|
||||
let version = globals.next_count()?.to_string();
|
||||
|
||||
let mut key = user_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&version.as_bytes());
|
||||
|
||||
self.backupid_algorithm.insert(
|
||||
&key,
|
||||
&*serde_json::to_string(backup_metadata)
|
||||
.expect("BackupAlgorithm::to_string always works"),
|
||||
)?;
|
||||
self.backupid_etag
|
||||
.insert(&key, &globals.next_count()?.to_be_bytes())?;
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
pub fn update_backup(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
version: &str,
|
||||
backup_metadata: &BackupAlgorithm,
|
||||
globals: &super::globals::Globals,
|
||||
) -> Result<String> {
|
||||
let mut key = user_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&version.as_bytes());
|
||||
|
||||
if self.backupid_algorithm.get(&key)?.is_none() {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"Tried to update nonexistent backup.",
|
||||
));
|
||||
}
|
||||
|
||||
self.backupid_algorithm.insert(
|
||||
&key,
|
||||
&*serde_json::to_string(backup_metadata)
|
||||
.expect("BackupAlgorithm::to_string always works"),
|
||||
)?;
|
||||
self.backupid_etag
|
||||
.insert(&key, &globals.next_count()?.to_be_bytes())?;
|
||||
Ok(version.to_string())
|
||||
}
|
||||
|
||||
pub fn get_latest_backup(&self, user_id: &UserId) -> Result<Option<(String, BackupAlgorithm)>> {
|
||||
let mut prefix = user_id.to_string().as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
self.backupid_algorithm
|
||||
.scan_prefix(&prefix)
|
||||
.last()
|
||||
.map_or(Ok(None), |r| {
|
||||
let (key, value) = r?;
|
||||
let version = utils::string_from_bytes(
|
||||
key.rsplit(|&b| b == 0xff)
|
||||
.next()
|
||||
.expect("rsplit always returns an element"),
|
||||
)
|
||||
.map_err(|_| Error::bad_database("backupid_algorithm key is invalid."))?;
|
||||
|
||||
Ok(Some((
|
||||
version,
|
||||
serde_json::from_slice(&value).map_err(|_| {
|
||||
Error::bad_database("Algorithm in backupid_algorithm is invalid.")
|
||||
})?,
|
||||
)))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_backup(&self, user_id: &UserId, version: &str) -> Result<Option<BackupAlgorithm>> {
|
||||
let mut key = user_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(version.as_bytes());
|
||||
|
||||
self.backupid_algorithm.get(key)?.map_or(Ok(None), |bytes| {
|
||||
Ok(serde_json::from_slice(&bytes)
|
||||
.map_err(|_| Error::bad_database("Algorithm in backupid_algorithm is invalid."))?)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_key(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
version: &str,
|
||||
room_id: &RoomId,
|
||||
session_id: &str,
|
||||
key_data: &KeyData,
|
||||
globals: &super::globals::Globals,
|
||||
) -> Result<()> {
|
||||
let mut key = user_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(version.as_bytes());
|
||||
|
||||
if self.backupid_algorithm.get(&key)?.is_none() {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::NotFound,
|
||||
"Tried to update nonexistent backup.",
|
||||
));
|
||||
}
|
||||
|
||||
self.backupid_etag
|
||||
.insert(&key, &globals.next_count()?.to_be_bytes())?;
|
||||
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(room_id.to_string().as_bytes());
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(session_id.as_bytes());
|
||||
|
||||
self.backupkeyid_backup.insert(
|
||||
&key,
|
||||
&*serde_json::to_string(&key_data).expect("KeyData::to_string always works"),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn count_keys(&self, user_id: &UserId, version: &str) -> Result<usize> {
|
||||
let mut prefix = user_id.to_string().as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
prefix.extend_from_slice(version.as_bytes());
|
||||
|
||||
Ok(self.backupkeyid_backup.scan_prefix(&prefix).count())
|
||||
}
|
||||
|
||||
pub fn get_etag(&self, user_id: &UserId, version: &str) -> Result<String> {
|
||||
let mut key = user_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&version.as_bytes());
|
||||
|
||||
Ok(utils::u64_from_bytes(
|
||||
&self
|
||||
.backupid_etag
|
||||
.get(&key)?
|
||||
.ok_or_else(|| Error::bad_database("Backup has no etag."))?,
|
||||
)
|
||||
.map_err(|_| Error::bad_database("etag in backupid_etag invalid."))?
|
||||
.to_string())
|
||||
}
|
||||
|
||||
pub fn get_all(&self, user_id: &UserId, version: &str) -> Result<BTreeMap<RoomId, Sessions>> {
|
||||
let mut prefix = user_id.to_string().as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
prefix.extend_from_slice(version.as_bytes());
|
||||
|
||||
let mut rooms = BTreeMap::<RoomId, Sessions>::new();
|
||||
|
||||
for result in self.backupkeyid_backup.scan_prefix(&prefix).map(|r| {
|
||||
let (key, value) = r?;
|
||||
let mut parts = key.rsplit(|&b| b == 0xff);
|
||||
|
||||
let session_id = utils::string_from_bytes(
|
||||
&parts
|
||||
.next()
|
||||
.ok_or_else(|| Error::bad_database("backupkeyid_backup key is invalid."))?,
|
||||
)
|
||||
.map_err(|_| Error::bad_database("backupkeyid_backup session_id is invalid."))?;
|
||||
|
||||
let room_id = RoomId::try_from(
|
||||
utils::string_from_bytes(
|
||||
&parts
|
||||
.next()
|
||||
.ok_or_else(|| Error::bad_database("backupkeyid_backup key is invalid."))?,
|
||||
)
|
||||
.map_err(|_| Error::bad_database("backupkeyid_backup room_id is invalid."))?,
|
||||
)
|
||||
.map_err(|_| Error::bad_database("backupkeyid_backup room_id is invalid room id."))?;
|
||||
|
||||
let key_data = serde_json::from_slice(&value)
|
||||
.map_err(|_| Error::bad_database("KeyData in backupkeyid_backup is invalid."))?;
|
||||
|
||||
Ok::<_, Error>((room_id, session_id, key_data))
|
||||
}) {
|
||||
let (room_id, session_id, key_data) = result?;
|
||||
rooms
|
||||
.entry(room_id)
|
||||
.or_insert_with(|| Sessions {
|
||||
sessions: BTreeMap::new(),
|
||||
})
|
||||
.sessions
|
||||
.insert(session_id, key_data);
|
||||
}
|
||||
|
||||
Ok(rooms)
|
||||
}
|
||||
}
|
|
@ -56,7 +56,10 @@ impl Rooms {
|
|||
}
|
||||
|
||||
/// Returns the full room state.
|
||||
pub fn room_state(&self, room_id: &RoomId) -> Result<HashMap<(EventType, String), PduEvent>> {
|
||||
pub fn room_state_full(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<HashMap<(EventType, String), PduEvent>> {
|
||||
let mut hashmap = HashMap::new();
|
||||
for pdu in self
|
||||
.roomstateid_pdu
|
||||
|
@ -78,6 +81,58 @@ impl Rooms {
|
|||
Ok(hashmap)
|
||||
}
|
||||
|
||||
/// Returns the full room state.
|
||||
pub fn room_state_type(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: &EventType,
|
||||
) -> Result<HashMap<String, PduEvent>> {
|
||||
let mut prefix = room_id.to_string().as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
prefix.extend_from_slice(&event_type.to_string().as_bytes());
|
||||
|
||||
let mut hashmap = HashMap::new();
|
||||
for pdu in self
|
||||
.roomstateid_pdu
|
||||
.scan_prefix(&prefix)
|
||||
.values()
|
||||
.map(|value| {
|
||||
Ok::<_, Error>(
|
||||
serde_json::from_slice::<PduEvent>(&value?)
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in db."))?,
|
||||
)
|
||||
})
|
||||
{
|
||||
let pdu = pdu?;
|
||||
let state_key = pdu.state_key.clone().ok_or_else(|| {
|
||||
Error::bad_database("Room state contains event without state_key.")
|
||||
})?;
|
||||
hashmap.insert(state_key, pdu);
|
||||
}
|
||||
Ok(hashmap)
|
||||
}
|
||||
|
||||
/// Returns the full room state.
|
||||
pub fn room_state_get(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_type: &EventType,
|
||||
state_key: &str,
|
||||
) -> Result<Option<PduEvent>> {
|
||||
let mut key = room_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&event_type.to_string().as_bytes());
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&state_key.as_bytes());
|
||||
|
||||
self.roomstateid_pdu.get(&key)?.map_or(Ok(None), |value| {
|
||||
Ok::<_, Error>(Some(
|
||||
serde_json::from_slice::<PduEvent>(&value)
|
||||
.map_err(|_| Error::bad_database("Invalid PDU in db."))?,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the `count` of this pdu's id.
|
||||
pub fn get_pdu_count(&self, event_id: &EventId) -> Result<Option<u64>> {
|
||||
self.eventid_pduid
|
||||
|
@ -212,8 +267,7 @@ impl Rooms {
|
|||
// Is the event authorized?
|
||||
if let Some(state_key) = &state_key {
|
||||
let power_levels = self
|
||||
.room_state(&room_id)?
|
||||
.get(&(EventType::RoomPowerLevels, "".to_owned()))
|
||||
.room_state_get(&room_id, &EventType::RoomPowerLevels, "")?
|
||||
.map_or_else(
|
||||
|| {
|
||||
Ok::<_, Error>(power_levels::PowerLevelsEventContent {
|
||||
|
@ -244,8 +298,7 @@ impl Rooms {
|
|||
},
|
||||
)?;
|
||||
let sender_membership = self
|
||||
.room_state(&room_id)?
|
||||
.get(&(EventType::RoomMember, sender.to_string()))
|
||||
.room_state_get(&room_id, &EventType::RoomMember, &sender.to_string())?
|
||||
.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| {
|
||||
Ok(
|
||||
serde_json::from_value::<EventJson<member::MemberEventContent>>(
|
||||
|
@ -280,8 +333,11 @@ impl Rooms {
|
|||
})?;
|
||||
|
||||
let current_membership = self
|
||||
.room_state(&room_id)?
|
||||
.get(&(EventType::RoomMember, target_user_id.to_string()))
|
||||
.room_state_get(
|
||||
&room_id,
|
||||
&EventType::RoomMember,
|
||||
&target_user_id.to_string(),
|
||||
)?
|
||||
.map_or(Ok::<_, Error>(member::MembershipState::Leave), |pdu| {
|
||||
Ok(
|
||||
serde_json::from_value::<EventJson<member::MemberEventContent>>(
|
||||
|
@ -315,8 +371,7 @@ impl Rooms {
|
|||
);
|
||||
|
||||
let join_rules =
|
||||
self.room_state(&room_id)?
|
||||
.get(&(EventType::RoomJoinRules, "".to_owned()))
|
||||
self.room_state_get(&room_id, &EventType::RoomJoinRules, "")?
|
||||
.map_or(Ok::<_, Error>(join_rules::JoinRule::Public), |pdu| {
|
||||
Ok(serde_json::from_value::<
|
||||
EventJson<join_rules::JoinRulesEventContent>,
|
||||
|
@ -446,13 +501,13 @@ impl Rooms {
|
|||
+ 1;
|
||||
|
||||
let mut unsigned = unsigned.unwrap_or_default();
|
||||
// TODO: Optimize this to not load the whole room state?
|
||||
if let Some(state_key) = &state_key {
|
||||
if let Some(prev_pdu) = self
|
||||
.room_state(&room_id)?
|
||||
.get(&(event_type.clone(), state_key.to_owned()))
|
||||
{
|
||||
if let Some(prev_pdu) = self.room_state_get(&room_id, &event_type, &state_key)? {
|
||||
unsigned.insert("prev_content".to_owned(), prev_pdu.content.clone());
|
||||
unsigned.insert(
|
||||
"prev_sender".to_owned(),
|
||||
serde_json::to_value(prev_pdu.sender).expect("UserId::to_value always works"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -551,13 +606,18 @@ impl Rooms {
|
|||
}
|
||||
|
||||
/// Returns an iterator over all PDUs in a room.
|
||||
pub fn all_pdus(&self, room_id: &RoomId) -> Result<impl Iterator<Item = Result<PduEvent>>> {
|
||||
self.pdus_since(room_id, 0)
|
||||
pub fn all_pdus(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
) -> Result<impl Iterator<Item = Result<PduEvent>>> {
|
||||
self.pdus_since(user_id, room_id, 0)
|
||||
}
|
||||
|
||||
/// Returns an iterator over all events in a room that happened after the event with id `since`.
|
||||
pub fn pdus_since(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
since: u64,
|
||||
) -> Result<impl Iterator<Item = Result<PduEvent>>> {
|
||||
|
@ -566,12 +626,13 @@ impl Rooms {
|
|||
pdu_id.push(0xff);
|
||||
pdu_id.extend_from_slice(&(since).to_be_bytes());
|
||||
|
||||
self.pdus_since_pduid(room_id, &pdu_id)
|
||||
self.pdus_since_pduid(user_id, room_id, &pdu_id)
|
||||
}
|
||||
|
||||
/// Returns an iterator over all events in a room that happened after the event with id `since`.
|
||||
pub fn pdus_since_pduid(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
pdu_id: &[u8],
|
||||
) -> Result<impl Iterator<Item = Result<PduEvent>>> {
|
||||
|
@ -579,6 +640,7 @@ impl Rooms {
|
|||
let mut prefix = room_id.to_string().as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
let user_id = user_id.clone();
|
||||
Ok(self
|
||||
.pduid_pdu
|
||||
.range(pdu_id..)
|
||||
|
@ -590,9 +652,13 @@ impl Rooms {
|
|||
})
|
||||
.filter_map(|r| r.ok())
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||
.map(|(_, v)| {
|
||||
Ok(serde_json::from_slice(&v)
|
||||
.map_err(|_| Error::bad_database("PDU in db is invalid."))?)
|
||||
.map(move |(_, v)| {
|
||||
let mut pdu = serde_json::from_slice::<PduEvent>(&v)
|
||||
.map_err(|_| Error::bad_database("PDU in db is invalid."))?;
|
||||
if pdu.sender != user_id {
|
||||
pdu.unsigned.remove("transaction_id");
|
||||
}
|
||||
Ok(pdu)
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -600,6 +666,7 @@ impl Rooms {
|
|||
/// `until` in reverse-chronological order.
|
||||
pub fn pdus_until(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
until: u64,
|
||||
) -> impl Iterator<Item = Result<PduEvent>> {
|
||||
|
@ -612,14 +679,19 @@ impl Rooms {
|
|||
|
||||
let current: &[u8] = ¤t;
|
||||
|
||||
let user_id = user_id.clone();
|
||||
self.pduid_pdu
|
||||
.range(..current)
|
||||
.rev()
|
||||
.filter_map(|r| r.ok())
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||
.map(|(_, v)| {
|
||||
Ok(serde_json::from_slice(&v)
|
||||
.map_err(|_| Error::bad_database("PDU in db is invalid."))?)
|
||||
.map(move |(_, v)| {
|
||||
let mut pdu = serde_json::from_slice::<PduEvent>(&v)
|
||||
.map_err(|_| Error::bad_database("PDU in db is invalid."))?;
|
||||
if pdu.sender != user_id {
|
||||
pdu.unsigned.remove("transaction_id");
|
||||
}
|
||||
Ok(pdu)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -627,6 +699,7 @@ impl Rooms {
|
|||
/// `from` in chronological order.
|
||||
pub fn pdus_after(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
from: u64,
|
||||
) -> impl Iterator<Item = Result<PduEvent>> {
|
||||
|
@ -639,13 +712,18 @@ impl Rooms {
|
|||
|
||||
let current: &[u8] = ¤t;
|
||||
|
||||
let user_id = user_id.clone();
|
||||
self.pduid_pdu
|
||||
.range(current..)
|
||||
.filter_map(|r| r.ok())
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||
.map(|(_, v)| {
|
||||
Ok(serde_json::from_slice(&v)
|
||||
.map_err(|_| Error::bad_database("PDU in db is invalid."))?)
|
||||
.map(move |(_, v)| {
|
||||
let mut pdu = serde_json::from_slice::<PduEvent>(&v)
|
||||
.map_err(|_| Error::bad_database("PDU in db is invalid."))?;
|
||||
if pdu.sender != user_id {
|
||||
pdu.unsigned.remove("transaction_id");
|
||||
}
|
||||
Ok(pdu)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
use crate::{utils, Error, Result};
|
||||
use js_int::UInt;
|
||||
use ruma::{
|
||||
api::client::r0::{
|
||||
api::client::{
|
||||
error::ErrorKind,
|
||||
r0::{
|
||||
device::Device,
|
||||
keys::{AlgorithmAndDeviceId, DeviceKeys, KeyAlgorithm, OneTimeKey},
|
||||
keys::{AlgorithmAndDeviceId, CrossSigningKey, DeviceKeys, KeyAlgorithm, OneTimeKey},
|
||||
},
|
||||
},
|
||||
events::{to_device::AnyToDeviceEvent, EventJson, EventType},
|
||||
identifiers::UserId,
|
||||
|
@ -19,8 +22,11 @@ pub struct Users {
|
|||
pub(super) token_userdeviceid: sled::Tree,
|
||||
|
||||
pub(super) onetimekeyid_onetimekeys: sled::Tree, // OneTimeKeyId = UserId + AlgorithmAndDeviceId
|
||||
pub(super) userdeviceid_devicekeys: sled::Tree,
|
||||
pub(super) devicekeychangeid_userid: sled::Tree, // DeviceKeyChangeId = Count
|
||||
pub(super) keychangeid_userid: sled::Tree, // KeyChangeId = Count
|
||||
pub(super) keyid_key: sled::Tree, // KeyId = UserId + KeyId (depends on key type)
|
||||
pub(super) userid_masterkeyid: sled::Tree,
|
||||
pub(super) userid_selfsigningkeyid: sled::Tree,
|
||||
pub(super) userid_usersigningkeyid: sled::Tree,
|
||||
|
||||
pub(super) todeviceid_events: sled::Tree, // ToDeviceId = UserId + DeviceId + Count
|
||||
}
|
||||
|
@ -171,9 +177,6 @@ impl Users {
|
|||
userdeviceid.push(0xff);
|
||||
userdeviceid.extend_from_slice(device_id.as_bytes());
|
||||
|
||||
// Remove device keys
|
||||
self.userdeviceid_devicekeys.remove(&userdeviceid)?;
|
||||
|
||||
// Remove tokens
|
||||
if let Some(old_token) = self.userdeviceid_token.remove(&userdeviceid)? {
|
||||
self.token_userdeviceid.remove(&old_token)?;
|
||||
|
@ -350,38 +353,163 @@ impl Users {
|
|||
userdeviceid.push(0xff);
|
||||
userdeviceid.extend_from_slice(device_id.as_bytes());
|
||||
|
||||
self.userdeviceid_devicekeys.insert(
|
||||
self.keyid_key.insert(
|
||||
&userdeviceid,
|
||||
&*serde_json::to_string(&device_keys).expect("DeviceKeys::to_string always works"),
|
||||
)?;
|
||||
|
||||
self.devicekeychangeid_userid
|
||||
self.keychangeid_userid
|
||||
.insert(globals.next_count()?.to_be_bytes(), &*user_id.to_string())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_device_keys(
|
||||
pub fn add_cross_signing_keys(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
device_id: &str,
|
||||
) -> impl Iterator<Item = Result<DeviceKeys>> {
|
||||
let mut key = user_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(device_id.as_bytes());
|
||||
master_key: &CrossSigningKey,
|
||||
self_signing_key: &Option<CrossSigningKey>,
|
||||
user_signing_key: &Option<CrossSigningKey>,
|
||||
globals: &super::globals::Globals,
|
||||
) -> Result<()> {
|
||||
// TODO: Check signatures
|
||||
|
||||
self.userdeviceid_devicekeys
|
||||
.scan_prefix(key)
|
||||
.values()
|
||||
.map(|bytes| {
|
||||
Ok(serde_json::from_slice(&bytes?)
|
||||
.map_err(|_| Error::bad_database("DeviceKeys in db are invalid."))?)
|
||||
})
|
||||
let mut prefix = user_id.to_string().as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
// Master key
|
||||
let mut master_key_ids = master_key.keys.values();
|
||||
let master_key_id = master_key_ids.next().ok_or(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Master key contained no key.",
|
||||
))?;
|
||||
|
||||
if master_key_ids.next().is_some() {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Master key contained more than one key.",
|
||||
));
|
||||
}
|
||||
|
||||
pub fn device_keys_changed(&self, since: u64) -> impl Iterator<Item = Result<UserId>> {
|
||||
self.devicekeychangeid_userid
|
||||
.range(since.to_be_bytes()..)
|
||||
let mut master_key_key = prefix.clone();
|
||||
master_key_key.extend_from_slice(master_key_id.as_bytes());
|
||||
|
||||
self.keyid_key.insert(
|
||||
&master_key_key,
|
||||
&*serde_json::to_string(&master_key).expect("CrossSigningKey::to_string always works"),
|
||||
)?;
|
||||
|
||||
self.userid_masterkeyid
|
||||
.insert(&*user_id.to_string(), master_key_key)?;
|
||||
|
||||
// Self-signing key
|
||||
if let Some(self_signing_key) = self_signing_key {
|
||||
let mut self_signing_key_ids = self_signing_key.keys.values();
|
||||
let self_signing_key_id = self_signing_key_ids.next().ok_or(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Self signing key contained no key.",
|
||||
))?;
|
||||
|
||||
if self_signing_key_ids.next().is_some() {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Self signing key contained more than one key.",
|
||||
));
|
||||
}
|
||||
|
||||
let mut self_signing_key_key = prefix.clone();
|
||||
self_signing_key_key.extend_from_slice(self_signing_key_id.as_bytes());
|
||||
|
||||
self.keyid_key.insert(
|
||||
&self_signing_key_key,
|
||||
&*serde_json::to_string(&self_signing_key)
|
||||
.expect("CrossSigningKey::to_string always works"),
|
||||
)?;
|
||||
|
||||
self.userid_selfsigningkeyid
|
||||
.insert(&*user_id.to_string(), self_signing_key_key)?;
|
||||
}
|
||||
|
||||
// User-signing key
|
||||
if let Some(user_signing_key) = user_signing_key {
|
||||
let mut user_signing_key_ids = user_signing_key.keys.values();
|
||||
let user_signing_key_id = user_signing_key_ids.next().ok_or(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"User signing key contained no key.",
|
||||
))?;
|
||||
|
||||
if user_signing_key_ids.next().is_some() {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"User signing key contained more than one key.",
|
||||
));
|
||||
}
|
||||
|
||||
let mut user_signing_key_key = prefix.clone();
|
||||
user_signing_key_key.extend_from_slice(user_signing_key_id.as_bytes());
|
||||
|
||||
self.keyid_key.insert(
|
||||
&user_signing_key_key,
|
||||
&*serde_json::to_string(&user_signing_key)
|
||||
.expect("CrossSigningKey::to_string always works"),
|
||||
)?;
|
||||
|
||||
self.userid_usersigningkeyid
|
||||
.insert(&*user_id.to_string(), user_signing_key_key)?;
|
||||
}
|
||||
|
||||
self.keychangeid_userid
|
||||
.insert(globals.next_count()?.to_be_bytes(), &*user_id.to_string())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn sign_key(
|
||||
&self,
|
||||
target_id: &UserId,
|
||||
key_id: &str,
|
||||
signature: (String, String),
|
||||
sender_id: &UserId,
|
||||
globals: &super::globals::Globals,
|
||||
) -> Result<()> {
|
||||
let mut key = target_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(key_id.to_string().as_bytes());
|
||||
|
||||
let mut cross_signing_key =
|
||||
serde_json::from_slice::<serde_json::Value>(&self.keyid_key.get(&key)?.ok_or(
|
||||
Error::BadRequest(ErrorKind::InvalidParam, "Tried to sign nonexistent key."),
|
||||
)?)
|
||||
.map_err(|_| Error::bad_database("key in keyid_key is invalid."))?;
|
||||
|
||||
let signatures = cross_signing_key
|
||||
.get_mut("signatures")
|
||||
.ok_or_else(|| Error::bad_database("key in keyid_key has no signatures field."))?
|
||||
.as_object_mut()
|
||||
.ok_or_else(|| Error::bad_database("key in keyid_key has invalid signatures field."))?
|
||||
.entry(sender_id.clone())
|
||||
.or_insert_with(|| serde_json::Map::new().into());
|
||||
|
||||
signatures
|
||||
.as_object_mut()
|
||||
.ok_or_else(|| Error::bad_database("signatures in keyid_key for a user is invalid."))?
|
||||
.insert(signature.0, signature.1.into());
|
||||
|
||||
self.keyid_key.insert(
|
||||
&key,
|
||||
&*serde_json::to_string(&cross_signing_key)
|
||||
.expect("CrossSigningKey::to_string always works"),
|
||||
)?;
|
||||
|
||||
self.keychangeid_userid
|
||||
.insert(globals.next_count()?.to_be_bytes(), &*target_id.to_string())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn keys_changed(&self, since: u64) -> impl Iterator<Item = Result<UserId>> {
|
||||
self.keychangeid_userid
|
||||
.range((since + 1).to_be_bytes()..)
|
||||
.values()
|
||||
.map(|bytes| {
|
||||
Ok(
|
||||
|
@ -397,26 +525,82 @@ impl Users {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn all_device_keys(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
) -> impl Iterator<Item = Result<(String, DeviceKeys)>> {
|
||||
pub fn get_device_keys(&self, user_id: &UserId, device_id: &str) -> Result<Option<DeviceKeys>> {
|
||||
let mut key = user_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(device_id.as_bytes());
|
||||
|
||||
self.userdeviceid_devicekeys.scan_prefix(key).map(|r| {
|
||||
let (key, value) = r?;
|
||||
let userdeviceid = utils::string_from_bytes(
|
||||
key.rsplit(|&b| b == 0xff)
|
||||
.next()
|
||||
.ok_or_else(|| Error::bad_database("UserDeviceID in db is invalid."))?,
|
||||
)
|
||||
.map_err(|_| Error::bad_database("UserDeviceId in db is invalid."))?;
|
||||
Ok((
|
||||
userdeviceid,
|
||||
serde_json::from_slice(&*value)
|
||||
.map_err(|_| Error::bad_database("DeviceKeys in db are invalid."))?,
|
||||
))
|
||||
self.keyid_key.get(key)?.map_or(Ok(None), |bytes| {
|
||||
Ok(Some(serde_json::from_slice(&bytes).map_err(|_| {
|
||||
Error::bad_database("DeviceKeys in db are invalid.")
|
||||
})?))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_master_key(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
sender_id: &UserId,
|
||||
) -> Result<Option<CrossSigningKey>> {
|
||||
// TODO: hide some signatures
|
||||
self.userid_masterkeyid
|
||||
.get(user_id.to_string())?
|
||||
.map_or(Ok(None), |key| {
|
||||
self.keyid_key.get(key)?.map_or(Ok(None), |bytes| {
|
||||
let mut cross_signing_key = serde_json::from_slice::<CrossSigningKey>(&bytes)
|
||||
.map_err(|_| {
|
||||
Error::bad_database("CrossSigningKey in db is invalid.")
|
||||
})?;
|
||||
|
||||
// A user is not allowed to see signatures from users other than himself and
|
||||
// the target user
|
||||
cross_signing_key.signatures = cross_signing_key
|
||||
.signatures
|
||||
.into_iter()
|
||||
.filter(|(user, _)| user == user_id || user == sender_id)
|
||||
.collect();
|
||||
|
||||
Ok(Some(cross_signing_key))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_self_signing_key(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
sender_id: &UserId,
|
||||
) -> Result<Option<CrossSigningKey>> {
|
||||
self.userid_selfsigningkeyid
|
||||
.get(user_id.to_string())?
|
||||
.map_or(Ok(None), |key| {
|
||||
self.keyid_key.get(key)?.map_or(Ok(None), |bytes| {
|
||||
let mut cross_signing_key = serde_json::from_slice::<CrossSigningKey>(&bytes)
|
||||
.map_err(|_| {
|
||||
Error::bad_database("CrossSigningKey in db is invalid.")
|
||||
})?;
|
||||
|
||||
// A user is not allowed to see signatures from users other than himself and
|
||||
// the target user
|
||||
cross_signing_key.signatures = cross_signing_key
|
||||
.signatures
|
||||
.into_iter()
|
||||
.filter(|(user, _)| user == user_id || user == sender_id)
|
||||
.collect();
|
||||
|
||||
Ok(Some(cross_signing_key))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_user_signing_key(&self, user_id: &UserId) -> Result<Option<CrossSigningKey>> {
|
||||
self.userid_usersigningkeyid
|
||||
.get(user_id.to_string())?
|
||||
.map_or(Ok(None), |key| {
|
||||
self.keyid_key.get(key)?.map_or(Ok(None), |bytes| {
|
||||
Ok(Some(serde_json::from_slice(&bytes).map_err(|_| {
|
||||
Error::bad_database("CrossSigningKey in db is invalid.")
|
||||
})?))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
12
src/main.rs
12
src/main.rs
|
@ -1,6 +1,8 @@
|
|||
#![feature(proc_macro_hygiene, decl_macro)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
pub mod push_rules;
|
||||
|
||||
mod client_server;
|
||||
mod database;
|
||||
mod error;
|
||||
|
@ -44,6 +46,12 @@ fn setup_rocket() -> rocket::Rocket {
|
|||
client_server::upload_keys_route,
|
||||
client_server::get_keys_route,
|
||||
client_server::claim_keys_route,
|
||||
client_server::create_backup_route,
|
||||
client_server::update_backup_route,
|
||||
client_server::get_latest_backup_route,
|
||||
client_server::get_backup_route,
|
||||
client_server::add_backup_keys_route,
|
||||
client_server::get_backup_keys_route,
|
||||
client_server::set_read_marker_route,
|
||||
client_server::create_typing_event_route,
|
||||
client_server::create_room_route,
|
||||
|
@ -88,6 +96,10 @@ fn setup_rocket() -> rocket::Rocket {
|
|||
client_server::delete_device_route,
|
||||
client_server::delete_devices_route,
|
||||
client_server::options_route,
|
||||
client_server::upload_signing_keys_route,
|
||||
client_server::upload_signatures_route,
|
||||
client_server::pushers_route,
|
||||
client_server::set_pushers_route,
|
||||
//server_server::well_known_server,
|
||||
//server_server::get_server_version,
|
||||
//server_server::get_server_keys,
|
||||
|
|
|
@ -4,6 +4,7 @@ use ruma::{
|
|||
api::federation::EventHash,
|
||||
events::{
|
||||
collections::all::{RoomEvent, StateEvent},
|
||||
room::member::MemberEvent,
|
||||
stripped::AnyStrippedStateEvent,
|
||||
EventJson, EventType,
|
||||
},
|
||||
|
@ -95,4 +96,9 @@ impl PduEvent {
|
|||
serde_json::from_str::<EventJson<AnyStrippedStateEvent>>(&json)
|
||||
.expect("EventJson::from_str always works")
|
||||
}
|
||||
pub fn to_member_event(&self) -> EventJson<MemberEvent> {
|
||||
let json = serde_json::to_string(&self).expect("PDUs are always valid");
|
||||
serde_json::from_str::<EventJson<MemberEvent>>(&json)
|
||||
.expect("EventJson::from_str always works")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,230 @@
|
|||
use ruma::{
|
||||
events::push_rules::{ConditionalPushRule, PatternedPushRule, PushCondition, Ruleset},
|
||||
identifiers::UserId,
|
||||
push::{Action, Tweak},
|
||||
};
|
||||
|
||||
pub fn default_pushrules(user_id: &UserId) -> Ruleset {
|
||||
Ruleset {
|
||||
content: vec![contains_user_name_rule(&user_id)],
|
||||
override_: vec![
|
||||
master_rule(),
|
||||
suppress_notices_rule(),
|
||||
invite_for_me_rule(),
|
||||
member_event_rule(),
|
||||
contains_display_name_rule(),
|
||||
tombstone_rule(),
|
||||
roomnotif_rule(),
|
||||
],
|
||||
room: vec![],
|
||||
sender: vec![],
|
||||
underride: vec![
|
||||
call_rule(),
|
||||
encrypted_room_one_to_one_rule(),
|
||||
room_one_to_one_rule(),
|
||||
message_rule(),
|
||||
encrypted_rule(),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn master_rule() -> ConditionalPushRule {
|
||||
ConditionalPushRule {
|
||||
actions: vec![Action::DontNotify],
|
||||
default: true,
|
||||
enabled: false,
|
||||
rule_id: ".m.rule.master".to_owned(),
|
||||
conditions: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn suppress_notices_rule() -> ConditionalPushRule {
|
||||
ConditionalPushRule {
|
||||
actions: vec![Action::DontNotify],
|
||||
default: true,
|
||||
enabled: true,
|
||||
rule_id: ".m.rule.suppress_notices".to_owned(),
|
||||
conditions: vec![PushCondition::EventMatch {
|
||||
key: "content.msgtype".to_owned(),
|
||||
pattern: "m.notice".to_owned(),
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invite_for_me_rule() -> ConditionalPushRule {
|
||||
ConditionalPushRule {
|
||||
actions: vec![
|
||||
Action::Notify,
|
||||
Action::SetTweak(Tweak::Sound("default".to_owned())),
|
||||
Action::SetTweak(Tweak::Highlight(false)),
|
||||
],
|
||||
default: true,
|
||||
enabled: true,
|
||||
rule_id: ".m.rule.invite_for_me".to_owned(),
|
||||
conditions: vec![PushCondition::EventMatch {
|
||||
key: "content.membership".to_owned(),
|
||||
pattern: "m.invite".to_owned(),
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn member_event_rule() -> ConditionalPushRule {
|
||||
ConditionalPushRule {
|
||||
actions: vec![Action::DontNotify],
|
||||
default: true,
|
||||
enabled: true,
|
||||
rule_id: ".m.rule.member_event".to_owned(),
|
||||
conditions: vec![PushCondition::EventMatch {
|
||||
key: "content.membership".to_owned(),
|
||||
pattern: "type".to_owned(),
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains_display_name_rule() -> ConditionalPushRule {
|
||||
ConditionalPushRule {
|
||||
actions: vec![
|
||||
Action::Notify,
|
||||
Action::SetTweak(Tweak::Sound("default".to_owned())),
|
||||
Action::SetTweak(Tweak::Highlight(true)),
|
||||
],
|
||||
default: true,
|
||||
enabled: true,
|
||||
rule_id: ".m.rule.contains_display_name".to_owned(),
|
||||
conditions: vec![PushCondition::ContainsDisplayName],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tombstone_rule() -> ConditionalPushRule {
|
||||
ConditionalPushRule {
|
||||
actions: vec![Action::Notify, Action::SetTweak(Tweak::Highlight(true))],
|
||||
default: true,
|
||||
enabled: true,
|
||||
rule_id: ".m.rule.tombstone".to_owned(),
|
||||
conditions: vec![
|
||||
PushCondition::EventMatch {
|
||||
key: "type".to_owned(),
|
||||
pattern: "m.room.tombstone".to_owned(),
|
||||
},
|
||||
PushCondition::EventMatch {
|
||||
key: "state_key".to_owned(),
|
||||
pattern: "".to_owned(),
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn roomnotif_rule() -> ConditionalPushRule {
|
||||
ConditionalPushRule {
|
||||
actions: vec![Action::Notify, Action::SetTweak(Tweak::Highlight(true))],
|
||||
default: true,
|
||||
enabled: true,
|
||||
rule_id: ".m.rule.roomnotif".to_owned(),
|
||||
conditions: vec![
|
||||
PushCondition::EventMatch {
|
||||
key: "content.body".to_owned(),
|
||||
pattern: "@room".to_owned(),
|
||||
},
|
||||
PushCondition::SenderNotificationPermission {
|
||||
key: "room".to_owned(),
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains_user_name_rule(user_id: &UserId) -> PatternedPushRule {
|
||||
PatternedPushRule {
|
||||
actions: vec![
|
||||
Action::Notify,
|
||||
Action::SetTweak(Tweak::Sound("default".to_owned())),
|
||||
Action::SetTweak(Tweak::Highlight(true)),
|
||||
],
|
||||
default: true,
|
||||
enabled: true,
|
||||
rule_id: ".m.rule.contains_user_name".to_owned(),
|
||||
pattern: user_id.localpart().to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call_rule() -> ConditionalPushRule {
|
||||
ConditionalPushRule {
|
||||
actions: vec![
|
||||
Action::Notify,
|
||||
Action::SetTweak(Tweak::Sound("ring".to_owned())),
|
||||
Action::SetTweak(Tweak::Highlight(false)),
|
||||
],
|
||||
default: true,
|
||||
enabled: true,
|
||||
rule_id: ".m.rule.call".to_owned(),
|
||||
conditions: vec![PushCondition::EventMatch {
|
||||
key: "type".to_owned(),
|
||||
pattern: "m.call.invite".to_owned(),
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encrypted_room_one_to_one_rule() -> ConditionalPushRule {
|
||||
ConditionalPushRule {
|
||||
actions: vec![
|
||||
Action::Notify,
|
||||
Action::SetTweak(Tweak::Sound("default".to_owned())),
|
||||
Action::SetTweak(Tweak::Highlight(false)),
|
||||
],
|
||||
default: true,
|
||||
enabled: true,
|
||||
rule_id: ".m.rule.encrypted_room_one_to_one".to_owned(),
|
||||
conditions: vec![
|
||||
PushCondition::RoomMemberCount { is: "2".to_owned() },
|
||||
PushCondition::EventMatch {
|
||||
key: "type".to_owned(),
|
||||
pattern: "m.room.encrypted".to_owned(),
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn room_one_to_one_rule() -> ConditionalPushRule {
|
||||
ConditionalPushRule {
|
||||
actions: vec![
|
||||
Action::Notify,
|
||||
Action::SetTweak(Tweak::Sound("default".to_owned())),
|
||||
Action::SetTweak(Tweak::Highlight(false)),
|
||||
],
|
||||
default: true,
|
||||
enabled: true,
|
||||
rule_id: ".m.rule.room_one_to_one".to_owned(),
|
||||
conditions: vec![
|
||||
PushCondition::RoomMemberCount { is: "2".to_owned() },
|
||||
PushCondition::EventMatch {
|
||||
key: "type".to_owned(),
|
||||
pattern: "m.room.message".to_owned(),
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn message_rule() -> ConditionalPushRule {
|
||||
ConditionalPushRule {
|
||||
actions: vec![Action::Notify, Action::SetTweak(Tweak::Highlight(false))],
|
||||
default: true,
|
||||
enabled: true,
|
||||
rule_id: ".m.rule.message".to_owned(),
|
||||
conditions: vec![PushCondition::EventMatch {
|
||||
key: "type".to_owned(),
|
||||
pattern: "m.room.message".to_owned(),
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encrypted_rule() -> ConditionalPushRule {
|
||||
ConditionalPushRule {
|
||||
actions: vec![Action::Notify, Action::SetTweak(Tweak::Highlight(false))],
|
||||
default: true,
|
||||
enabled: true,
|
||||
rule_id: ".m.rule.encrypted".to_owned(),
|
||||
conditions: vec![PushCondition::EventMatch {
|
||||
key: "type".to_owned(),
|
||||
pattern: "m.room.encrypted".to_owned(),
|
||||
}],
|
||||
}
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
# Register endpoints implemented
|
||||
GET /register yields a set of flows
|
||||
POST /register can create a user
|
||||
POST /register downcases capitals in usernames
|
||||
|
@ -17,14 +16,12 @@ POST /register rejects registration of usernames with '£'
|
|||
POST /register rejects registration of usernames with 'é'
|
||||
POST /register rejects registration of usernames with '\n'
|
||||
POST /register rejects registration of usernames with '''
|
||||
# Login endpoints implemented
|
||||
GET /login yields a set of flows
|
||||
POST /login can log in as a user
|
||||
POST /login returns the same device_id as that in the request
|
||||
POST /login can log in as a user with just the local part of the id
|
||||
POST /login as non-existing user is rejected
|
||||
POST /login wrong password is rejected
|
||||
# Room creation endpoints implemented
|
||||
POST /createRoom makes a private room
|
||||
POST /createRoom makes a private room with invites
|
||||
GET /rooms/:room_id/state/m.room.member/:user_id fetches my membership
|
||||
|
@ -49,15 +46,6 @@ Can read configuration endpoint
|
|||
AS cannot create users outside its own namespace
|
||||
Changing the actions of an unknown default rule fails with 404
|
||||
Changing the actions of an unknown rule fails with 404
|
||||
Trying to add push rule with invalid scope fails with 400
|
||||
Trying to add push rule with invalid template fails with 400
|
||||
Trying to add push rule with rule_id with slashes fails with 400
|
||||
Trying to add push rule with override rule without conditions fails with 400
|
||||
Trying to add push rule with underride rule without conditions fails with 400
|
||||
Trying to add push rule with condition without kind fails with 400
|
||||
Trying to add push rule with content rule without pattern fails with 400
|
||||
Trying to add push rule with no actions fails with 400
|
||||
Trying to add push rule with invalid action fails with 400
|
||||
Trying to get push rules with unknown rule_id fails with 404
|
||||
GET /events with non-numeric 'limit'
|
||||
GET /events with negative 'limit'
|
||||
|
|
Loading…
Reference in New Issue