diff --git a/.github/codeowners b/.github/codeowners new file mode 100644 index 00000000..8b922a10 --- /dev/null +++ b/.github/codeowners @@ -0,0 +1,2 @@ +* @geekbrother +* @chris13524 diff --git a/.github/workflows/sub-app-check.yml b/.github/workflows/sub-app-check.yml index a2a4ee2c..031cd1f1 100644 --- a/.github/workflows/sub-app-check.yml +++ b/.github/workflows/sub-app-check.yml @@ -38,7 +38,7 @@ jobs: uses: WalletConnect/actions-rs/cargo@1.0.0 with: command: clippy - args: -- -D warnings + args: --all-features --tests -- -D warnings formatting: name: Formatting @@ -68,6 +68,26 @@ jobs: name: Tests runs-on: group: ${{ vars.RUN_GROUP }} + services: + mongodb: + image: mongo + env: + MONGO_INITDB_ROOT_USERNAME: admin + MONGO_INITDB_ROOT_PASSWORD: admin + MONGO_INITDB_DATABASE: notify + ports: + - 27017:27017 + postgres: + image: postgres:16 + env: + POSTGRES_HOST_AUTH_METHOD: trust + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 steps: - name: Checkout uses: actions/checkout@v3 @@ -92,3 +112,12 @@ jobs: with: command: test args: --all-features --lib --bins + + - name: "Storage Tests" + uses: WalletConnect/actions-rs/cargo@1.0.0 + with: + command: test + args: --test storage -- --test-threads=1 + env: + DATABASE_URL: mongodb://admin:admin@localhost:27017/notify?authSource=admin + POSTGRES_URL: postgres://postgres:password@localhost:5432/postgres diff --git a/.github/workflows/sub-validate.yml b/.github/workflows/sub-validate.yml index 4a655605..fea87c93 100644 --- a/.github/workflows/sub-validate.yml +++ b/.github/workflows/sub-validate.yml @@ -33,22 +33,6 @@ jobs: environment: name: ${{ inputs.stage }} url: ${{ inputs.stage-url }} - services: - mongodb: - image: mongo - ports: - - 27017:27017 - postgres: - image: postgres:16 - env: - POSTGRES_HOST_AUTH_METHOD: trust - ports: - - 5432:5432 - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 steps: - uses: actions/checkout@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index e654272b..f38421fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -585,4 +585,4 @@ All notable changes to this project will be documented in this file. See [conven - triggering action - (0b555c9) - Rakowskiii - - - -Changelog generated by [cocogitto](https://github.com/cocogitto/cocogitto). \ No newline at end of file +Changelog generated by [cocogitto](https://github.com/cocogitto/cocogitto). diff --git a/Cargo.lock b/Cargo.lock index 781f2943..a805f3cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,6 +27,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom 0.2.9", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.3" @@ -35,16 +46,16 @@ checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ "cfg-if", "const-random", - "getrandom 0.2.10", + "getrandom 0.2.9", "once_cell", "version_check", ] [[package]] name = "aho-corasick" -version = "1.1.1" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" dependencies = [ "memchr", ] @@ -64,6 +75,12 @@ dependencies = [ "tokio", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "analytics" version = "0.1.0" @@ -82,12 +99,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -99,19 +110,19 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.15", ] [[package]] @@ -130,6 +141,15 @@ dependencies = [ "tungstenite", ] +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "atty" version = "0.2.14" @@ -165,12 +185,12 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes", - "fastrand", + "fastrand 2.0.1", "hex", "http 0.2.9", "hyper", "ring", - "time", + "time 0.3.20", "tokio", "tower", "tracing", @@ -185,7 +205,7 @@ checksum = "70a66ac8ef5fa9cf01c2d999f39d16812e90ec1467bd382cbbb74ba23ea86201" dependencies = [ "aws-smithy-async", "aws-smithy-types", - "fastrand", + "fastrand 2.0.1", "tokio", "tracing", "zeroize", @@ -225,7 +245,7 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "fastrand", + "fastrand 2.0.1", "http 0.2.9", "percent-encoding", "tracing", @@ -328,8 +348,8 @@ dependencies = [ "once_cell", "percent-encoding", "regex", - "sha2 0.10.8", - "time", + "sha2 0.10.6", + "time 0.3.20", "tracing", ] @@ -362,7 +382,7 @@ dependencies = [ "md-5", "pin-project-lite", "sha1", - "sha2 0.10.8", + "sha2 0.10.6", "tracing", ] @@ -377,14 +397,14 @@ dependencies = [ "aws-smithy-http-tower", "aws-smithy-types", "bytes", - "fastrand", + "fastrand 2.0.1", "http 0.2.9", "http-body", "hyper", "hyper-rustls", "lazy_static", "pin-project-lite", - "rustls", + "rustls 0.21.7", "tokio", "tower", "tracing", @@ -471,7 +491,7 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "bytes", - "fastrand", + "fastrand 2.0.1", "http 0.2.9", "http-body", "once_cell", @@ -507,7 +527,7 @@ dependencies = [ "num-integer", "ryu", "serde", - "time", + "time 0.3.20", ] [[package]] @@ -537,9 +557,9 @@ dependencies = [ [[package]] name = "axum" -version = "0.6.20" +version = "0.6.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +checksum = "113713495a32dd0ab52baf5c10044725aa3aec00b31beda84218e469029b72a3" dependencies = [ "async-trait", "axum-core", @@ -625,9 +645,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.4" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" [[package]] name = "base64-simd" @@ -665,6 +685,9 @@ name = "bitflags" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +dependencies = [ + "serde", +] [[package]] name = "bitvec" @@ -704,49 +727,51 @@ checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" [[package]] name = "bson" -version = "2.7.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58da0ae1e701ea752cc46c1bb9f39d5ecefc7395c3ecd526261a566d4f16e0c2" +checksum = "9aeb8bae494e49dbc330dd23cf78f6f7accee22f640ce3ab17841badaa4ce232" dependencies = [ - "ahash", + "ahash 0.7.6", "base64 0.13.1", "bitvec", "hex", "indexmap 1.9.3", "js-sys", - "once_cell", + "lazy_static", "rand 0.8.5", "serde", "serde_bytes", "serde_json", - "time", + "time 0.3.20", "uuid", ] [[package]] name = "build-info" -version = "0.0.33" +version = "0.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1add148a02352a8149c1ae42528988427aeb0438f808df66f393cb9948006ec" +checksum = "36484c58823abd59bb12a8873fff765f0637723e3c4fbd9c523a9035995e95ce" dependencies = [ "build-info-common", "build-info-proc", + "once_cell", ] [[package]] name = "build-info-build" -version = "0.0.33" +version = "0.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9cddd0655ecb60d7a500f30d3ae4e69b798026d76c3d16b2f464a72913066e" +checksum = "a81141b1af6bc87316a3fdcc3b33658f7a9153c7677da6b0e57d762c87f8ee61" dependencies = [ "anyhow", - "base64 0.21.4", + "base64 0.21.0", "bincode", "build-info-common", "cargo_metadata", "chrono", "git2", "glob", + "once_cell", "pretty_assertions", "rustc_version 0.4.0", "serde_json", @@ -755,24 +780,24 @@ dependencies = [ [[package]] name = "build-info-common" -version = "0.0.33" +version = "0.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3b772d111f4cc62aa9018ba5cad5a1748a6d9a286d91a69b748cd233e43cdbd" +checksum = "0209923629b6854e590663db44001ea7cf35b3c7c13b04428c227f42bfda0489" dependencies = [ "chrono", "derive_more", - "semver 1.0.19", + "semver 1.0.17", "serde", ] [[package]] name = "build-info-proc" -version = "0.0.33" +version = "0.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39fc1a191a93dd7d72c21007a3d65cbfd88204bc744ecccac21858812bc06988" +checksum = "3a05d014e9d21c950661a8f774d0147297b7d24f22c1ce8605a1e3853c8dfdaf" dependencies = [ "anyhow", - "base64 0.21.4", + "base64 0.21.0", "bincode", "build-info-common", "chrono", @@ -782,15 +807,15 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.37", + "syn 2.0.15", "xz2", ] [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "byteorder" @@ -816,31 +841,31 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.6" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +checksum = "c530edf18f37068ac2d977409ed5cd50d53d73bc653c7647b48eb78976ac9ae2" dependencies = [ "serde", ] [[package]] name = "cargo-platform" -version = "0.1.3" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" dependencies = [ "serde", ] [[package]] name = "cargo_metadata" -version = "0.18.0" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb9ac64500cc83ce4b9f8dafa78186aa008c8dea77a09b94cd307fd0cd5022a8" +checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", - "semver 1.0.19", + "semver 1.0.17", "serde", "serde_json", "thiserror", @@ -848,12 +873,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" dependencies = [ "jobserver", - "libc", ] [[package]] @@ -901,17 +925,18 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", + "num-integer", "num-traits", "serde", + "time 0.1.45", "wasm-bindgen", - "windows-targets", + "winapi", ] [[package]] @@ -925,6 +950,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "collections" version = "0.1.0" @@ -946,9 +981,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" [[package]] name = "const-random" @@ -966,7 +1001,7 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb" dependencies = [ - "getrandom 0.2.10", + "getrandom 0.2.9", "once_cell", "proc-macro-hack", "tiny-keccak", @@ -996,18 +1031,33 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" + [[package]] name = "crc32c" -version = "0.6.4" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f48d60e5b4d2c53d5c2b1d8a58c849a70ae5e5509b08a48d047e3b65714a74" +checksum = "3dfea2db42e9927a3845fb268a10a72faed6d416065f77873f05e411457c363e" dependencies = [ "rustc_version 0.4.0", ] @@ -1031,11 +1081,21 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ "cfg-if", ] @@ -1048,9 +1108,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.5.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124" +checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -1069,6 +1129,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -1084,14 +1154,14 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.1" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2" dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest 0.10.7", + "digest 0.10.6", "fiat-crypto", "platforms", "rustc_version 0.4.0", @@ -1107,7 +1177,51 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.15", +] + +[[package]] +name = "cxx" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.15", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", ] [[package]] @@ -1147,12 +1261,12 @@ dependencies = [ [[package]] name = "dashmap" -version = "5.5.3" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" dependencies = [ "cfg-if", - "hashbrown 0.14.1", + "hashbrown 0.12.3", "lock_api", "once_cell", "parking_lot_core", @@ -1160,9 +1274,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.4.0" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" [[package]] name = "deadpool" @@ -1189,29 +1303,24 @@ dependencies = [ [[package]] name = "deadpool-runtime" -version = "0.1.3" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63dfa964fe2a66f3fde91fc70b267fe193d822c7e603e2a675a49a7f46ad3f49" +checksum = "eaa37046cc0f6c3cc6090fbdbf73ef0b8ef4cfcc37f6befc0020f63e8cf121e1" dependencies = [ "tokio", ] [[package]] name = "der" -version = "0.7.8" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "56acb310e15652100da43d130af8d97b509e95af61aab1c5a7939ef24337ee17" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] -[[package]] -name = "deranged" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" - [[package]] name = "derivative" version = "2.2.0" @@ -1272,9 +1381,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.7" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer 0.10.4", "const-oid", @@ -1288,14 +1397,20 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "ecdsa" -version = "0.16.8" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +checksum = "0997c976637b606099b9985693efa3581e84e41f5c11ba5255f88711058ad428" dependencies = [ "der", - "digest 0.10.7", + "digest 0.10.6", "elliptic-curve", "rfc6979", "signature 2.1.0", @@ -1340,19 +1455,22 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" dependencies = [ - "curve25519-dalek 4.1.1", + "curve25519-dalek 4.0.0", "ed25519 2.2.2", "rand_core 0.6.4", "serde", - "sha2 0.10.8", + "sha2 0.10.6", "zeroize", ] [[package]] name = "either" -version = "1.9.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +dependencies = [ + "serde", +] [[package]] name = "elliptic-curve" @@ -1362,7 +1480,7 @@ checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" dependencies = [ "base16ct", "crypto-bigint", - "digest 0.10.7", + "digest 0.10.6", "ff", "generic-array", "group", @@ -1375,9 +1493,9 @@ dependencies = [ [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ "cfg-if", ] @@ -1411,13 +1529,13 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.3" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1430,6 +1548,32 @@ dependencies = [ "libc", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.0.1" @@ -1448,26 +1592,32 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0870c84016d4b481be5c9f323c24f65e31e901ae618f0e80f4308fb00de1d2d" - -[[package]] -name = "finl_unicode" -version = "1.2.0" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" +checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" [[package]] name = "flate2" -version = "1.0.27" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "futures-core", + "futures-sink", + "pin-project", + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1491,9 +1641,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ "percent-encoding", ] @@ -1568,6 +1718,17 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + [[package]] name = "futures-io" version = "0.3.28" @@ -1582,7 +1743,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.15", ] [[package]] @@ -1658,9 +1819,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", @@ -1675,11 +1836,11 @@ checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "git2" -version = "0.18.1" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf97ba92db08df386e10c8ede66a2a0369bd277090afd8710e19e38de9ec0cd" +checksum = "ccf7f68c2995f392c49fffb4f95ae2c873297830eb25c6bc4c114ce8f4562acc" dependencies = [ - "bitflags 2.4.0", + "bitflags 1.3.2", "libc", "libgit2-sys", "log", @@ -1705,9 +1866,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.21" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" dependencies = [ "bytes", "fnv", @@ -1730,17 +1891,31 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.1" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "ahash 0.8.3", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.0", +] [[package]] name = "headers" -version = "0.3.9" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ - "base64 0.21.4", + "base64 0.13.1", + "bitflags 1.3.2", "bytes", "headers-core", "http 0.2.9", @@ -1763,6 +1938,9 @@ name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "hermit-abi" @@ -1775,9 +1953,18 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" [[package]] name = "hex" @@ -1800,7 +1987,16 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.7", + "digest 0.10.6", +] + +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", ] [[package]] @@ -1849,9 +2045,9 @@ dependencies = [ [[package]] name = "http-range-header" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" [[package]] name = "httparse" @@ -1861,15 +2057,15 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" dependencies = [ "bytes", "futures-channel", @@ -1882,7 +2078,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2", "tokio", "tower-service", "tracing", @@ -1899,10 +2095,10 @@ dependencies = [ "http 0.2.9", "hyper", "log", - "rustls", + "rustls 0.21.7", "rustls-native-certs", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", ] [[package]] @@ -1920,9 +2116,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1934,11 +2130,12 @@ dependencies = [ [[package]] name = "iana-time-zone-haiku" -version = "0.1.2" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" dependencies = [ - "cc", + "cxx", + "cxx-build", ] [[package]] @@ -1960,9 +2157,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.4.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1980,12 +2177,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", - "hashbrown 0.14.1", + "hashbrown 0.14.0", ] [[package]] @@ -1997,21 +2194,41 @@ dependencies = [ "generic-array", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "integer-encoding" version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "ipconfig" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +checksum = "bd302af1b90f2463a98fa5ad469fc212c8e3175a41c3068601bfa2727591c5be" dependencies = [ - "socket2 0.5.4", + "socket2", "widestring", - "windows-sys", + "winapi", "winreg", ] @@ -2039,11 +2256,20 @@ dependencies = [ "serde", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "jobserver" @@ -2056,9 +2282,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] @@ -2069,7 +2295,7 @@ version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" dependencies = [ - "base64 0.21.4", + "base64 0.21.0", "pem", "ring", "serde", @@ -2087,7 +2313,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "once_cell", - "sha2 0.10.8", + "sha2 0.10.6", "signature 2.1.0", ] @@ -2105,18 +2331,21 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] [[package]] name = "libc" -version = "0.2.148" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "libgit2-sys" -version = "0.16.1+1.7.1" +version = "0.14.2+1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2a2bb3680b094add03bb3732ec520ece34da31a8cd2d633d1389d0f0fb60d0c" +checksum = "7f3d95f6b51075fe9810a7ae22c7095f12b98005ab364d8544797a825ce946a4" dependencies = [ "cc", "libc", @@ -2124,11 +2353,28 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + +[[package]] +name = "libsqlite3-sys" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "libz-sys" -version = "1.1.12" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" dependencies = [ "cc", "libc", @@ -2136,6 +2382,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -2144,15 +2399,15 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.7" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" +checksum = "9b085a4f2cde5781fc4b1717f2e86c62f5cda49de7ba99a7c2eae02b61c9064c" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg", "scopeguard", @@ -2160,9 +2415,12 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] [[package]] name = "lru-cache" @@ -2196,7 +2454,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "regex-automata 0.1.10", + "regex-automata", ] [[package]] @@ -2207,9 +2465,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "matchit" -version = "0.7.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" [[package]] name = "maxminddb" @@ -2225,19 +2483,18 @@ dependencies = [ [[package]] name = "md-5" -version = "0.10.6" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" dependencies = [ - "cfg-if", - "digest 0.10.7", + "digest 0.10.6", ] [[package]] name = "memchr" -version = "2.6.3" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "metrics" @@ -2258,8 +2515,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] -name = "miniz_oxide" -version = "0.7.1" +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ @@ -2278,20 +2541,21 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", + "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] name = "mongodb" -version = "2.7.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22d517e7e678e1c9a2983ec704b43f3b22f38b1b7a247ea3ddb36d21578bf4e" +checksum = "ebe15399de63ad4294c80069967736cbb87ebe467a8cd0629df9cab88a6fbde6" dependencies = [ "async-trait", "base64 0.13.1", @@ -2312,20 +2576,20 @@ dependencies = [ "percent-encoding", "rand 0.8.5", "rustc_version_runtime", - "rustls", + "rustls 0.20.8", "rustls-pemfile", "serde", "serde_bytes", "serde_with", "sha-1", - "sha2 0.10.8", - "socket2 0.4.9", + "sha2 0.10.6", + "socket2", "stringprep", "strsim", "take_mut", "thiserror", "tokio", - "tokio-rustls", + "tokio-rustls 0.23.4", "tokio-util", "trust-dns-proto", "trust-dns-resolver", @@ -2358,6 +2622,16 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nonempty" version = "0.7.0" @@ -2375,7 +2649,7 @@ dependencies = [ "aws-config", "aws-sdk-s3", "axum", - "base64 0.21.4", + "base64 0.21.0", "bs58", "build-info", "build-info-build", @@ -2415,9 +2689,10 @@ dependencies = [ "serde", "serde_bson", "serde_json", - "sha2 0.10.8", + "sha2 0.10.6", "sha256", "sha3", + "sqlx", "test-context", "thiserror", "tokio", @@ -2447,9 +2722,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ "num-complex", "num-integer", @@ -2460,20 +2735,37 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ "autocfg", "num-integer", "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-complex" -version = "0.4.4" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" dependencies = [ "num-traits", ] @@ -2512,20 +2804,21 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", + "libm", ] [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi 0.3.3", + "hermit-abi 0.2.6", "libc", ] @@ -2552,11 +2845,11 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.57" +version = "0.10.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" +checksum = "97ea2d98598bf9ada7ea6ee8a30fb74f9156b63bbe495d64ec2b87c269d2dda3" dependencies = [ - "bitflags 2.4.0", + "bitflags 1.3.2", "cfg-if", "foreign-types", "libc", @@ -2573,7 +2866,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.15", ] [[package]] @@ -2584,9 +2877,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.93" +version = "0.9.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" +checksum = "992bac49bdbab4423199c654a5515bd2a6c6a23bf03f2dd3bdb7e5ae6259bc69" dependencies = [ "cc", "libc", @@ -2662,6 +2955,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "output_vt100" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" +dependencies = [ + "winapi", +] + [[package]] name = "outref" version = "0.5.1" @@ -2686,15 +2988,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", - "windows-targets", + "windows-sys 0.45.0", ] [[package]] @@ -2702,11 +3004,11 @@ name = "parquet" version = "42.0.0" source = "git+https://github.com/WalletConnect/arrow-rs.git?rev=99a1cc3#99a1cc36bce8d55e411dd441f2219d0689a82bee" dependencies = [ - "ahash", + "ahash 0.8.3", "bytes", "chrono", "flate2", - "hashbrown 0.14.1", + "hashbrown 0.14.0", "num", "num-bigint", "paste", @@ -2723,14 +3025,14 @@ dependencies = [ "parquet", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.15", ] [[package]] name = "paste" -version = "1.0.14" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] name = "pbkdf2" @@ -2738,7 +3040,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "digest 0.10.7", + "digest 0.10.6", ] [[package]] @@ -2750,37 +3052,46 @@ dependencies = [ "base64 0.13.1", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pin-project" -version = "1.1.3" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 1.0.109", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -2788,6 +3099,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -2800,15 +3122,15 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "platforms" -version = "3.1.2" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4503fa043bf02cee09a9582e9554b4c6403b2ef55e4612e96561d294419429f8" +checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" [[package]] name = "pnet_base" @@ -2861,11 +3183,13 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "pretty_assertions" -version = "1.4.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" dependencies = [ + "ctor", "diff", + "output_vt100", "yansi", ] @@ -2901,9 +3225,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] @@ -2937,9 +3261,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.33" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] @@ -3009,7 +3333,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.10", + "getrandom 0.2.9", ] [[package]] @@ -3023,9 +3347,9 @@ dependencies = [ [[package]] name = "redis" -version = "0.23.3" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f49cdc0bb3f412bf8e7d1bd90fe1d9eb10bc5c399ba90973c14662a27b3f8ba" +checksum = "3ea8c51b5dc1d8e5fd3350ec8167f464ec0995e79f2e90a075b63371500d557f" dependencies = [ "async-trait", "bytes", @@ -3040,6 +3364,15 @@ dependencies = [ "url", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -3051,14 +3384,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.5" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.8", - "regex-syntax 0.7.5", + "regex-syntax 0.7.1", ] [[package]] @@ -3070,17 +3402,6 @@ dependencies = [ "regex-syntax 0.6.29", ] -[[package]] -name = "regex-automata" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax 0.7.5", -] - [[package]] name = "regex-syntax" version = "0.6.29" @@ -3089,9 +3410,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" [[package]] name = "relay_client" @@ -3134,18 +3455,18 @@ dependencies = [ "serde", "serde-aux", "serde_json", - "sha2 0.10.8", + "sha2 0.10.6", "sha3", "thiserror", ] [[package]] name = "reqwest" -version = "0.11.20" +version = "0.11.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" dependencies = [ - "base64 0.21.4", + "base64 0.21.0", "bytes", "encoding_rs", "futures-core", @@ -3211,7 +3532,7 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", + "spin 0.5.2", "untrusted", "web-sys", "winapi", @@ -3219,9 +3540,9 @@ dependencies = [ [[package]] name = "rmp" -version = "0.8.12" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20" +checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f" dependencies = [ "byteorder", "num-traits", @@ -3230,15 +3551,37 @@ dependencies = [ [[package]] name = "rmp-serde" -version = "1.1.2" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a" +checksum = "c5b13be192e0220b8afb7222aa5813cb62cc269ebb5cac346ca6487681d2913e" dependencies = [ "byteorder", "rmp", "serde", ] +[[package]] +name = "rsa" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab43bb47d23c1a631b4b680199a45255dce26fa9ab2fa902581f624ff13e6a8" +dependencies = [ + "byteorder", + "const-oid", + "digest 0.10.6", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature 2.1.0", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -3266,7 +3609,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.19", + "semver 1.0.17", ] [[package]] @@ -3281,15 +3624,28 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.15" +version = "0.37.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f9da0cbd88f9f09e7814e388301c8414c51c62aa6ce1e4b5c551d49d96e531" +checksum = "f79bef90eb6d984c72722595b5b1348ab39275a5e5123faca6863bf07d75a4e0" dependencies = [ - "bitflags 2.4.0", + "bitflags 1.3.2", "errno", + "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustls" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +dependencies = [ + "log", + "ring", + "sct", + "webpki", ] [[package]] @@ -3306,9 +3662,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.6.3" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -3318,11 +3674,11 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64 0.21.4", + "base64 0.21.0", ] [[package]] @@ -3337,30 +3693,36 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "windows-sys", + "windows-sys 0.42.0", ] [[package]] name = "scopeguard" -version = "1.2.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "scratch" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" [[package]] name = "sct" @@ -3374,9 +3736,9 @@ dependencies = [ [[package]] name = "sec1" -version = "0.7.3" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e" dependencies = [ "base16ct", "der", @@ -3388,9 +3750,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.2" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -3401,9 +3763,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" dependencies = [ "core-foundation-sys", "libc", @@ -3420,9 +3782,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.19" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" dependencies = [ "serde", ] @@ -3435,15 +3797,15 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "seq-macro" -version = "0.3.5" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" +checksum = "e6b44e8fc93a14e66336d230954dda83d18b4605ccace8fe09bc7514a71ad0bc" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" dependencies = [ "serde_derive", ] @@ -3471,31 +3833,31 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.12" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" +checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.15", ] [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ - "indexmap 2.0.2", + "indexmap 1.9.3", "itoa", "ryu", "serde", @@ -3503,11 +3865,10 @@ dependencies = [ [[package]] name = "serde_path_to_error" -version = "0.1.14" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" +checksum = "f7f05c1d5476066defcdfacce1f52fc3cae3af1d3089727100c02ae92e5abbe0" dependencies = [ - "itoa", "serde", ] @@ -3564,18 +3925,18 @@ checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.7", + "digest 0.10.6", ] [[package]] name = "sha1" -version = "0.10.6" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.7", + "digest 0.10.6", ] [[package]] @@ -3593,26 +3954,23 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.7", + "digest 0.10.6", ] [[package]] name = "sha256" -version = "1.4.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7895c8ae88588ccead14ff438b939b0c569cd619116f14b4d13fdff7b8333386" +checksum = "5f9f8b5de2bac3a4ae28e9b611072a8e326d9b26c8189c0972d4c321fa684f1f" dependencies = [ - "async-trait", - "bytes", "hex", - "sha2 0.10.8", - "tokio", + "sha2 0.10.6", ] [[package]] @@ -3621,15 +3979,15 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest 0.10.7", + "digest 0.10.6", "keccak", ] [[package]] name = "sharded-slab" -version = "0.1.6" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b21f559e07218024e7e9f90f96f601825397de0e25420135f7f952453fed0b" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ "lazy_static", ] @@ -3655,7 +4013,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" dependencies = [ - "digest 0.10.7", + "digest 0.10.6", "rand_core 0.6.4", ] @@ -3668,23 +4026,23 @@ dependencies = [ "num-bigint", "num-traits", "thiserror", - "time", + "time 0.3.20", ] [[package]] name = "slab" -version = "0.4.9" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.11.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" @@ -3697,20 +4055,19 @@ dependencies = [ ] [[package]] -name = "socket2" -version = "0.5.4" +name = "spin" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" -dependencies = [ - "libc", - "windows-sys", -] +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spin" -version = "0.5.2" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "spki" @@ -3722,6 +4079,221 @@ dependencies = [ "der", ] +[[package]] +name = "sqlformat" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85" +dependencies = [ + "itertools", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e58421b6bc416714d5115a2ca953718f6c621a51b68e4f4922aea5a4391a721" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4cef4251aabbae751a3710927945901ee1d97ee96d757f6880ebb9a79bfd53" +dependencies = [ + "ahash 0.8.3", + "atoi", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "dotenvy", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap 2.0.0", + "log", + "memchr", + "native-tls", + "once_cell", + "paste", + "percent-encoding", + "serde", + "serde_json", + "sha2 0.10.6", + "smallvec", + "sqlformat", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "sqlx-macros" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "208e3165167afd7f3881b16c1ef3f2af69fa75980897aac8874a0696516d12c2" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a4a8336d278c62231d87f24e8a7a74898156e34c1c18942857be2acb29c7dfc" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2 0.10.6", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca69bf415b93b60b80dc8fda3cb4ef52b2336614d8da2de5456cc942a110482" +dependencies = [ + "atoi", + "base64 0.21.0", + "bitflags 2.4.0", + "byteorder", + "bytes", + "chrono", + "crc", + "digest 0.10.6", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2 0.10.6", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0db2df1b8731c3651e204629dd55e52adbae0462fa1bdcbed56a2302c18181e" +dependencies = [ + "atoi", + "base64 0.21.0", + "bitflags 2.4.0", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha1", + "sha2 0.10.6", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4c21bf34c7cae5b283efb3ac1bcc7670df7561124dc2f8bdc0b59be40f79a2" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "tracing", + "url", + "uuid", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -3730,11 +4302,10 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stringprep" -version = "0.1.4" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" +checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" dependencies = [ - "finl_unicode", "unicode-bidi", "unicode-normalization", ] @@ -3747,9 +4318,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "subtle" -version = "2.5.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" @@ -3764,9 +4335,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.37" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" dependencies = [ "proc-macro2", "quote", @@ -3803,15 +4374,24 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.8.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ "cfg-if", - "fastrand", - "redox_syscall", + "fastrand 1.9.0", + "redox_syscall 0.3.5", "rustix", - "windows-sys", + "windows-sys 0.45.0", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", ] [[package]] @@ -3837,22 +4417,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.49" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.49" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.15", ] [[package]] @@ -3915,11 +4495,21 @@ dependencies = [ [[package]] name = "time" -version = "0.3.29" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ - "deranged", "itoa", "serde", "time-core", @@ -3928,15 +4518,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] name = "time-macros" -version = "0.2.15" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" dependencies = [ "time-core", ] @@ -3967,11 +4557,11 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.0" +version = "1.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" dependencies = [ - "backtrace", + "autocfg", "bytes", "libc", "mio", @@ -3979,20 +4569,20 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.4", + "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.15", ] [[package]] @@ -4005,21 +4595,32 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls 0.20.8", + "tokio", + "webpki", +] + [[package]] name = "tokio-rustls" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.7", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" dependencies = [ "futures-core", "pin-project-lite", @@ -4042,9 +4643,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.9" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" dependencies = [ "bytes", "futures-core", @@ -4122,26 +4723,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" dependencies = [ "crossbeam-channel", - "time", + "time 0.3.20", "tracing-subscriber", ] [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 1.0.109", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", "valuable", @@ -4174,9 +4775,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" dependencies = [ "matchers", "nu-ansi-term", @@ -4285,9 +4886,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.17.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-bidi" @@ -4297,9 +4898,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-normalization" @@ -4310,11 +4911,29 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + [[package]] name = "universal-hash" -version = "0.5.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" dependencies = [ "crypto-common", "subtle", @@ -4328,20 +4947,20 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.4.1" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", - "idna 0.4.0", + "idna 0.3.0", "percent-encoding", ] [[package]] name = "urlencoding" -version = "2.1.3" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" [[package]] name = "utf-8" @@ -4351,11 +4970,11 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "uuid" -version = "1.4.1" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +checksum = "5b55a3fef2a1e3b3a00ce878640918820d3c51081576ac657d23af9fc7928fdb" dependencies = [ - "getrandom 0.2.10", + "getrandom 0.2.9", "serde", ] @@ -4385,10 +5004,11 @@ checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "want" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ + "log", "try-lock", ] @@ -4398,6 +5018,12 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -4406,9 +5032,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -4416,24 +5042,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.37", + "syn 1.0.109", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" dependencies = [ "cfg-if", "js-sys", @@ -4443,9 +5069,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4453,22 +5079,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "wc" @@ -4486,25 +5112,44 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "webpki-roots" -version = "0.25.2" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + +[[package]] +name = "whoami" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" [[package]] name = "widestring" -version = "1.0.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" +checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" [[package]] name = "winapi" @@ -4522,6 +5167,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -4534,7 +5188,31 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets", + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", ] [[package]] @@ -4543,74 +5221,130 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winreg" -version = "0.50.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ - "cfg-if", - "windows-sys", + "winapi", ] [[package]] @@ -4628,7 +5362,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ - "curve25519-dalek 4.1.1", + "curve25519-dalek 4.0.0", "rand_core 0.6.4", "serde", "zeroize", @@ -4672,5 +5406,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.15", ] diff --git a/Cargo.toml b/Cargo.toml index 885b713c..bdf0c72e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "notify-server" version = "0.1.0" edition = "2021" -authors = ["Maciej Rak "] +authors = ["Chris Smith "] build = "build.rs" [dependencies] @@ -97,6 +97,7 @@ lazy_static = "1.4.0" rmp-serde = "1.1.1" deadpool-redis = "0.12.0" rand_chacha = "0.3.1" +sqlx = { version = "0.7.1", features = ["runtime-tokio-native-tls", "postgres", "chrono", "uuid"] } [dev-dependencies] sha3 = "0.10.8" diff --git a/README.md b/README.md index d415cb13..244bc263 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,16 @@ just amigood ``` +### Storage tests + +```bash +just run-storage-docker test-storage +``` + +```bash +just stop-storage-docker +``` + ### Integration tests ```bash diff --git a/docker-compose.notify-server.yml b/docker-compose.notify-server.yml index 13d82595..7d5b45fc 100644 --- a/docker-compose.notify-server.yml +++ b/docker-compose.notify-server.yml @@ -18,6 +18,7 @@ services: - LOG_LEVEL_OTEL=info,notify-server=4 - TELEMETRY_ENABLED=true - DATABASE_URL=mongodb://admin:admin@mongo:27017/notify?authSource=admin + - POSTGRES_URL=postgres://postgres:password@postgres:5432/postgres - OTEL_SERVICE_NAME=notify-server - OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317 volumes: diff --git a/docker-compose.storage.yml b/docker-compose.storage.yml index 0de07a9a..de8f6c53 100644 --- a/docker-compose.storage.yml +++ b/docker-compose.storage.yml @@ -45,6 +45,13 @@ services: - ME_CONFIG_MONGODB_ADMINPASSWORD=admin - ME_CONFIG_MONGODB_URL="mongodb://admin:admin@mongo:27017" + postgres: + image: postgres:16 + environment: + POSTGRES_HOST_AUTH_METHOD: trust + ports: + - "5432:5432" + networks: notify-server: ipam: diff --git a/justfile b/justfile index 8765064c..02ed5d96 100644 --- a/justfile +++ b/justfile @@ -27,6 +27,10 @@ test-all: @echo '==> Testing project (all features)' cargo test --all-features --lib --bins +test-storage: + @echo '==> Testing storage' + cargo test --test storage -- --test-threads=1 # --test-threads=1 to only run 1 migration test at a time since they drop the entire schema + # Clean build artifacts clean: @echo '==> Cleaning project target/*' @@ -44,7 +48,7 @@ clippy: if command -v cargo-clippy >/dev/null; then echo '==> Running clippy' - cargo clippy --all-features --tests -- -D clippy::all + cargo clippy --all-features --tests -- -D warnings else echo '==> clippy not found in PATH, skipping' fi @@ -56,7 +60,7 @@ fmt: if command -v cargo-fmt >/dev/null; then echo '==> Running rustfmt' - cargo +nightly fmt + cargo fmt else echo '==> rustfmt not found in PATH, skipping' fi @@ -68,6 +72,17 @@ fmt: echo '==> terraform not found in PATH, skipping' fi +fmt-imports: + #!/bin/bash + set -euo pipefail + + if command -v cargo-fmt >/dev/null; then + echo '==> Running rustfmt' + cargo +nightly fmt -- --config group_imports=StdExternalCrate,imports_granularity=One + else + echo '==> rustfmt not found in PATH, skipping' + fi + # Run commit checker commit-check: #!/bin/bash @@ -148,11 +163,6 @@ deploy-terraform ENV: terraform -chdir=terraform workspace select {{ENV}} terraform -chdir=terraform apply --var-file=vars/{{ENV}}.tfvars -commit MSG: - @echo '==> Committing changes' - cargo +nightly fmt && \ - git commit -a -S -m "{{MSG}}" - tarp ENV: @echo '==> Checking test coverage' ENVIRONMENT={{ENV}} cargo tarpaulin diff --git a/migrations/20231018121518_init.sql b/migrations/20231018121518_init.sql new file mode 100644 index 00000000..39baf0ff --- /dev/null +++ b/migrations/20231018121518_init.sql @@ -0,0 +1,64 @@ +CREATE TABLE project ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + inserted_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), + + project_id VARCHAR(255) NOT NULL UNIQUE, + app_domain VARCHAR(255) NOT NULL UNIQUE, + topic VARCHAR(255) NOT NULL UNIQUE, + + authentication_public_key VARCHAR(255) NOT NULL UNIQUE, + authentication_private_key VARCHAR(255) NOT NULL UNIQUE, + subscribe_public_key VARCHAR(255) NOT NULL UNIQUE, + subscribe_private_key VARCHAR(255) NOT NULL UNIQUE +); +CREATE INDEX projects_project_id_idx ON project (project_id); +CREATE INDEX projects_app_domain_idx ON project (app_domain); +CREATE INDEX projects_topic_idx ON project (topic); + +CREATE TABLE subscriber ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + inserted_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), + + project UUID NOT NULL REFERENCES project (id) ON DELETE CASCADE, + account VARCHAR(255) NOT NULL, + sym_key VARCHAR(255) NOT NULL UNIQUE, + topic VARCHAR(255) NOT NULL UNIQUE, + expiry TIMESTAMPTZ NOT NULL, + + UNIQUE (project, account) +); +CREATE INDEX subscribers_project_idx ON subscriber (project); +CREATE INDEX subscribers_account_idx ON subscriber (account); +CREATE INDEX subscribers_topic_idx ON subscriber (topic); +CREATE INDEX subscribers_expiry_idx ON subscriber (expiry); + +CREATE TABLE subscriber_scope ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + inserted_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), + + subscriber UUID NOT NULL REFERENCES subscriber (id) ON DELETE CASCADE, + name VARCHAR(255) NOT NULL, + + UNIQUE (subscriber, name) +); +CREATE INDEX subscriber_scope_subscriber_idx ON subscriber_scope (subscriber); +CREATE INDEX subscriber_scope_name_idx ON subscriber_scope (name); + +CREATE TABLE subscription_watcher ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + inserted_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), + + account VARCHAR(255) NOT NULL, + project UUID REFERENCES project (id) ON DELETE CASCADE, + did_key VARCHAR(255) NOT NULL UNIQUE, + sym_key VARCHAR(255) NOT NULL, + expiry TIMESTAMPTZ NOT NULL +); +CREATE INDEX subscription_watcher_account_idx ON subscription_watcher (account); +CREATE INDEX subscription_watcher_project_idx ON subscription_watcher (project); +CREATE INDEX subscription_watcher_did_key_idx ON subscription_watcher (did_key); +CREATE INDEX subscription_watcher_expiry_idx ON subscription_watcher (expiry); diff --git a/migrations/ERD.md b/migrations/ERD.md new file mode 100644 index 00000000..68ab3fa3 --- /dev/null +++ b/migrations/ERD.md @@ -0,0 +1,84 @@ +# ERD + +```mermaid +erDiagram + project { + uuid id PK + string project_id + string app_domain + string topic + string authentication_public_key + string authentication_private_key + string subscribe_public_key + string subscribe_private_key + } + + subscriber { + uuid id PK + uuid project FK + string account + string sym_key + string topic + timestamp expiry + } + subscriber }o--|| project : "subscribed to" + + subscriber_scope { + uuid id PK + uuid subscriber FK + string name + } + subscriber ||--o{ subscriber_scope : "has scope" + + subscription_watcher { + uuid id PK + string account + uuid project FK "NULL is all projects" + string did_key + string sym_key + timestamp expiry + } + subscription_watcher }o--o| project : "watching" + + notification { + uuid id PK + uuid subscriber FK + string type + string title + string body + string url + } + notification }o--|| subscriber : "sent to" + + notification_event { + uuid id PK + uuid message FK + enum name "queued, sent, delivered, read, deleted" + timestamp time + } + notification_event }o--|| notification : "for" + + webhook { + uuid id PK + uuid project FK + string url + } + webhook }o--|| project : "watching" + + webhook_type { + uuid id PK + uuid webhook FK + enum type "subscribed, updated, unsubscribed" + } + webhook ||--|{ webhook_type : "has types" + + webhook_message { + uuid id PK + uuid webhook FK + enum event "subscribed, updated, unsubscribed" + string account + timestamp created + timestamp next_send + } + webhook_message }o--|| webhook : "send to" +``` diff --git a/migrations/README.md b/migrations/README.md new file mode 100644 index 00000000..2351439c --- /dev/null +++ b/migrations/README.md @@ -0,0 +1,15 @@ +# Migrations + +This folder contains migrations for Notify Server and they are automatically run on start-up. + +If you make a change, please also update [ERD.md](./ERD.md). + +## New Migration + +```bash +cargo install sqlx-cli +``` + +```bash +sqlx migrate add +``` diff --git a/migrations/new.sh b/migrations/new.sh new file mode 100755 index 00000000..8db3fa48 --- /dev/null +++ b/migrations/new.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -e + +DIR="$(dirname "$0")" + +DESCRIPTION=$1 +touch "$DIR/$(date +%s)_$DESCRIPTION.sql" diff --git a/src/analytics/client_info.rs b/src/analytics/client_info.rs deleted file mode 100644 index 6570177d..00000000 --- a/src/analytics/client_info.rs +++ /dev/null @@ -1,26 +0,0 @@ -use {parquet_derive::ParquetRecordWriter, serde::Serialize, std::sync::Arc}; - -#[derive(Debug, Serialize, ParquetRecordWriter)] -#[serde(rename_all = "camelCase")] -pub struct ClientInfo { - pub project_id: Arc, - pub account: Arc, - pub topic: Arc, - pub registered_at: chrono::NaiveDateTime, -} - -impl ClientInfo { - pub fn new( - project_id: Arc, - account: Arc, - topic: Arc, - registered_at: chrono::NaiveDateTime, - ) -> Self { - Self { - project_id, - account, - topic, - registered_at, - } - } -} diff --git a/src/analytics/message_info.rs b/src/analytics/message_info.rs deleted file mode 100644 index 2821b2ff..00000000 --- a/src/analytics/message_info.rs +++ /dev/null @@ -1,14 +0,0 @@ -use {parquet_derive::ParquetRecordWriter, serde::Serialize, std::sync::Arc}; - -#[derive(Debug, Serialize, ParquetRecordWriter)] -#[serde(rename_all = "camelCase")] -pub struct MessageInfo { - pub region: Option>, - pub country: Option>, - pub continent: Option>, - pub project_id: Arc, - pub msg_id: Arc, - pub topic: Arc, - pub account: Arc, - pub sent_at: chrono::NaiveDateTime, -} diff --git a/src/analytics/mod.rs b/src/analytics/mod.rs index 1fb59392..a52cc8a6 100644 --- a/src/analytics/mod.rs +++ b/src/analytics/mod.rs @@ -1,6 +1,9 @@ use { - self::message_info::MessageInfo, - crate::{analytics::client_info::ClientInfo, config::Configuration, error::Result}, + self::{ + notify_client::NotifyClientParams, + notify_message::{NotifyMessage, NotifyMessageParams}, + }, + crate::{analytics::notify_client::NotifyClient, config::Configuration, error::Result}, aws_sdk_s3::Client as S3Client, std::{net::IpAddr, sync::Arc}, tracing::{error, info}, @@ -15,13 +18,13 @@ use { }, }; -pub mod client_info; -pub mod message_info; +pub mod notify_client; +pub mod notify_message; #[derive(Clone)] pub struct NotifyAnalytics { - pub messages: Analytics, - pub clients: Analytics, + pub messages: Analytics, + pub clients: Analytics, pub geoip_resolver: Option>, } @@ -58,7 +61,7 @@ impl NotifyAnalytics { node_ip: node_ip.clone(), }); - let collector = ParquetWriter::::new(opts.clone(), exporter)?; + let collector = ParquetWriter::::new(opts.clone(), exporter)?; Analytics::new(collector) }; @@ -72,7 +75,7 @@ impl NotifyAnalytics { node_ip, }); - Analytics::new(ParquetWriter::::new(opts, exporter)?) + Analytics::new(ParquetWriter::::new(opts, exporter)?) }; Ok(Self { @@ -82,12 +85,12 @@ impl NotifyAnalytics { }) } - pub fn message(&self, data: MessageInfo) { - self.messages.collect(data); + pub fn message(&self, message: NotifyMessageParams) { + self.messages.collect(message.into()); } - pub fn client(&self, data: ClientInfo) { - self.clients.collect(data); + pub fn client(&self, client: NotifyClientParams) { + self.clients.collect(client.into()); } pub fn lookup_geo_data(&self, addr: IpAddr) -> Option { diff --git a/src/analytics/notify_client.rs b/src/analytics/notify_client.rs new file mode 100644 index 00000000..4decc1f9 --- /dev/null +++ b/src/analytics/notify_client.rs @@ -0,0 +1,64 @@ +use { + parquet_derive::ParquetRecordWriter, + serde::Serialize, + std::fmt::{self, Display, Formatter}, +}; + +pub enum NotifyClientMethod { + Subscribe, + Update, + Unsubscribe, +} + +impl Display for NotifyClientMethod { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Subscribe => write!(f, "subscribe"), + Self::Update => write!(f, "update"), + Self::Unsubscribe => write!(f, "unsubscribe"), + } + } +} + +pub struct NotifyClientParams { + pub pk: String, + pub method: NotifyClientMethod, + pub project_id: String, + pub account: String, + pub topic: String, + pub notify_topic: String, + pub old_scope: String, + pub new_scope: String, +} + +#[derive(Debug, Serialize, ParquetRecordWriter)] +#[serde(rename_all = "camelCase")] +pub struct NotifyClient { + pub pk: String, + pub method: String, // subscribe, update, unsubscribe + pub project_id: String, + pub account: String, + pub account_hash: String, + pub topic: String, + pub notify_topic: String, + pub old_scope: String, + pub new_scope: String, + pub event_at: chrono::NaiveDateTime, +} + +impl From for NotifyClient { + fn from(client: NotifyClientParams) -> Self { + Self { + pk: client.pk, + method: client.method.to_string(), + project_id: client.project_id, + account_hash: sha256::digest(client.account.as_bytes()), + account: client.account, + topic: client.topic, + notify_topic: client.notify_topic, + old_scope: client.old_scope, + new_scope: client.new_scope, + event_at: wc::analytics::time::now(), + } + } +} diff --git a/src/analytics/notify_message.rs b/src/analytics/notify_message.rs new file mode 100644 index 00000000..2566d2a6 --- /dev/null +++ b/src/analytics/notify_message.rs @@ -0,0 +1,38 @@ +use {parquet_derive::ParquetRecordWriter, serde::Serialize, std::sync::Arc}; + +pub struct NotifyMessageParams { + pub project_id: Arc, + pub msg_id: Arc, + pub topic: Arc, + pub account: Arc, + pub notification_type: Arc, + pub send_id: String, +} + +#[derive(Debug, Serialize, ParquetRecordWriter)] +#[serde(rename_all = "camelCase")] +pub struct NotifyMessage { + pub project_id: Arc, + pub msg_id: Arc, + pub topic: Arc, + pub account: Arc, + pub account_hash: String, + pub notification_type: Arc, + pub send_id: String, + pub event_at: chrono::NaiveDateTime, +} + +impl From for NotifyMessage { + fn from(message: NotifyMessageParams) -> Self { + Self { + project_id: message.project_id, + msg_id: message.msg_id, + topic: message.topic, + account_hash: sha256::digest(message.account.as_ref()), + account: message.account, + notification_type: message.notification_type, + send_id: message.send_id, + event_at: wc::analytics::time::now(), + } + } +} diff --git a/src/auth.rs b/src/auth.rs index b474abc5..742c145d 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,5 +1,5 @@ use { - crate::error::Result, + crate::{error::Result, model::types::AccountId}, base64::Engine, chrono::{DateTime, Duration as CDuration, Utc}, ed25519_dalek::Signer, @@ -87,7 +87,7 @@ pub struct NotifyServerSubscription { /// wc_notifySubscriptionDelete pub sym_key: String, /// CAIP-10 account - pub account: String, + pub account: AccountId, // TODO do we need to return this? /// Array of notification types enabled for this subscription pub scope: HashSet, /// Unix timestamp of expiration @@ -405,7 +405,7 @@ pub enum AuthError { #[derive(Serialize, Deserialize, Debug)] pub struct Authorization { - pub account: String, + pub account: AccountId, pub app: AuthorizedApp, } @@ -482,7 +482,7 @@ pub async fn verify_identity(iss: &str, ksu: &str, sub: &str) -> Result> for AuthedProjectId { @@ -46,17 +47,18 @@ impl FromRequestParts> for AuthedProjectId { ) })?; - let project_id = path_args + let project_id: ProjectId = path_args .get("project_id") .ok_or(( StatusCode::BAD_REQUEST, json!({"reason": "Missing project_id parameter".to_string()}).to_string(), ))? - .to_string(); + .to_owned() + .into(); let authenticated = state .registry - .is_authenticated(&project_id, project_secret.token()) + .is_authenticated(project_id.clone(), project_secret.token()) .await .map_err(|e| { warn!(?e, "Failed to authenticate project"); diff --git a/src/handlers/get_subscribers.rs b/src/handlers/get_subscribers.rs index 8d90cbae..90aaab38 100644 --- a/src/handlers/get_subscribers.rs +++ b/src/handlers/get_subscribers.rs @@ -1,7 +1,9 @@ use { - crate::{error::Result, extractors::AuthedProjectId, state::AppState, types::ClientData}, + crate::{ + error::Result, extractors::AuthedProjectId, + model::helpers::get_subscriber_accounts_by_project_id, state::AppState, + }, axum::{extract::State, http::StatusCode, response::IntoResponse, Json}, - futures::TryStreamExt, std::sync::Arc, tracing::info, }; @@ -10,19 +12,9 @@ pub async fn handler( State(state): State>, AuthedProjectId(project_id, _): AuthedProjectId, ) -> Result { - info!("Getting subscribers for project: {}", project_id); + info!("Getting subscribers for project: {project_id}"); - let mut cursor = state - .database - .collection::(&project_id) - .find(None, None) - .await?; + let accounts = get_subscriber_accounts_by_project_id(project_id, &state.postgres).await?; - let mut result = vec![]; - - while let Some(client) = cursor.try_next().await? { - result.push(client.id); - } - - Ok((StatusCode::OK, Json(result)).into_response()) + Ok((StatusCode::OK, Json(accounts)).into_response()) } diff --git a/src/handlers/notify.rs b/src/handlers/notify.rs index 5da55895..061680bd 100644 --- a/src/handlers/notify.rs +++ b/src/handlers/notify.rs @@ -1,37 +1,35 @@ use { - super::subscribe_topic::ProjectData, crate::{ - analytics::message_info::MessageInfo, + analytics::notify_message::NotifyMessageParams, auth::add_ttl, error, extractors::AuthedProjectId, jsonrpc::{JsonRpcParams, JsonRpcPayload, NotifyPayload}, + model::{ + helpers::{ + get_project_by_project_id, get_subscribers_for_project_in, SubscriberWithScope, + }, + types::AccountId, + }, spec::{NOTIFY_MESSAGE_TAG, NOTIFY_MESSAGE_TTL}, state::AppState, - types::{ClientData, Envelope, EnvelopeType0, Notification}, + types::{Envelope, EnvelopeType0, Notification}, websocket_service::decode_key, }, - axum::{ - extract::{ConnectInfo, State}, - http::StatusCode, - response::IntoResponse, - Json, - }, - base64::Engine, + axum::{extract::State, http::StatusCode, response::IntoResponse, Json}, + base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}, chrono::Utc, - ed25519_dalek::Signer, + ed25519_dalek::{Signer, SigningKey}, error::Result, futures::FutureExt, - mongodb::bson::doc, relay_rpc::{ domain::{ClientId, DecodedClientId, Topic}, jwt::{JwtHeader, JWT_HEADER_ALG, JWT_HEADER_TYP}, rpc::{msg_id::MsgId, Publish}, }, serde::{Deserialize, Serialize}, - std::{collections::HashSet, net::SocketAddr, sync::Arc, time::Duration}, + std::{collections::HashSet, sync::Arc, time::Duration}, tokio::time::error::Elapsed, - tokio_stream::StreamExt, tracing::{info, warn}, wc::metrics::otel::{Context, KeyValue}, }; @@ -39,18 +37,18 @@ use { #[derive(Debug, Serialize, Deserialize, Clone)] pub struct NotifyBody { pub notification: Notification, - pub accounts: Vec, + pub accounts: Vec, } #[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Debug)] pub struct SendFailure { - pub account: String, + pub account: AccountId, pub reason: String, } #[derive(Clone)] struct PublishJob { - account: String, + account: AccountId, topic: Topic, message: String, } @@ -59,14 +57,13 @@ struct PublishJob { // Change String to Error #[derive(Serialize, Deserialize, Debug)] pub struct Response { - pub sent: HashSet, + pub sent: HashSet, pub failed: HashSet, - pub not_found: HashSet, + pub not_found: HashSet, } pub async fn handler( State(state): State>, - ConnectInfo(addr): ConnectInfo, AuthedProjectId(project_id, _): AuthedProjectId, Json(notify_args): Json, ) -> Result { @@ -74,6 +71,23 @@ pub async fn handler( let request_id = uuid::Uuid::new_v4(); let timer = std::time::Instant::now(); + // TODO handle project not found + let project = get_project_by_project_id(project_id.clone(), &state.postgres).await?; + let project_signing_details = { + let private_key = ed25519_dalek::SigningKey::from_bytes(&decode_key( + &project.authentication_private_key, + )?); + let decoded_client_id = DecodedClientId( + hex::decode(project.authentication_public_key.clone())?[0..32].try_into()?, + ); + let identity = ClientId::from(decoded_client_id); + ProjectSigningDetails { + identity, + private_key, + app: project.app_domain.into(), + } + }; + let mut response = Response { sent: HashSet::new(), failed: HashSet::new(), @@ -84,40 +98,34 @@ pub async fn handler( notification, accounts, } = notify_args; + let notification_type = notification.r#type.clone().into(); // We assume all accounts were not found untill found - accounts.iter().for_each(|account| { - response.not_found.insert(account.clone()); - }); - - // Get the accounts - let cursor = state - .database - .collection::(&project_id) - .find(doc! { "_id": {"$in": &accounts}}, None) - .await?; + response.not_found.extend(accounts.iter().cloned()); - let project_data: ProjectData = state - .database - .collection::("project_data") - .find_one(doc! { "_id": project_id.clone()}, None) - .await? - .ok_or(error::Error::NoProjectDataForTopic(project_id.clone()))?; + let subscribers = + get_subscribers_for_project_in(project.id, &accounts, &state.postgres).await?; // Generate publish jobs - this will also remove accounts from not_found // Prepares the encrypted message and gets the topic for each account - let jobs = generate_publish_jobs(notification, cursor, &mut response, &project_data).await?; + let jobs = generate_publish_jobs( + notification, + subscribers, + &mut response, + &project_signing_details, + ) + .await?; // Attempts to send to all found accounts, waiting for relay ack for // NOTIFY_TIMEOUT seconds process_publish_jobs( jobs, + notification_type, state.http_relay_client.clone(), &mut response, request_id, - &addr, &state, - &project_id, + project_id.as_ref(), ) .await?; @@ -140,24 +148,13 @@ enum JobError { async fn process_publish_jobs( jobs: Vec, + notification_type: Arc, client: Arc, response: &mut Response, request_id: uuid::Uuid, - addr: &SocketAddr, state: &Arc, project_id: &str, ) -> Result<()> { - let geo_ip = state - .analytics - .lookup_geo_data(addr.ip()) - .map_or((None, None, None), |geo| { - ( - geo.country, - geo.continent, - geo.region.map(|r| Arc::from(r.join(", "))), - ) - }); - let timer = std::time::Instant::now(); let futures = jobs.into_iter().map(|job| { let remaining_time = timer.elapsed(); @@ -181,7 +178,7 @@ async fn process_publish_jobs( async fn do_publish( client: Arc, - account: String, + account: AccountId, publish: Publish, ) -> std::result::Result<(), relay_client::error::Error> { let go = || { @@ -229,18 +226,16 @@ async fn process_publish_jobs( } }) .map({ - let (country, continent, region) = geo_ip.clone(); + let notification_type = notification_type.clone(); move |result| { if result.is_ok() { - state.analytics.message(MessageInfo { - region, - country, - continent, + state.analytics.message(NotifyMessageParams { project_id: project_id.into(), msg_id: msg_id.into(), - topic: job.topic.to_string().into(), - account: job.account.clone().into(), - sent_at: wc::analytics::time::now(), + topic: job.topic.into_value(), + account: job.account.into_value(), + notification_type, + send_id: "".to_string(), // TODO for when queueing is added }); } result @@ -253,10 +248,10 @@ async fn process_publish_jobs( for result in results { match result { Ok((account, topic)) => { - response.sent.insert(account.to_string()); info!( "[{request_id}] Successfully sent notification to {account} on topic: {topic}", ); + response.sent.insert(account); } Err((error, account, topic)) => { warn!( @@ -264,7 +259,7 @@ async fn process_publish_jobs( {topic}: {error:?}" ); response.failed.insert(SendFailure { - account: account.to_string(), + account, reason: "Internal error".into(), }); } @@ -276,20 +271,21 @@ async fn process_publish_jobs( async fn generate_publish_jobs( notification: Notification, - mut cursor: mongodb::Cursor, + subscribers: Vec, response: &mut Response, - project_data: &ProjectData, + project_signing_details: &ProjectSigningDetails, ) -> Result> { let mut jobs = vec![]; let id = chrono::Utc::now().timestamp_millis().unsigned_abs(); - while let Some(client_data) = cursor.try_next().await? { - response.not_found.remove(&client_data.id); + let notification = Arc::new(notification); + for subscriber in subscribers { + response.not_found.remove(&subscriber.account); - if !client_data.scope.contains(¬ification.r#type) { + if !subscriber.scope.contains(¬ification.r#type) { response.failed.insert(SendFailure { - account: client_data.id.clone(), + account: subscriber.account.clone(), reason: "Client is not subscribed to this notification type".into(), }); continue; @@ -299,11 +295,16 @@ async fn generate_publish_jobs( id, jsonrpc: "2.0".to_string(), params: JsonRpcParams::Push(NotifyPayload { - message_auth: sign_message(¬ification, project_data, &client_data)?.to_string(), + message_auth: sign_message( + notification.clone(), + subscriber.account.clone(), + project_signing_details, + )? + .to_string(), }), }; - let sym_key = decode_key(&client_data.sym_key)?; + let sym_key = decode_key(&subscriber.sym_key)?; let envelope = Envelope::::new(&sym_key, &message)?; @@ -315,7 +316,7 @@ async fn generate_publish_jobs( jobs.push(PublishJob { topic, message: base64_notification, - account: client_data.id, + account: subscriber.account, }) } Ok(jobs) @@ -346,52 +347,40 @@ fn send_metrics(metrics: &crate::metrics::Metrics, response: &Response, timer: s .record(&ctx, timer.elapsed().as_millis().try_into().unwrap(), &[]) } +struct ProjectSigningDetails { + identity: ClientId, + private_key: SigningKey, + app: Arc, +} + fn sign_message( - msg: &Notification, - project_data: &ProjectData, - client_data: &ClientData, + msg: Arc, + account: AccountId, + ProjectSigningDetails { + identity, + private_key, + app, + }: &ProjectSigningDetails, ) -> Result { - let decoded_client_id = DecodedClientId( - hex::decode(project_data.identity_keypair.public_key.clone())?[0..32].try_into()?, - ); - let identity = ClientId::from(decoded_client_id).to_string(); - - let did_pkh = format!("did:pkh:{}", client_data.id); - let now = Utc::now(); - let message = { - let msg = JwtMessage { - iat: now.timestamp(), - exp: add_ttl(now, NOTIFY_MESSAGE_TTL).timestamp(), - iss: format!("did:key:{identity}"), - act: "notify_message".to_string(), - sub: did_pkh, - app: project_data.app_domain.to_string(), - msg: msg.clone(), - }; - let serialized = serde_json::to_string(&msg)?; - base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(serialized) - }; - - let header = { - let data = JwtHeader { - typ: JWT_HEADER_TYP, - alg: JWT_HEADER_ALG, - }; - - let serialized = serde_json::to_string(&data)?; - - base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(serialized) - }; - - // TODO don't decode this key for every message - let private_key = ed25519_dalek::SigningKey::from_bytes(&decode_key( - &project_data.identity_keypair.private_key, - )?); + let message = URL_SAFE_NO_PAD.encode(serde_json::to_string(&JwtMessage { + iat: now.timestamp(), + exp: add_ttl(now, NOTIFY_MESSAGE_TTL).timestamp(), + iss: format!("did:key:{identity}"), + act: "notify_message".to_string(), + sub: format!("did:pkh:{account}"), + app: app.clone(), + msg, + })?); + + let header = URL_SAFE_NO_PAD.encode(serde_json::to_string(&JwtHeader { + typ: JWT_HEADER_TYP, + alg: JWT_HEADER_ALG, + })?); let message = format!("{header}.{message}"); let signature = private_key.sign(message.as_bytes()); - let signature = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(signature.to_bytes()); + let signature = URL_SAFE_NO_PAD.encode(signature.to_bytes()); Ok(format!("{message}.{signature}")) } @@ -401,9 +390,9 @@ pub struct JwtMessage { pub iat: i64, // issued at pub exp: i64, // expiry // TODO: This was changed from notify pubkey, should be confirmed if we want to keep this - pub iss: String, // dapps identity key - pub act: String, // action intent (must be "notify_message") - pub sub: String, // did:pkh of blockchain account - pub app: String, // dapp domain url - pub msg: Notification, // message + pub iss: String, // dapps identity key + pub act: String, // action intent (must be "notify_message") + pub sub: String, // did:pkh of blockchain account + pub app: Arc, // dapp domain url + pub msg: Arc, // message } diff --git a/src/handlers/subscribe_topic.rs b/src/handlers/subscribe_topic.rs index b628dd83..6551d9aa 100644 --- a/src/handlers/subscribe_topic.rs +++ b/src/handlers/subscribe_topic.rs @@ -1,8 +1,13 @@ use { - crate::{error::Result, extractors::AuthedProjectId, state::AppState}, + crate::{ + error::Result, extractors::AuthedProjectId, model::helpers::upsert_project, state::AppState, + }, axum::{self, extract::State, response::IntoResponse, Json}, chacha20poly1305::aead::{rand_core::RngCore, OsRng}, - mongodb::{bson::doc, options::ReplaceOptions}, + hyper::StatusCode, + once_cell::sync::Lazy, + regex::Regex, + relay_rpc::domain::Topic, serde::{Deserialize, Serialize}, serde_json::json, std::sync::Arc, @@ -11,25 +16,16 @@ use { }; #[derive(Serialize, Deserialize, Debug)] -pub struct ProjectData { - #[serde(rename = "_id")] - pub id: String, - pub identity_keypair: Keypair, - pub signing_keypair: Keypair, +#[serde(rename_all = "camelCase")] +pub struct SubscribeTopicRequestData { pub app_domain: String, - pub topic: String, } -#[derive(Serialize, Deserialize, Debug)] -pub struct Keypair { - pub private_key: String, - pub public_key: String, -} - -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] -pub struct SubscribeTopicData { - app_domain: String, +pub struct SubscribeTopicResponseData { + authentication_key: String, + subscribe_key: String, } // TODO test idempotency @@ -38,50 +34,32 @@ pub struct SubscribeTopicData { pub async fn handler( State(state): State>, AuthedProjectId(project_id, _): AuthedProjectId, - Json(subscribe_topic_data): Json, + Json(subscribe_topic_data): Json, ) -> Result { // let _span = tracing::info_span!( // "subscribe_topic", project_id = %project_id, // ) // .entered(); - info!( - "Getting or generating keypair for project: {} and domain: {}", - project_id, subscribe_topic_data.app_domain - ); - let db = state.database.clone(); - - if let Some(project_data) = db - .collection::("project_data") - .find_one(doc! { "_id": project_id.clone()}, None) - .await? - .iter() - .next() - { - let signing_pubkey = project_data.signing_keypair.public_key.clone(); - let identity_pubkey = project_data.identity_keypair.public_key.clone(); - info!( - "Project already exists: {:?} with pubkey: {:?} and identity: {:?}", - project_data, signing_pubkey, identity_pubkey - ); - - if project_data.app_domain != subscribe_topic_data.app_domain { - info!("Updating app_domain for project: {}", project_id); - db.collection::("project_data") - .update_one( - doc! { "_id": project_id.clone()}, - doc! { "$set": { "app_domain": &subscribe_topic_data.app_domain } }, - None, - ) - .await?; - } - - return Ok(Json( - // TODO use struct - json!({ "authenticationKey": identity_pubkey, "subscribeKey": signing_pubkey}), + let app_domain = subscribe_topic_data.app_domain; + if app_domain.len() > 253 { + // Domains max at 253 chars according to: https://en.wikipedia.org/wiki/Hostname + // Conveniently, that fits into a varchar(255) column + return Ok(( + StatusCode::BAD_REQUEST, + Json(json!({"error":"app_domain exceeds 253 characters"})), ) - .into_response()); - }; + .into_response()); + } + if !is_domain(&app_domain) { + return Ok(( + StatusCode::BAD_REQUEST, + Json(json!({"error":"app_domain is not a valid domain"})), + ) + .into_response()); + } + + info!("Getting or generating keypair for project: {project_id} and domain: {app_domain}"); let mut rng = OsRng; @@ -91,51 +69,65 @@ pub async fn handler( signing_secret }); let signing_public = PublicKey::from(&signing_secret); - let topic = sha256::digest(signing_public.as_bytes()); + let topic: Topic = sha256::digest(signing_public.as_bytes()).into(); let signing_public = hex::encode(signing_public); + let signing_secret = hex::encode(signing_secret.to_bytes()); let identity_secret = ed25519_dalek::SigningKey::generate(&mut rng); let identity_public = hex::encode(ed25519_dalek::VerifyingKey::from(&identity_secret)); - - let project_data = ProjectData { - id: project_id.clone(), - signing_keypair: Keypair { - private_key: hex::encode(signing_secret.to_bytes()), - public_key: signing_public.clone(), - }, - identity_keypair: Keypair { - private_key: hex::encode(identity_secret.to_bytes()), - public_key: identity_public.clone(), - }, - app_domain: subscribe_topic_data.app_domain, - topic: topic.clone(), - }; + let identity_secret = hex::encode(identity_secret.to_bytes()); info!( - "Saving project_info to database for project: {} with signing pubkey: {} and identity \ - pubkey: {}, topic: {}", - project_id, signing_public, identity_public, topic + "Saving project_info to database for project: {project_id} and app_domain {app_domain} \ + with signing pubkey: {signing_public} and identity pubkey: {identity_public}, topic: \ + {topic}" ); - // FIXME race condition - // INSERT INTO project_data (id, keys) VALUES ($ID, $key) - // ON CONFLICT (id) DO NOTHING - // RETURNING keys; - db.collection::("project_data") - .replace_one( - doc! { "_id": project_id.clone()}, - project_data, - ReplaceOptions::builder().upsert(true).build(), - ) - .await?; - - info!("Subscribing to project topic: {}", &topic); + let project = upsert_project( + project_id, + &app_domain, + topic.clone(), + identity_public, + identity_secret, + signing_public, + signing_secret, + &state.postgres, + ) + .await?; + // TODO handle duplicate app_domain error - state.wsclient.subscribe(topic.into()).await?; + info!("Subscribing to project topic: {topic}"); + state.wsclient.subscribe(topic).await?; - Ok(Json( - // TODO use struct - json!({ "authenticationKey": identity_public, "subscribeKey": signing_public}), - ) + Ok(Json(SubscribeTopicResponseData { + authentication_key: project.authentication_public_key, + subscribe_key: project.subscribe_public_key, + }) .into_response()) } + +fn is_domain(domain: &str) -> bool { + static DOMAIN_REGEX: Lazy = Lazy::new(|| Regex::new(r"^[a-z0-9-_\.]+$").unwrap()); + DOMAIN_REGEX.is_match(domain) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn valid_domains() { + assert!(is_domain("com")); + assert!(is_domain("example.com")); + assert!(is_domain("app.example.com")); + assert!(is_domain("123.example.com")); + } + + #[test] + fn not_valid_domains() { + assert!(!is_domain("https://app.example.com")); + assert!(!is_domain("app.example.com/")); + assert!(!is_domain(" app.example.com")); + assert!(!is_domain("app.example.com ")); + } +} diff --git a/src/handlers/webhooks/mod.rs b/src/handlers/webhooks/mod.rs index 28ab0269..94dcc8c0 100644 --- a/src/handlers/webhooks/mod.rs +++ b/src/handlers/webhooks/mod.rs @@ -1,12 +1,13 @@ use { - crate::{error::Result, state::WebhookNotificationEvent}, + crate::state::WebhookNotificationEvent, serde::{Deserialize, Serialize}, }; -pub mod delete_webhook; -pub mod get_webhooks; -pub mod register_webhook; -pub mod update_webhook; +// FIXME +// pub mod delete_webhook; +// pub mod get_webhooks; +// pub mod register_webhook; +// pub mod update_webhook; #[derive(Debug, Deserialize, Serialize)] pub struct WebhookConfig { @@ -15,10 +16,10 @@ pub struct WebhookConfig { events: Vec, } -fn validate_url(url: &str) -> Result<()> { - let url = url::Url::parse(url)?; - if url.scheme() != "https" { - return Err(crate::error::Error::InvalidScheme); - } - Ok(()) -} +// fn validate_url(url: &str) -> Result<()> { +// let url = url::Url::parse(url)?; +// if url.scheme() != "https" { +// return Err(crate::error::Error::InvalidScheme); +// } +// Ok(()) +// } diff --git a/src/lib.rs b/src/lib.rs index fbbdc5e1..aa0f244b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,12 +10,13 @@ use { aws_sdk_s3::{config::Region, Client as S3Client}, axum::{ http, - routing::{delete, get, post, put}, + routing::{get, post}, Router, }, mongodb::options::{ClientOptions, ResolverConfig}, rand::prelude::*, relay_rpc::auth::ed25519_dalek::Keypair, + sqlx::postgres::PgPoolOptions, std::{net::SocketAddr, sync::Arc}, tokio::{select, sync::broadcast}, tower::ServiceBuilder, @@ -38,6 +39,8 @@ pub mod extractors; pub mod handlers; pub mod jsonrpc; mod metrics; +pub mod migrate; +pub mod model; mod networking; mod notify_keys; pub mod registry; @@ -61,20 +64,22 @@ pub async fn bootstrap(mut shutdown: broadcast::Receiver<()>, config: Configurat let analytics = analytics::initialize(&config, s3_client, geoip_resolver.clone()).await?; - // A Client is needed to connect to MongoDB: - // An extra line of code to work around a DNS issue on Windows: - let options = ClientOptions::parse_with_resolver_config( - &config.database_url, - ResolverConfig::cloudflare(), - ) - .await?; - - let db = Arc::new( - mongodb::Client::with_options(options) - .unwrap() - .database("notify"), + let mongodb = Arc::new( + mongodb::Client::with_options( + ClientOptions::parse_with_resolver_config( + &config.database_url, + ResolverConfig::cloudflare(), + ) + .await?, + ) + .unwrap() + .database("notify"), ); + let postgres = PgPoolOptions::new().connect(&config.postgres_url).await?; + sqlx::migrate!("./migrations").run(&postgres).await?; + migrate::migrate(mongodb.as_ref(), &postgres).await?; + let seed = sha256::digest(config.keypair_seed.as_bytes()).as_bytes()[..32] .try_into() .map_err(|_| error::Error::InvalidKeypairSeed)?; @@ -102,7 +107,7 @@ pub async fn bootstrap(mut shutdown: broadcast::Receiver<()>, config: Configurat let state = AppState::new( analytics, config, - db, + postgres, keypair, wsclient.clone(), http_client, @@ -137,22 +142,23 @@ pub async fn bootstrap(mut shutdown: broadcast::Receiver<()>, config: Configurat "/:project_id/subscribe-topic", post(handlers::subscribe_topic::handler), ) - .route( - "/:project_id/register-webhook", - post(handlers::webhooks::register_webhook::handler), - ) - .route( - "/:project_id/webhooks", - get(handlers::webhooks::get_webhooks::handler), - ) - .route( - "/:project_id/webhooks/:webhook_id", - delete(handlers::webhooks::delete_webhook::handler), - ) - .route( - "/:project_id/webhooks/:webhook_id", - put(handlers::webhooks::update_webhook::handler), - ) + // FIXME + // .route( + // "/:project_id/register-webhook", + // post(handlers::webhooks::register_webhook::handler), + // ) + // .route( + // "/:project_id/webhooks", + // get(handlers::webhooks::get_webhooks::handler), + // ) + // .route( + // "/:project_id/webhooks/:webhook_id", + // delete(handlers::webhooks::delete_webhook::handler), + // ) + // .route( + // "/:project_id/webhooks/:webhook_id", + // put(handlers::webhooks::update_webhook::handler), + // ) .route( "/:project_id/subscribers", get(handlers::get_subscribers::handler), diff --git a/src/migrate.rs b/src/migrate.rs new file mode 100644 index 00000000..9b027751 --- /dev/null +++ b/src/migrate.rs @@ -0,0 +1,110 @@ +use { + crate::{ + model::helpers::{get_project_by_project_id, upsert_project, upsert_subscriber}, + Result, + }, + mongodb::bson::doc, + serde::{Deserialize, Serialize}, + std::collections::HashSet, +}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct ProjectData { + #[serde(rename = "_id")] + pub id: String, + pub identity_keypair: Keypair, + pub signing_keypair: Keypair, + pub app_domain: String, + pub topic: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Keypair { + pub private_key: String, + pub public_key: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ClientData { + #[serde(rename = "_id")] + pub id: String, + pub relay_url: String, + pub sym_key: String, + pub expiry: u64, + pub scope: HashSet, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct LookupEntry { + #[serde(rename = "_id")] + pub topic: String, + pub project_id: String, + pub account: String, + pub expiry: u64, +} + +pub async fn migrate(mongo: &mongodb::Database, postgres: &sqlx::PgPool) -> Result<()> { + let mut projects_cursor = mongo + .collection::("project_data") + .find(None, None) + .await?; + + while projects_cursor.advance().await? { + let project = projects_cursor.deserialize_current()?; + + upsert_project( + project.id.clone().into(), + &project.app_domain, + project.topic.into(), + project.identity_keypair.public_key, + project.identity_keypair.private_key, + project.signing_keypair.public_key, + project.signing_keypair.private_key, + postgres, + ) + .await?; + + mongo + .collection::("project_data") + .delete_one(doc! {"_id": project.id}, None) + .await?; + } + + let mut lookup_entry_cursor = mongo + .collection::("lookup_table") + .find(None, None) + .await?; + + while lookup_entry_cursor.advance().await? { + let lookup_entry = lookup_entry_cursor.deserialize_current()?; + let client_data = mongo + .collection::(&lookup_entry.project_id) + .find_one(doc! {"_id": lookup_entry.account.clone()}, None) + .await? + .unwrap(); + + let project = + get_project_by_project_id(lookup_entry.project_id.clone().into(), postgres).await?; + + upsert_subscriber( + project.id, + client_data.id.into(), + client_data.scope, + &hex::decode(&client_data.sym_key)?.try_into().unwrap(), + lookup_entry.topic.clone().into(), + postgres, + ) + .await?; + + mongo + .collection::("lookup_table") + .delete_one(doc! {"_id": lookup_entry.topic}, None) + .await?; + mongo + .collection::(&lookup_entry.project_id) + .delete_one(doc! {"_id": lookup_entry.account}, None) + .await?; + } + + Ok(()) +} diff --git a/src/model/helpers.rs b/src/model/helpers.rs new file mode 100644 index 00000000..384c8092 --- /dev/null +++ b/src/model/helpers.rs @@ -0,0 +1,496 @@ +use { + super::types::{Project, Subscriber}, + crate::model::types::AccountId, + chrono::{DateTime, Utc}, + relay_rpc::domain::{ProjectId, Topic}, + sqlx::{FromRow, PgPool, Postgres}, + std::collections::HashSet, + tracing::instrument, + uuid::Uuid, +}; + +#[derive(Debug, FromRow)] +pub struct ProjectWithPublicKeys { + pub authentication_public_key: String, + pub subscribe_public_key: String, +} + +// TODO test idempotency +#[allow(clippy::too_many_arguments)] +#[instrument(skip(postgres))] +pub async fn upsert_project( + project_id: ProjectId, + app_domain: &str, + topic: Topic, + identity_public: String, + identity_secret: String, + signing_public: String, + signing_secret: String, + postgres: &PgPool, +) -> Result { + let query = " + INSERT INTO project ( + project_id, + app_domain, + topic, + authentication_public_key, + authentication_private_key, + subscribe_public_key, + subscribe_private_key + ) + VALUES ($1, $2, $3, $4, $5, $6, $7) + ON CONFLICT (project_id) DO UPDATE SET + app_domain=$2, + updated_at=now() + RETURNING authentication_public_key, subscribe_public_key + "; + sqlx::query_as::(query) + .bind(project_id.as_ref()) + .bind(app_domain) + .bind(topic.as_ref()) + .bind(identity_public) + .bind(identity_secret) + .bind(signing_public) + .bind(signing_secret) + .fetch_one(postgres) + .await +} + +#[instrument(skip(postgres))] +pub async fn get_project_by_id(id: Uuid, postgres: &PgPool) -> Result { + let query = " + SELECT * + FROM project + WHERE id=$1 + "; + sqlx::query_as::(query) + .bind(id) + .fetch_one(postgres) + .await +} + +#[instrument(skip(postgres))] +pub async fn get_project_by_project_id( + project_id: ProjectId, + postgres: &PgPool, +) -> Result { + let query = " + SELECT * + FROM project + WHERE project_id=$1 + "; + sqlx::query_as::(query) + .bind(project_id.as_ref()) + .fetch_one(postgres) + .await +} + +#[instrument(skip(postgres))] +pub async fn get_project_by_app_domain( + app_domain: &str, + postgres: &PgPool, +) -> Result { + let query = " + SELECT * + FROM project + WHERE app_domain=$1 + "; + sqlx::query_as::(query) + .bind(app_domain) + .fetch_one(postgres) + .await +} + +#[instrument(skip(postgres))] +pub async fn get_project_by_topic( + topic: Topic, + postgres: &PgPool, +) -> Result { + let query = " + SELECT * + FROM project + WHERE topic=$1 + "; + sqlx::query_as::(query) + .bind(topic.as_ref()) + .fetch_one(postgres) + .await +} + +#[instrument(skip(postgres))] +pub async fn get_subscriber_accounts_by_project_id( + project_id: ProjectId, + postgres: &PgPool, +) -> Result, sqlx::error::Error> { + #[derive(Debug, FromRow)] + struct ProjectWithAccount { + #[sqlx(try_from = "String")] + account: AccountId, + } + let query = " + SELECT account + FROM subscriber + JOIN project ON project.id=subscriber.project + WHERE project.project_id=$1 + "; + let projects = sqlx::query_as::(query) + .bind(project_id.as_ref()) + .fetch_all(postgres) + .await?; + Ok(projects.into_iter().map(|p| p.account).collect()) +} + +#[instrument(skip(postgres))] +pub async fn get_subscriber_topics(postgres: &PgPool) -> Result, sqlx::error::Error> { + #[derive(Debug, FromRow)] + struct SubscriberWithTopic { + #[sqlx(try_from = "String")] + topic: Topic, + } + let query = " + SELECT topic + FROM subscriber + "; + let subscribers = sqlx::query_as::(query) + .fetch_all(postgres) + .await?; + Ok(subscribers.into_iter().map(|p| p.topic).collect()) +} + +#[instrument(skip(postgres))] +pub async fn get_project_topics(postgres: &PgPool) -> Result, sqlx::error::Error> { + #[derive(Debug, FromRow)] + struct ProjectWithTopic { + #[sqlx(try_from = "String")] + topic: Topic, + } + let query = " + SELECT topic + FROM project + "; + let projects = sqlx::query_as::(query) + .fetch_all(postgres) + .await?; + Ok(projects.into_iter().map(|p| p.topic).collect()) +} + +// TODO test idempotency +#[instrument(skip(postgres))] +pub async fn upsert_subscriber( + project: Uuid, + account: AccountId, + scope: HashSet, + notify_key: &[u8; 32], + notify_topic: Topic, + postgres: &PgPool, +) -> Result { + let mut txn = postgres.begin().await?; + + #[derive(Debug, FromRow)] + struct SubscriberWithId { + id: Uuid, + } + let query = " + INSERT INTO subscriber ( + project, + account, + sym_key, + topic, + expiry + ) + VALUES ($1, $2, $3, $4, $5) + ON CONFLICT (project, account) DO UPDATE SET + sym_key=$3, + topic=$4, + expiry=$5, + updated_at=now() + RETURNING id + "; + let subscriber = sqlx::query_as::(query) + .bind(project) + .bind(account.as_ref()) + .bind(hex::encode(notify_key)) + .bind(notify_topic.as_ref()) + .bind(Utc::now() + chrono::Duration::days(30)) + .fetch_one(&mut *txn) + .await?; + + update_subscriber_scope(subscriber.id, scope, &mut txn).await?; + + txn.commit().await?; + + Ok(subscriber.id) +} + +// TODO test idempotency +#[instrument(skip(postgres))] +pub async fn update_subscriber( + project: Uuid, + account: AccountId, + scope: HashSet, + postgres: &PgPool, +) -> Result { + let mut txn = postgres.begin().await?; + + let query = " + UPDATE subscriber + SET expiry=$1, + updated_at=now() + WHERE project=$2 AND account=$3 + RETURNING * + "; + let updated_subscriber = sqlx::query_as::<_, Subscriber>(query) + .bind(Utc::now() + chrono::Duration::days(30)) + .bind(project) + .bind(account.as_ref()) + .fetch_one(&mut *txn) + .await?; + + update_subscriber_scope(updated_subscriber.id, scope, &mut txn).await?; + + txn.commit().await?; + + Ok(updated_subscriber) +} + +async fn update_subscriber_scope( + subscriber: Uuid, + scope: HashSet, + txn: &mut sqlx::Transaction<'_, Postgres>, +) -> Result<(), sqlx::error::Error> { + let query = " + DELETE FROM subscriber_scope + WHERE subscriber=$1 + "; + sqlx::query(query) + .bind(subscriber) + .execute(&mut **txn) + .await?; + + let query = " + INSERT INTO subscriber_scope ( + subscriber, + name + ) SELECT $1 AS subscriber, name FROM UNNEST($2) AS name; + "; + let _ = sqlx::query::(query) + .bind(subscriber) + .bind(scope.into_iter().collect::>()) + .execute(&mut **txn) + .await?; + + Ok(()) +} + +#[instrument(skip(postgres))] +pub async fn delete_subscriber( + subscriber: Uuid, + postgres: &PgPool, +) -> Result<(), sqlx::error::Error> { + let query = " + DELETE FROM subscriber + WHERE id=$1 + "; + let _ = sqlx::query::(query) + .bind(subscriber) + .execute(postgres) + .await?; + Ok(()) +} + +#[derive(FromRow)] +pub struct SubscriberWithScope { + pub id: Uuid, + pub project: Uuid, + #[sqlx(try_from = "String")] + pub account: AccountId, + pub sym_key: String, + #[sqlx(try_from = "String")] + pub topic: Topic, + pub scope: Vec, + pub expiry: DateTime, +} + +#[instrument(skip(postgres))] +pub async fn get_subscriber_by_topic( + topic: Topic, + postgres: &PgPool, +) -> Result { + let query = " + SELECT subscriber.id, project, account, sym_key, array_agg(subscriber_scope.name) as \ + scope, topic, expiry + FROM subscriber + JOIN subscriber_scope ON subscriber_scope.subscriber=subscriber.id + WHERE topic=$1 + GROUP BY subscriber.id, project, account, sym_key, topic, expiry + "; + sqlx::query_as::(query) + .bind(topic.as_ref()) + .fetch_one(postgres) + .await +} + +// TODO this doesn't need to return a full subscriber +#[instrument(skip(postgres))] +pub async fn get_subscribers_for_project_in( + project: Uuid, + accounts: &[AccountId], + postgres: &PgPool, +) -> Result, sqlx::error::Error> { + let query = " + SELECT subscriber.id, project, account, sym_key, array_agg(subscriber_scope.name) as \ + scope, topic, expiry + FROM subscriber + JOIN subscriber_scope ON subscriber_scope.subscriber=subscriber.id + WHERE project=$1 AND account = ANY($2) + GROUP BY subscriber.id, project, account, sym_key, topic, expiry + "; + sqlx::query_as::(query) + .bind(project) + .bind(accounts.iter().map(|a| a.as_ref()).collect::>()) + .fetch_all(postgres) + .await +} + +#[derive(FromRow)] +pub struct SubscriberWithProject { + /// dApp url that the subscription refers to + pub app_domain: String, + /// CAIP-10 account + #[sqlx(try_from = "String")] + pub account: AccountId, // TODO do we need to return this? + /// Symetric key used for notify topic. sha256 to get notify topic to manage + /// the subscription and call wc_notifySubscriptionUpdate and + /// wc_notifySubscriptionDelete + pub sym_key: String, + /// Array of notification types enabled for this subscription + pub scope: Vec, + /// Unix timestamp of expiration + pub expiry: DateTime, +} + +// TODO this doesn't need to return a full subscriber (especially not scopes) +#[instrument(skip(postgres))] +pub async fn get_subscriptions_by_account( + account: AccountId, + postgres: &PgPool, +) -> Result, sqlx::error::Error> { + let query: &str = " + SELECT app_domain, account, sym_key, array_agg(subscriber_scope.name) as scope, expiry + FROM subscriber + JOIN project ON project.id=subscriber.project + JOIN subscriber_scope ON subscriber_scope.subscriber=subscriber.id + WHERE account=$1 + GROUP BY app_domain, account, sym_key, expiry + "; + sqlx::query_as::(query) + .bind(account.as_ref()) + .fetch_all(postgres) + .await +} + +// TODO this doesn't need to return a full subscriber (especially not scopes) +#[instrument(skip(postgres))] +pub async fn get_subscriptions_by_account_and_app( + account: AccountId, + app_domain: &str, + postgres: &PgPool, +) -> Result, sqlx::error::Error> { + let query: &str = " + SELECT app_domain, sym_key, account, array_agg(subscriber_scope.name) as scope, expiry + FROM subscriber + JOIN project ON project.id=subscriber.project + JOIN subscriber_scope ON subscriber_scope.subscriber=subscriber.id + WHERE account=$1 AND project.app_domain=$2 + GROUP BY app_domain, sym_key, account, expiry + "; + sqlx::query_as::(query) + .bind(account.as_ref()) + .bind(app_domain) + .fetch_all(postgres) + .await +} + +#[instrument(skip(postgres))] +pub async fn upsert_subscription_watcher( + account: AccountId, + project: Option, + did_key: &str, + sym_key: &str, + expiry: DateTime, + postgres: &PgPool, +) -> Result<(), sqlx::error::Error> { + let _ = sqlx::query::( + " + INSERT INTO subscription_watcher ( + account, + project, + did_key, + sym_key, + expiry + ) + VALUES ($1, $2, $3, $4, $5) + ON CONFLICT (did_key) DO UPDATE SET + sym_key=$4, + expiry=$5, + updated_at=now() + ", + ) + .bind(account.as_ref()) + .bind(project) + .bind(did_key) + .bind(sym_key) + .bind(expiry) + .execute(postgres) + .await?; + + Ok(()) +} + +#[derive(Debug, FromRow)] +pub struct SubscriptionWatcherQuery { + pub project: Option, + pub did_key: String, + pub sym_key: String, +} + +#[instrument(skip(postgres))] +pub async fn get_subscription_watchers_for_account_by_app_or_all_app( + account: AccountId, + app_domain: &str, + postgres: &PgPool, +) -> Result, sqlx::error::Error> { + let query = " + SELECT project, did_key, sym_key + FROM subscription_watcher + JOIN project ON project.id=subscription_watcher.project + WHERE account=$1 AND (project IS NULL OR project.app_domain=$2) + "; + sqlx::query_as::(query) + .bind(account.as_ref()) + .bind(app_domain) + .fetch_all(postgres) + .await +} + +#[instrument(skip(postgres))] +pub async fn delete_expired_subscription_watchers( + postgres: &PgPool, +) -> Result { + #[derive(Debug, FromRow)] + struct DeleteResult { + count: i64, + } + let query = " + WITH deleted AS ( + DELETE FROM subscription_watcher + WHERE expiry < now() + RETURNING * + ) + SELECT count(*) FROM deleted + "; + let result = sqlx::query_as::(query) + .fetch_one(postgres) + .await?; + Ok(result.count) +} diff --git a/src/model/mod.rs b/src/model/mod.rs new file mode 100644 index 00000000..52af0f9c --- /dev/null +++ b/src/model/mod.rs @@ -0,0 +1,2 @@ +pub mod helpers; +pub mod types; diff --git a/src/model/types.rs b/src/model/types.rs new file mode 100644 index 00000000..824580b7 --- /dev/null +++ b/src/model/types.rs @@ -0,0 +1,56 @@ +use { + chrono::{DateTime, Utc}, + relay_rpc::{ + domain::{ProjectId, Topic}, + new_type, + }, + sqlx::FromRow, + std::sync::Arc, + uuid::Uuid, +}; + +// See /migrations/ERD.md + +new_type!( + #[doc = "Represents a CAIP-10 account ID."] + #[as_ref(forward)] + #[from(forward)] + AccountId: Arc +); + +#[derive(Debug, FromRow)] +pub struct Project { + pub id: Uuid, + #[sqlx(try_from = "String")] + pub project_id: ProjectId, + pub app_domain: String, + #[sqlx(try_from = "String")] + pub topic: Topic, + pub authentication_public_key: String, + pub authentication_private_key: String, + pub subscribe_public_key: String, + pub subscribe_private_key: String, +} + +#[derive(Debug, FromRow)] +pub struct Subscriber { + pub id: Uuid, + pub project: Uuid, + /// CAIP-10 account + #[sqlx(try_from = "String")] + pub account: AccountId, + pub sym_key: String, + #[sqlx(try_from = "String")] + pub topic: Topic, + pub expiry: DateTime, +} + +#[derive(Debug)] +pub struct SubscriptionWatcher { + pub account: AccountId, + /// Project the watcher is authorized for. None for all. + pub project: Option, + pub did_key: String, + pub sym_key: String, + pub expiry: DateTime, +} diff --git a/src/registry.rs b/src/registry.rs index cbaa0fbf..3ffe1057 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -5,6 +5,7 @@ use { storage::{redis::Redis, KeyValueStorage}, }, hyper::header, + relay_rpc::domain::ProjectId, serde::{Deserialize, Serialize}, sha2::{Digest, Sha256}, std::{sync::Arc, time::Duration}, @@ -90,8 +91,8 @@ impl Registry { Ok(Self { client, cache }) } - pub async fn is_authenticated(&self, id: &str, secret: &str) -> Result { - self.is_authenticated_internal(id, secret) + pub async fn is_authenticated(&self, project_id: ProjectId, secret: &str) -> Result { + self.is_authenticated_internal(project_id, secret) .await .map_err(|e| { error!("Failed to authenticate project: {}", e); @@ -99,9 +100,9 @@ impl Registry { }) } - async fn is_authenticated_internal(&self, id: &str, secret: &str) -> Result { + async fn is_authenticated_internal(&self, project_id: ProjectId, secret: &str) -> Result { let mut hasher = Sha256::new(); - hasher.update(id); + hasher.update(project_id.as_ref()); hasher.update(secret); let hash = hasher.finalize(); let hash = hex::encode(hash); @@ -112,7 +113,11 @@ impl Registry { } } - let validity = self.client.authenticate(id, secret).await?.is_success(); + let validity = self + .client + .authenticate(project_id.as_ref(), secret) + .await? + .is_success(); if let Some(cache) = &self.cache { cache.set(&hash, &validity, Some(CACHE_TTL)).await?; diff --git a/src/state.rs b/src/state.rs index 605368bb..7dd4ee70 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,24 +1,14 @@ use { crate::{ - analytics::NotifyAnalytics, - error::Result, - metrics::Metrics, - notify_keys::NotifyKeys, - registry::Registry, - types::{ClientData, LookupEntry, WebhookInfo}, - Configuration, + analytics::NotifyAnalytics, error::Result, metrics::Metrics, notify_keys::NotifyKeys, + registry::Registry, Configuration, }, build_info::BuildInfo, - futures::TryStreamExt, - mongodb::{ - bson::doc, - options::{DeleteOptions, InsertOneOptions, ReplaceOptions}, - }, relay_rpc::auth::ed25519_dalek::Keypair, serde::{Deserialize, Serialize}, + sqlx::PgPool, std::{fmt, sync::Arc}, tracing::info, - url::Url, }; pub struct AppState { @@ -26,7 +16,7 @@ pub struct AppState { pub analytics: NotifyAnalytics, pub build_info: BuildInfo, pub metrics: Option, - pub database: Arc, + pub postgres: PgPool, pub keypair: Keypair, pub wsclient: Arc, pub http_relay_client: Arc, @@ -41,23 +31,23 @@ impl AppState { pub fn new( analytics: NotifyAnalytics, config: Configuration, - database: Arc, + postgres: PgPool, keypair: Keypair, wsclient: Arc, http_relay_client: Arc, metrics: Option, registry: Arc, - ) -> crate::Result { + ) -> crate::Result { let build_info: &BuildInfo = build_info(); let notify_keys = NotifyKeys::new(&config.notify_url, &config.keypair_seed)?; - Ok(AppState { + Ok(Self { analytics, config, build_info: build_info.clone(), metrics, - database, + postgres, keypair, wsclient, http_relay_client, @@ -66,77 +56,6 @@ impl AppState { }) } - pub async fn register_client( - &self, - project_id: &str, - client_data: ClientData, - url: &Url, - ) -> Result<()> { - let key = hex::decode(client_data.sym_key.clone())?; - let topic = sha256::digest(&*key); - - let insert_data = ClientData { - id: client_data.id.clone(), - relay_url: url.to_string().trim_end_matches('/').to_string(), /* TODO test trim_end_matches('/') */ - sym_key: client_data.sym_key.clone(), - scope: client_data.scope.clone(), - ..client_data - }; - - self.database - .collection::(project_id) - .replace_one( - doc! { "_id": client_data.id.clone()}, - insert_data, - ReplaceOptions::builder().upsert(true).build(), - ) - .await?; - - self.database - .collection::("lookup_table") - .delete_one( - doc! { - // Don't query by `_id: topic` to avoid duplicate topics for the same account. See https://github.com/WalletConnect/notify-server/issues/26 - "account": client_data.id.clone(), - "project_id": &project_id.to_string(), - }, - DeleteOptions::builder().build(), - ) - .await?; - - self.database - .collection::("lookup_table") - .insert_one( - LookupEntry { - topic: topic.clone(), - project_id: project_id.to_string(), - account: client_data.id.clone(), - expiry: client_data.expiry, - }, - InsertOneOptions::builder().build(), - ) - .await?; - - self.analytics - .client(crate::analytics::client_info::ClientInfo { - project_id: project_id.into(), - account: client_data.id.clone().into(), - topic: topic.clone().into(), - registered_at: wc::analytics::time::now(), - }); - - self.wsclient.subscribe(topic.into()).await?; - - self.notify_webhook( - project_id, - WebhookNotificationEvent::Subscribed, - &client_data.id, - ) - .await?; - - Ok(()) - } - pub async fn notify_webhook( &self, project_id: &str, @@ -152,36 +71,37 @@ impl AppState { "Triggering webhook for project: {}, with account: {} and event \"{}\"", project_id, account, event ); - let mut cursor = self - .database - .collection::("webhooks") - .find(doc! { "project_id": project_id}, None) - .await?; - - let client = reqwest::Client::new(); - - // Interate over cursor - while let Some(webhook) = cursor.try_next().await? { - if !webhook.events.contains(&event) { - continue; - } - - let res = client - .post(&webhook.url) - .json(&WebhookMessage { - id: webhook.id.clone(), - event, - account: account.to_string(), - }) - .send() - .await?; - - info!( - "Triggering webhook: {} resulted in http status: {}", - webhook.id, - res.status() - ); - } + // TODO + // let mut cursor = self + // .database + // .collection::("webhooks") + // .find(doc! { "project_id": project_id}, None) + // .await?; + + // let client = reqwest::Client::new(); + + // // Interate over cursor + // while let Some(webhook) = cursor.try_next().await? { + // if !webhook.events.contains(&event) { + // continue; + // } + + // let res = client + // .post(&webhook.url) + // .json(&WebhookMessage { + // id: webhook.id.clone(), + // event, + // account: account.to_string(), + // }) + // .send() + // .await?; + + // info!( + // "Triggering webhook: {} resulted in http status: {}", + // webhook.id, + // res.status() + // ); + // } Ok(()) } diff --git a/src/types.rs b/src/types.rs index 78acefbf..71c9c6c6 100644 --- a/src/types.rs +++ b/src/types.rs @@ -4,9 +4,9 @@ use { rand::{distributions::Uniform, prelude::Distribution, rngs::OsRng}, serde::{Deserialize, Serialize}, sha2::digest::generic_array::GenericArray, - std::collections::HashSet, }; +// TODO move to Postgres #[derive(Serialize, Deserialize, Debug)] pub struct WebhookInfo { pub id: String, @@ -15,46 +15,6 @@ pub struct WebhookInfo { pub project_id: String, } -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct RegisterBody { - pub account: String, - pub relay_url: String, - pub sym_key: String, - pub subscription_auth: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct ClientData { - #[serde(rename = "_id")] - pub id: String, - pub relay_url: String, // TODO remove this, it's not read anywhere? - pub sym_key: String, - pub expiry: u64, - pub scope: HashSet, // TODO rename scope to type? -} - -// TODO purpose of lookup_table is to enable indexing on `topic`, but indexes -// can be made on any field -#[derive(Debug, Serialize, Deserialize)] -pub struct LookupEntry { - #[serde(rename = "_id")] - pub topic: String, - pub project_id: String, - pub account: String, - pub expiry: u64, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct WatchSubscriptionsEntry { - pub account: String, - /// App the watcher is authorized for. None for all. - pub app_domain: Option, - pub sym_key: String, - pub did_key: String, - pub expiry: u64, -} - #[derive(Debug)] pub struct Envelope { pub envelope_type: u8, diff --git a/src/watcher_expiration.rs b/src/watcher_expiration.rs index 2124294d..c3e88fd4 100644 --- a/src/watcher_expiration.rs +++ b/src/watcher_expiration.rs @@ -1,7 +1,5 @@ use { - crate::{state::AppState, types::WatchSubscriptionsEntry}, - chrono::Utc, - mongodb::bson::doc, + crate::{model::helpers::delete_expired_subscription_watchers, state::AppState}, std::{sync::Arc, time::Duration}, tracing::{info, warn}, }; @@ -18,14 +16,8 @@ pub async fn watcher_expiration_job(state: Arc) { } } -async fn job(state: &AppState) -> mongodb::error::Result<()> { - let now = Utc::now().timestamp(); - let delete_result = state - .database - .collection::("watch_subscriptions") - .delete_many(doc! { "expiry": { "$lte": now } }, None) - .await?; - let count = delete_result.deleted_count; +async fn job(state: &AppState) -> sqlx::Result<()> { + let count = delete_expired_subscription_watchers(&state.postgres).await?; info!("expired watchers: {count}"); Ok(()) } diff --git a/src/websocket_service/handlers/notify_delete.rs b/src/websocket_service/handlers/notify_delete.rs index a34d28f8..cc4945c9 100644 --- a/src/websocket_service/handlers/notify_delete.rs +++ b/src/websocket_service/handlers/notify_delete.rs @@ -1,14 +1,15 @@ use { crate::{ + analytics::notify_client::{NotifyClientMethod, NotifyClientParams}, auth::{ add_ttl, from_jwt, sign_jwt, verify_identity, AuthError, Authorization, AuthorizedApp, SharedClaims, SubscriptionDeleteRequestAuth, SubscriptionDeleteResponseAuth, }, error::Error, - handlers::subscribe_topic::ProjectData, + model::helpers::{delete_subscriber, get_project_by_id, get_subscriber_by_topic}, spec::{NOTIFY_DELETE_RESPONSE_TAG, NOTIFY_DELETE_RESPONSE_TTL}, state::{AppState, WebhookNotificationEvent}, - types::{ClientData, Envelope, EnvelopeType0, LookupEntry}, + types::{Envelope, EnvelopeType0}, websocket_service::{ decode_key, handlers::{decrypt_message, notify_watch_subscriptions::update_subscription_watchers}, @@ -19,11 +20,10 @@ use { anyhow::anyhow, base64::Engine, chrono::Utc, - mongodb::bson::doc, relay_rpc::domain::DecodedClientId, serde_json::{json, Value}, std::sync::Arc, - tracing::{info, warn}, + tracing::warn, }; // TODO make and test idempotency @@ -32,39 +32,17 @@ pub async fn handle( state: &Arc, client: &Arc, ) -> Result<()> { - let request_id = uuid::Uuid::new_v4(); let topic = msg.topic; - let database = &state.database; let subscription_id = msg.subscription_id; - let Ok(Some(LookupEntry { - project_id, - account, - .. - })) = database - .collection::("lookup_table") - .find_one_and_delete(doc! {"_id": &topic.to_string() }, None) + // TODO combine these two SQL queries + let subscriber = get_subscriber_by_topic(topic.clone(), &state.postgres) .await - else { - return Err(Error::NoProjectDataForTopic(topic.to_string())); - }; - - let project_data = state - .database - .collection::("project_data") - .find_one(doc!("_id": project_id.clone()), None) - .await? - .ok_or(crate::error::Error::NoProjectDataForTopic( - topic.to_string(), - ))?; - - let Ok(Some(client_data)) = database - .collection::(&project_id) - .find_one_and_delete(doc! {"_id": &account }, None) - .await - else { - return Err(Error::NoClientDataForTopic(topic.to_string())); - }; + .map_err(|e| match e { + sqlx::Error::RowNotFound => Error::NoClientDataForTopic(topic.clone()), + e => e.into(), + })?; + let project = get_project_by_id(subscriber.project, &state.postgres).await?; let Ok(message_bytes) = base64::engine::general_purpose::STANDARD.decode(msg.message.to_string()) @@ -74,17 +52,16 @@ pub async fn handle( let envelope = Envelope::::from_bytes(message_bytes)?; - let sym_key = decode_key(&client_data.sym_key)?; + let sym_key = decode_key(&subscriber.sym_key)?; let msg: NotifyRequest = decrypt_message(envelope, &sym_key)?; - // TODO move above find_one_and_delete() let sub_auth = from_jwt::(&msg.params.delete_auth)?; if sub_auth .app .strip_prefix("did:web:") .ok_or(Error::AppNotDidWeb)? - != project_data.app_domain + != project.app_domain { Err(Error::AppDoesNotMatch)?; } @@ -100,7 +77,7 @@ pub async fn handle( // TODO verify `sub_auth.aud` matches `project_data.identity_keypair` if let AuthorizedApp::Limited(app) = app { - if app != project_data.app_domain { + if app != project.app_domain { Err(Error::AppSubscriptionsUnauthorized)?; } } @@ -108,26 +85,33 @@ pub async fn handle( account }; - info!( - "[{request_id}] Unregistered {} from {} with reason {}", - account, project_id, sub_auth.sub, - ); - if let Err(e) = client.unsubscribe(topic.clone(), subscription_id).await { - warn!( - "[{request_id}] Error unsubscribing Notify from topic: {}", - e - ); - }; + delete_subscriber(subscriber.id, &state.postgres).await?; + // TODO do in same txn as delete_subscriber() state .notify_webhook( - &project_id, + project.project_id.as_ref(), WebhookNotificationEvent::Unsubscribed, - &account, + subscriber.account.as_ref(), ) .await?; - let identity = DecodedClientId(decode_key(&project_data.identity_keypair.public_key)?); + if let Err(e) = client.unsubscribe(topic.clone(), subscription_id).await { + warn!("Error unsubscribing Notify from topic: {}", e); + }; + + state.analytics.client(NotifyClientParams { + pk: subscriber.id.to_string(), + method: NotifyClientMethod::Unsubscribe, + project_id: project.id.to_string(), + account: account.to_string(), + topic: topic.to_string(), + notify_topic: subscriber.topic.to_string(), + old_scope: subscriber.scope.join(","), + new_scope: "".to_owned(), + }); + + let identity = DecodedClientId(decode_key(&project.authentication_public_key)?); let now = Utc::now(); let response_message = SubscriptionDeleteResponseAuth { @@ -139,13 +123,11 @@ pub async fn handle( act: "notify_delete_response".to_string(), }, sub: format!("did:pkh:{account}"), - app: format!("did:web:{}", project_data.app_domain), + app: format!("did:web:{}", project.app_domain), }; let response_auth = sign_jwt( response_message, - &ed25519_dalek::SigningKey::from_bytes(&decode_key( - &project_data.identity_keypair.private_key, - )?), + &ed25519_dalek::SigningKey::from_bytes(&decode_key(&project.authentication_private_key)?), )?; let response = NotifyResponse:: { @@ -171,9 +153,9 @@ pub async fn handle( .await?; update_subscription_watchers( - &account, - &project_data.app_domain, - &state.database, + account.clone(), + &project.app_domain, + &state.postgres, client.as_ref(), &state.notify_keys.authentication_secret, &state.notify_keys.authentication_public, diff --git a/src/websocket_service/handlers/notify_subscribe.rs b/src/websocket_service/handlers/notify_subscribe.rs index 8452fa92..43275070 100644 --- a/src/websocket_service/handlers/notify_subscribe.rs +++ b/src/websocket_service/handlers/notify_subscribe.rs @@ -1,14 +1,15 @@ use { crate::{ + analytics::notify_client::{NotifyClientMethod, NotifyClientParams}, auth::{ add_ttl, from_jwt, sign_jwt, verify_identity, AuthError, Authorization, AuthorizedApp, SharedClaims, SubscriptionRequestAuth, SubscriptionResponseAuth, }, error::Error, - handlers::subscribe_topic::ProjectData, + model::helpers::{get_project_by_topic, upsert_subscriber}, spec::{NOTIFY_SUBSCRIBE_RESPONSE_TAG, NOTIFY_SUBSCRIBE_RESPONSE_TTL}, - state::AppState, - types::{ClientData, Envelope, EnvelopeType0, EnvelopeType1}, + state::{AppState, WebhookNotificationEvent}, + types::{Envelope, EnvelopeType0, EnvelopeType1}, websocket_service::{ decode_key, derive_key, handlers::{decrypt_message, notify_watch_subscriptions::update_subscription_watchers}, @@ -18,10 +19,9 @@ use { }, base64::Engine, chrono::Utc, - mongodb::bson::doc, - relay_rpc::domain::DecodedClientId, + relay_rpc::domain::{DecodedClientId, Topic}, serde_json::{json, Value}, - std::{sync::Arc, time::Duration}, + std::{collections::HashSet, sync::Arc, time::Duration}, tracing::{info, instrument}, x25519_dalek::StaticSecret, }; @@ -33,15 +33,14 @@ pub async fn handle( state: &Arc, client: &Arc, ) -> Result<()> { - let topic = msg.topic.to_string(); + let topic = msg.topic; - // Grab record from db - let project_data = state - .database - .collection::("project_data") - .find_one(doc!("topic": topic.clone()), None) - .await? - .ok_or(crate::error::Error::NoProjectDataForTopic(topic))?; + let project = get_project_by_topic(topic.clone(), &state.postgres) + .await + .map_err(|e| match e { + sqlx::Error::RowNotFound => Error::NoProjectDataForTopic(topic.clone()), + e => e.into(), + })?; let envelope = Envelope::::from_bytes( base64::engine::general_purpose::STANDARD.decode(msg.message.to_string())?, @@ -52,7 +51,7 @@ pub async fn handle( let sym_key = derive_key( &client_pubkey, - &x25519_dalek::StaticSecret::from(decode_key(&project_data.signing_keypair.private_key)?), + &x25519_dalek::StaticSecret::from(decode_key(&project.subscribe_private_key)?), )?; let response_topic = sha256::digest(&sym_key); info!("response_topic: {response_topic}"); @@ -65,7 +64,7 @@ pub async fn handle( .app .strip_prefix("did:web:") .ok_or(Error::AppNotDidWeb)? - != project_data.app_domain + != project.app_domain { Err(Error::AppDoesNotMatch)?; } @@ -81,7 +80,7 @@ pub async fn handle( // TODO verify `sub_auth.aud` matches `project_data.identity_keypair` if let AuthorizedApp::Limited(app) = app { - if app != project_data.app_domain { + if app != project.app_domain { Err(Error::AppSubscriptionsUnauthorized)?; } } @@ -94,7 +93,7 @@ pub async fn handle( let secret = StaticSecret::random_from_rng(chacha20poly1305::aead::OsRng); - let identity = DecodedClientId(decode_key(&project_data.identity_keypair.public_key)?); + let identity = DecodedClientId(decode_key(&project.authentication_public_key)?); let now = Utc::now(); let response_message = SubscriptionResponseAuth { @@ -106,13 +105,11 @@ pub async fn handle( act: "notify_subscription_response".to_string(), }, sub: format!("did:pkh:{account}"), - app: format!("did:web:{}", project_data.app_domain), + app: format!("did:web:{}", project.app_domain), }; let response_auth = sign_jwt( response_message, - &ed25519_dalek::SigningKey::from_bytes(&decode_key( - &project_data.identity_keypair.private_key, - )?), + &ed25519_dalek::SigningKey::from_bytes(&decode_key(&project.authentication_private_key)?), )?; let response = NotifyResponse:: { @@ -127,39 +124,56 @@ pub async fn handle( let base64_notification = base64::engine::general_purpose::STANDARD.encode(envelope.to_bytes()); - let client_data = ClientData { - id: account.clone(), - relay_url: state.config.relay_url.clone(), - sym_key: hex::encode(notify_key), - scope: sub_auth.scp.split(' ').map(|s| s.to_owned()).collect(), - expiry: sub_auth.shared_claims.exp, - }; + let scope = sub_auth + .scp + .split(' ') + .map(|s| s.to_owned()) + .collect::>(); - let notify_topic = sha256::digest(¬ify_key); + let notify_topic: Topic = sha256::digest(¬ify_key).into(); - // Registers account and subscribes to topic + let project_id = project.project_id; info!( - "Registering account: {} with topic: {} at project: {}. Scope: {:?}. Msg id: {:?}", - &client_data.id, ¬ify_topic, &project_data.id, &client_data.scope, &msg.id, + "Registering account: {account} with topic: {notify_topic} at project: {project_id}. \ + Scope: {scope:?}. RPC ID: {id:?}", ); + + let subscriber_id = upsert_subscriber( + project.id, + account.clone(), + scope.clone(), + ¬ify_key, + notify_topic.clone(), + &state.postgres, + ) + .await?; + + // TODO do in same transaction as upsert_subscriber() state - .register_client( - &project_data.id, - client_data, - &url::Url::parse(&state.config.relay_url)?, + .notify_webhook( + project_id.as_ref(), + WebhookNotificationEvent::Subscribed, + account.as_ref(), ) .await?; + state.wsclient.subscribe(notify_topic.clone()).await?; + + state.analytics.client(NotifyClientParams { + pk: subscriber_id.to_string(), + method: NotifyClientMethod::Subscribe, + project_id: project_id.to_string(), + account: account.to_string(), + topic: topic.to_string(), + notify_topic: notify_topic.to_string(), + old_scope: "".to_owned(), + new_scope: scope.into_iter().collect::>().join(","), + }); + // Send noop to extend ttl of relay's mapping info!("publishing noop to notify_topic: {notify_topic}"); client - .publish( - notify_topic.clone().into(), - "", - 4050, - Duration::from_secs(300), - false, - ) + .publish(notify_topic, "", 4050, Duration::from_secs(300), false) .await?; info!("publishing subscribe response to topic: {response_topic}"); @@ -174,9 +188,9 @@ pub async fn handle( .await?; update_subscription_watchers( - &account, - &project_data.app_domain, - &state.database, + account, + &project.app_domain, + &state.postgres, client.as_ref(), &state.notify_keys.authentication_secret, &state.notify_keys.authentication_public, diff --git a/src/websocket_service/handlers/notify_update.rs b/src/websocket_service/handlers/notify_update.rs index ac33873c..9c85b664 100644 --- a/src/websocket_service/handlers/notify_update.rs +++ b/src/websocket_service/handlers/notify_update.rs @@ -1,15 +1,16 @@ use { super::notify_watch_subscriptions::update_subscription_watchers, crate::{ + analytics::notify_client::{NotifyClientMethod, NotifyClientParams}, auth::{ add_ttl, from_jwt, sign_jwt, verify_identity, AuthError, Authorization, AuthorizedApp, SharedClaims, SubscriptionUpdateRequestAuth, SubscriptionUpdateResponseAuth, }, error::Error, - handlers::subscribe_topic::ProjectData, + model::helpers::{get_project_by_id, get_subscriber_by_topic, update_subscriber}, spec::{NOTIFY_UPDATE_RESPONSE_TAG, NOTIFY_UPDATE_RESPONSE_TTL}, state::AppState, - types::{ClientData, Envelope, EnvelopeType0, LookupEntry}, + types::{Envelope, EnvelopeType0}, websocket_service::{ decode_key, handlers::decrypt_message, NotifyRequest, NotifyResponse, NotifyUpdate, }, @@ -17,10 +18,9 @@ use { }, base64::Engine, chrono::Utc, - mongodb::bson::doc, relay_rpc::domain::DecodedClientId, serde_json::{json, Value}, - std::sync::Arc, + std::{collections::HashSet, sync::Arc}, }; // TODO test idempotency @@ -29,38 +29,22 @@ pub async fn handle( state: &Arc, client: &Arc, ) -> Result<()> { - let _request_id = uuid::Uuid::new_v4(); - let topic = msg.topic.to_string(); - - // Grab record from db - let lookup_data = state - .database - .collection::("lookup_table") - .find_one(doc!("_id":topic.clone()), None) - .await? - .ok_or(crate::error::Error::NoProjectDataForTopic(topic.clone()))?; - - let project_data = state - .database - .collection::("project_data") - .find_one(doc!("_id": lookup_data.project_id.clone()), None) - .await? - .ok_or(crate::error::Error::NoProjectDataForTopic( - topic.to_string(), - ))?; - - let client_data = state - .database - .collection::(&lookup_data.project_id) - .find_one(doc!("_id": &lookup_data.account), None) - .await? - .ok_or(crate::error::Error::NoClientDataForTopic(topic.clone()))?; + let topic = msg.topic; + + // TODO combine these two SQL queries + let subscriber = get_subscriber_by_topic(topic.clone(), &state.postgres) + .await + .map_err(|e| match e { + sqlx::Error::RowNotFound => Error::NoClientDataForTopic(topic.clone()), + e => e.into(), + })?; + let project = get_project_by_id(subscriber.project, &state.postgres).await?; let envelope = Envelope::::from_bytes( base64::engine::general_purpose::STANDARD.decode(msg.message.to_string())?, )?; - let sym_key = decode_key(&client_data.sym_key)?; + let sym_key = decode_key(&subscriber.sym_key)?; let msg: NotifyRequest = decrypt_message(envelope, &sym_key)?; @@ -69,7 +53,7 @@ pub async fn handle( .app .strip_prefix("did:web:") .ok_or(Error::AppNotDidWeb)? - != project_data.app_domain + != project.app_domain { Err(Error::AppDoesNotMatch)?; } @@ -85,7 +69,7 @@ pub async fn handle( // TODO verify `sub_auth.aud` matches `project_data.identity_keypair` if let AuthorizedApp::Limited(app) = app { - if app != project_data.app_domain { + if app != project.app_domain { Err(Error::AppSubscriptionsUnauthorized)?; } } @@ -93,23 +77,37 @@ pub async fn handle( account }; - let client_data = ClientData { - id: account.clone(), - relay_url: state.config.relay_url.clone(), - sym_key: client_data.sym_key.clone(), - scope: sub_auth.scp.split(' ').map(|s| s.to_owned()).collect(), - expiry: sub_auth.shared_claims.exp, - }; - - state - .register_client( - &lookup_data.project_id, - client_data, - &url::Url::parse(&state.config.relay_url)?, - ) - .await?; - - let identity = DecodedClientId(decode_key(&project_data.identity_keypair.public_key)?); + let old_scope = subscriber.scope; + let scope = sub_auth + .scp + .split(' ') + .map(|s| s.to_owned()) + .collect::>(); + + let subscriber = + update_subscriber(project.id, account.clone(), scope.clone(), &state.postgres).await?; + + // TODO do in same transaction as update_subscriber() + // state + // .notify_webhook( + // project_id.as_ref(), + // WebhookNotificationEvent::Updated, + // account.as_ref(), + // ) + // .await?; + + state.analytics.client(NotifyClientParams { + pk: subscriber.id.to_string(), + method: NotifyClientMethod::Update, + project_id: project.id.to_string(), + account: account.to_string(), + topic: topic.to_string(), + notify_topic: subscriber.topic.to_string(), + old_scope: old_scope.join(","), + new_scope: scope.into_iter().collect::>().join(","), + }); + + let identity = DecodedClientId(decode_key(&project.authentication_public_key)?); let now = Utc::now(); let response_message = SubscriptionUpdateResponseAuth { @@ -121,13 +119,11 @@ pub async fn handle( act: "notify_update_response".to_string(), }, sub: format!("did:pkh:{account}"), - app: format!("did:web:{}", project_data.app_domain), + app: format!("did:web:{}", project.app_domain), }; let response_auth = sign_jwt( response_message, - &ed25519_dalek::SigningKey::from_bytes(&decode_key( - &project_data.identity_keypair.private_key, - )?), + &ed25519_dalek::SigningKey::from_bytes(&decode_key(&project.authentication_private_key)?), )?; let response = NotifyResponse:: { @@ -153,9 +149,9 @@ pub async fn handle( .await?; update_subscription_watchers( - &account, - &project_data.app_domain, - &state.database, + account, + &project.app_domain, + &state.postgres, client.as_ref(), &state.notify_keys.authentication_secret, &state.notify_keys.authentication_public, diff --git a/src/websocket_service/handlers/notify_watch_subscriptions.rs b/src/websocket_service/handlers/notify_watch_subscriptions.rs index 67c1a0a1..ef406ac9 100644 --- a/src/websocket_service/handlers/notify_watch_subscriptions.rs +++ b/src/websocket_service/handlers/notify_watch_subscriptions.rs @@ -6,17 +6,21 @@ use { WatchSubscriptionsRequestAuth, WatchSubscriptionsResponseAuth, }, error::Error, - handlers::subscribe_topic::ProjectData, + model::{ + helpers::{ + get_project_by_app_domain, get_subscription_watchers_for_account_by_app_or_all_app, + get_subscriptions_by_account, get_subscriptions_by_account_and_app, + upsert_subscription_watcher, + }, + types::AccountId, + }, spec::{ NOTIFY_SUBSCRIPTIONS_CHANGED_METHOD, NOTIFY_SUBSCRIPTIONS_CHANGED_TAG, NOTIFY_SUBSCRIPTIONS_CHANGED_TTL, NOTIFY_WATCH_SUBSCRIPTIONS_RESPONSE_TAG, NOTIFY_WATCH_SUBSCRIPTIONS_RESPONSE_TTL, }, state::AppState, - types::{ - ClientData, Envelope, EnvelopeType0, EnvelopeType1, LookupEntry, - WatchSubscriptionsEntry, - }, + types::{Envelope, EnvelopeType0, EnvelopeType1}, websocket_service::{ decode_key, derive_key, handlers::decrypt_message, NotifyRequest, NotifyResponse, NotifyWatchSubscriptions, @@ -25,10 +29,9 @@ use { }, base64::Engine, chrono::{Duration, Utc}, - futures::TryStreamExt, - mongodb::{bson::doc, Database}, relay_rpc::domain::{DecodedClientId, Topic}, serde_json::{json, Value}, + sqlx::PgPool, std::sync::Arc, tracing::{info, instrument}, }; @@ -78,7 +81,7 @@ pub async fn handle( // - put desired `iss` value as an argument to make sure we verify // it }; - let account = &authorization.account; + let account = authorization.account; info!("authorization.app: {:?}", authorization.app); info!("request_auth.app: {:?}", request_auth.app); @@ -94,35 +97,31 @@ pub async fn handle( check_app_authorization(&authorization.app, app_domain.as_deref())?; let subscriptions = - collect_subscriptions(account, app_domain.as_deref(), state.database.as_ref()).await?; + collect_subscriptions(account.clone(), app_domain.as_deref(), &state.postgres).await?; let did_key = request_auth.shared_claims.iss; info!("did_key: {did_key}"); - // TODO txn - - let delete_result = state - .database - .collection::("watch_subscriptions") - .delete_many(doc! { "did_key": &did_key }, None) - .await?; - info!("deleted_count: {}", delete_result.deleted_count); - - // TODO same did_key replaces old watcher - state - .database - .collection("watch_subscriptions") - .insert_one( - WatchSubscriptionsEntry { - account: account.clone(), - app_domain, - sym_key: hex::encode(response_sym_key), - did_key: did_key.clone(), - expiry: (Utc::now() + Duration::days(1)).timestamp() as u64, - }, - None, - ) - .await?; + let project = if let Some(app_domain) = app_domain { + let project = get_project_by_app_domain(&app_domain, &state.postgres) + .await + .map_err(|e| match e { + sqlx::Error::RowNotFound => Error::NoProjectDataForAppDomain(app_domain), + e => e.into(), + })?; + Some(project.id) + } else { + None + }; + upsert_subscription_watcher( + account, + project, + &did_key, + &hex::encode(response_sym_key), + Utc::now() + Duration::days(1), + &state.postgres, + ) + .await?; // Respond { @@ -167,60 +166,30 @@ pub async fn handle( Ok(()) } -#[instrument(skip(database))] +#[instrument(skip(postgres))] pub async fn collect_subscriptions( - account: &str, + account: AccountId, app_domain: Option<&str>, - database: &Database, + postgres: &PgPool, ) -> Result> { info!("Called collect_subscriptions"); - let mut lookup_data = database - .collection::("lookup_table") - .find(doc! { "account": account }, None) - .await?; - - let mut subscriptions = vec![]; - - while let Some(lookup_entry) = lookup_data.try_next().await? { - let project_id = lookup_entry.project_id; - info!("project_id: {project_id}"); - - let project_data = database - .collection::("project_data") - .find_one(doc!("_id": &project_id), None) - .await? - .ok_or_else(|| crate::error::Error::NoProjectDataForProjectId(project_id.clone()))?; - - if let Some(domain) = app_domain { - info!("app_domain is Some"); - // TODO make domain a dedicated field and query by it when app_domain is Some() - if project_data.app_domain != domain { - info!("project app_domain does not match"); - continue; - } - } - - info!("querying project_id database for client_data"); - let client_data = database - .collection::(&project_id) - .find_one(doc!("_id": account), None) - .await? - .ok_or_else(|| { - crate::error::Error::NoClientDataForProjectIdAndAccount( - project_id.clone(), - account.to_owned(), - ) - })?; + let subscriptions = if let Some(app_domain) = app_domain { + get_subscriptions_by_account_and_app(account, app_domain, postgres).await? + } else { + get_subscriptions_by_account(account, postgres).await? + }; - subscriptions.push(NotifyServerSubscription { - app_domain: project_data.app_domain, - account: account.to_owned(), - scope: client_data.scope, - sym_key: client_data.sym_key, - expiry: client_data.expiry, - }); - } + let subscriptions = subscriptions + .into_iter() + .map(|sub| NotifyServerSubscription { + app_domain: sub.app_domain, + sym_key: sub.sym_key, + account: sub.account, + scope: sub.scope.into_iter().collect(), + expiry: sub.expiry.timestamp() as u64, + }) + .collect::>(); Ok(subscriptions) } @@ -228,9 +197,9 @@ pub async fn collect_subscriptions( // TODO do async outside of websocket request handler #[instrument(skip_all, fields(account = %account, app_domain = %app_domain))] pub async fn update_subscription_watchers( - account: &str, + account: AccountId, app_domain: &str, - database: &Database, + postgres: &PgPool, client: &relay_client::websocket::Client, authentication_secret: &ed25519_dalek::SigningKey, authentication_public: &ed25519_dalek::VerifyingKey, @@ -290,50 +259,34 @@ pub async fn update_subscription_watchers( Ok(()) } - info!("querying watch_subscriptions for app_domain: {app_domain}"); - let mut cursor = database - .collection::("watch_subscriptions") - .find( - doc! { "account": account, "app_domain": Some(app_domain) }, - None, - ) - .await?; + let all_account_subscriptions = collect_subscriptions(account.clone(), None, postgres).await?; - let subscriptions = collect_subscriptions(account, Some(app_domain), database).await?; - while let Some(watch_subscriptions_entry) = cursor.try_next().await? { - send( - subscriptions.clone(), - watch_subscriptions_entry.did_key.clone(), - &watch_subscriptions_entry.sym_key, - notify_did_key.clone(), - did_pkh.clone(), - client, - authentication_secret, - ) - .await?; - } + let app_subscriptions = all_account_subscriptions + .iter() + .filter(|sub| sub.app_domain == app_domain) + .cloned() + .collect::>(); - info!("querying watch_subscriptions for no app_domain"); - let mut cursor = database - .collection::("watch_subscriptions") - .find( - doc! { "account": account, "app_domain": None::<&str> }, - None, - ) - .await?; + let subscription_watchers = + get_subscription_watchers_for_account_by_app_or_all_app(account, app_domain, postgres) + .await?; + for watcher in subscription_watchers { + let subscriptions = if watcher.project.is_some() { + app_subscriptions.clone() + } else { + all_account_subscriptions.clone() + }; - let subscriptions = collect_subscriptions(account, None, database).await?; - while let Some(watch_subscriptions_entry) = cursor.try_next().await? { send( - subscriptions.clone(), - watch_subscriptions_entry.did_key.clone(), - &watch_subscriptions_entry.sym_key, + subscriptions, + watcher.did_key.clone(), + &watcher.sym_key, notify_did_key.clone(), did_pkh.clone(), client, authentication_secret, ) - .await?; + .await? } Ok(()) diff --git a/src/websocket_service/mod.rs b/src/websocket_service/mod.rs index e5ba89b6..08a78fa3 100644 --- a/src/websocket_service/mod.rs +++ b/src/websocket_service/mod.rs @@ -1,28 +1,26 @@ use { crate::{ - handlers::subscribe_topic::ProjectData, metrics::Metrics, + model::helpers::{get_project_topics, get_subscriber_topics}, spec::{ NOTIFY_DELETE_TAG, NOTIFY_SUBSCRIBE_TAG, NOTIFY_UPDATE_TAG, NOTIFY_WATCH_SUBSCRIPTIONS_TAG, }, state::AppState, - types::LookupEntry, websocket_service::handlers::{ notify_delete, notify_subscribe, notify_update, notify_watch_subscriptions, }, wsclient::{self, create_connection_opts, RelayClientEvent}, Result, }, - futures::{executor, future, StreamExt}, - mongodb::{bson::doc, Database}, rand::Rng, relay_rpc::{ domain::{MessageId, Topic}, - rpc::JSON_RPC_VERSION_STR, + rpc::{JSON_RPC_VERSION_STR, MAX_SUBSCRIPTION_BATCH_SIZE}, }, serde::{Deserialize, Serialize}, sha2::Sha256, + sqlx::PgPool, std::{sync::Arc, time::Instant}, tracing::{error, info, instrument, warn}, wc::metrics::otel::Context, @@ -69,11 +67,12 @@ impl WebsocketService { resubscribe( self.state.notify_keys.key_agreement_topic.clone(), - &self.state.database, + &self.state.postgres, &self.wsclient, &self.state.metrics, ) .await?; + Ok(()) } @@ -90,7 +89,7 @@ impl WebsocketService { tokio::spawn(async move { handle_msg(msg, &state, &wsclient).await }); } wsclient::RelayClientEvent::Error(e) => { - warn!("Received error from relay: {}", e); + warn!("Received error from relay: {e}"); while let Err(e) = self.connect().await { error!("Error reconnecting to relay: {}", e); tokio::time::sleep(std::time::Duration::from_secs(1)).await; @@ -159,67 +158,31 @@ async fn handle_msg( async fn resubscribe( key_agreement_topic: Topic, - database: &Arc, + postgres: &PgPool, client: &Arc, metrics: &Option, ) -> Result<()> { info!("Resubscribing to all topics"); let start = Instant::now(); - client.subscribe(key_agreement_topic).await?; - - // TODO pipeline key_agreement_topic and both cursors into 1 iterator and call - // batch_subscribe in one set of chunks instead of 2 - - // Get all topics from db - let cursor = database - .collection::("lookup_table") - .find(None, None) - .await?; + let subscribers = get_subscriber_topics(postgres).await?; + let subscribers_count = subscribers.len(); + info!("subscribers_count: {subscribers_count}"); - // Iterate over all topics and sub to them again using the _id field from each - // record - // Chunked into 500, as thats the max relay is allowing - let mut clients_count = 0; - cursor - .chunks(relay_rpc::rpc::MAX_SUBSCRIPTION_BATCH_SIZE) - .for_each(|chunk| { - clients_count += chunk.len(); - let topics = chunk - .into_iter() - .filter_map(|x| x.ok()) - .map(|x| Topic::new(x.topic.into())) - .collect::>(); - if let Err(e) = executor::block_on(client.batch_subscribe(topics)) { - warn!("Error resubscribing to topics: {}", e); - } - future::ready(()) - }) - .await; - info!("clients_count: {clients_count}"); + let projects = get_project_topics(postgres).await?; + let projects_count = projects.len(); + info!("projects_count: {projects_count}"); - let cursor = database - .collection::("project_data") - .find(None, None) - .await?; + let topics = [key_agreement_topic] + .into_iter() + .chain(subscribers.into_iter()) + .chain(projects.into_iter()) + .collect::>(); - let mut projects_count = 0; - cursor - .chunks(relay_rpc::rpc::MAX_SUBSCRIPTION_BATCH_SIZE) - .for_each(|chunk| { - projects_count += chunk.len(); - let topics = chunk - .into_iter() - .filter_map(|x| x.ok()) - .map(|x| Topic::new(x.topic.into())) - .collect::>(); - if let Err(e) = executor::block_on(client.batch_subscribe(topics)) { - warn!("Error resubscribing to topics: {}", e); - } - future::ready(()) - }) - .await; - info!("projects_count: {projects_count}"); + let chunks = topics.chunks(MAX_SUBSCRIPTION_BATCH_SIZE); + for chunk in chunks { + client.batch_subscribe(chunk).await?; + } if let Some(metrics) = metrics { let ctx = Context::current(); @@ -228,7 +191,7 @@ async fn resubscribe( .observe(&ctx, projects_count as u64, &[]); metrics .subscribed_client_topics - .observe(&ctx, clients_count as u64, &[]); + .observe(&ctx, subscribers_count as u64, &[]); metrics.subscribe_latency.record( &ctx, start.elapsed().as_millis().try_into().unwrap(), diff --git a/tests/integration.rs b/tests/integration.rs index 20ba54d7..93dae0af 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -18,8 +18,9 @@ use { WatchSubscriptionsRequestAuth, WatchSubscriptionsResponseAuth, STATEMENT, STATEMENT_ALL_DOMAINS, }, - handlers::notify::JwtMessage, + handlers::{notify::JwtMessage, subscribe_topic::SubscribeTopicRequestData}, jsonrpc::NotifyPayload, + model::types::AccountId, spec::{ NOTIFY_DELETE_METHOD, NOTIFY_DELETE_RESPONSE_TAG, NOTIFY_DELETE_TAG, NOTIFY_DELETE_TTL, NOTIFY_MESSAGE_TAG, NOTIFY_SUBSCRIBE_METHOD, NOTIFY_SUBSCRIBE_RESPONSE_TAG, @@ -103,7 +104,7 @@ async fn notify_properly_sending_message() { .as_bytes()[1..], ) .finalize()[12..]; - let account = format!("eip155:1:0x{}", hex::encode(address)); + let account: AccountId = format!("eip155:1:0x{}", hex::encode(address)).into(); let did_pkh = format!("did:pkh:{account}"); let app_domain = "app.example.com"; @@ -645,11 +646,11 @@ async fn notify_properly_sending_message() { // https://github.com/WalletConnect/walletconnect-docs/blob/main/docs/specs/clients/notify/notify-authentication.md#notify-message // TODO: verify issuer - assert_eq!(claims.msg, notification); + assert_eq!(claims.msg.as_ref(), ¬ification); assert_eq!(claims.sub, did_pkh); assert!(claims.iat < chrono::Utc::now().timestamp() + JWT_LEEWAY); // TODO remove leeway assert!(claims.exp > chrono::Utc::now().timestamp() - JWT_LEEWAY); // TODO remove leeway - assert_eq!(claims.app, app_domain); + assert_eq!(claims.app.as_ref(), app_domain); assert_eq!(claims.sub, did_pkh); assert_eq!(claims.act, "notify_message"); @@ -942,7 +943,7 @@ async fn old_siwe_compatible() { .as_bytes()[1..], ) .finalize()[12..]; - let account = format!("eip155:1:0x{}", hex::encode(address)); + let account: AccountId = format!("eip155:1:0x{}", hex::encode(address)).into(); let did_pkh = format!("did:pkh:{account}"); let app_domain = "app.example.com"; @@ -1198,27 +1199,6 @@ async fn old_siwe_compatible() { (auth.sbs, response_topic_key) } - // ==== watchSubscriptions ==== - { - let (secret, public, wsclient, mut rx) = - create_client(&relay_url, &relay_project_id, &keypair, ¬ify_url).await; - - let (subs, _) = watch_subscriptions( - app_domain, - ¬ify_url, - &signing_key, - &client_did_key, - &did_pkh, - &secret, - &public, - &wsclient, - &mut rx, - ) - .await; - - assert!(subs.is_empty()); - } - let (secret, public, wsclient, mut rx) = create_client(&relay_url, &relay_project_id, &keypair, ¬ify_url).await; @@ -1230,9 +1210,11 @@ async fn old_siwe_compatible() { // Register project - generating subscribe topic let subscribe_topic_response = reqwest::Client::new() - .post(format!("{}/{}/subscribe-topic", ¬ify_url, &project_id)) + .post(format!("{notify_url}/{project_id}/subscribe-topic")) .bearer_auth(&project_secret) - .json(&json!({ "appDomain": app_domain })) + .json(&SubscribeTopicRequestData { + app_domain: app_domain.to_owned(), + }) .send() .await .unwrap(); @@ -1408,10 +1390,6 @@ async fn old_siwe_compatible() { ); assert_eq!(sub.account, account); assert_eq!(sub.app_domain, app_domain); - assert_eq!( - sub.scope, - HashSet::from(["test".to_owned(), "test1".to_owned()]), - ); decode_key(&sub.sym_key).unwrap() }; @@ -1484,11 +1462,11 @@ async fn old_siwe_compatible() { // https://github.com/WalletConnect/walletconnect-docs/blob/main/docs/specs/clients/notify/notify-authentication.md#notify-message // TODO: verify issuer - assert_eq!(claims.msg, notification); + assert_eq!(*claims.msg, notification); assert_eq!(claims.sub, did_pkh); assert!(claims.iat < chrono::Utc::now().timestamp() + JWT_LEEWAY); // TODO remove leeway assert!(claims.exp > chrono::Utc::now().timestamp() - JWT_LEEWAY); // TODO remove leeway - assert_eq!(claims.app, app_domain); + assert_eq!(claims.app.as_ref(), app_domain); assert_eq!(claims.sub, did_pkh); assert_eq!(claims.act, "notify_message"); @@ -1781,7 +1759,7 @@ async fn old_old_siwe_compatible() { .as_bytes()[1..], ) .finalize()[12..]; - let account = format!("eip155:1:0x{}", hex::encode(address)); + let account: AccountId = format!("eip155:1:0x{}", hex::encode(address)).into(); let did_pkh = format!("did:pkh:{account}"); let app_domain = "app.example.com"; @@ -2041,27 +2019,6 @@ async fn old_old_siwe_compatible() { (auth.sbs, response_topic_key) } - // ==== watchSubscriptions ==== - { - let (secret, public, wsclient, mut rx) = - create_client(&relay_url, &relay_project_id, &keypair, ¬ify_url).await; - - let (subs, _) = watch_subscriptions( - app_domain, - ¬ify_url, - &signing_key, - &client_did_key, - &did_pkh, - &secret, - &public, - &wsclient, - &mut rx, - ) - .await; - - assert!(subs.is_empty()); - } - let (secret, public, wsclient, mut rx) = create_client(&relay_url, &relay_project_id, &keypair, ¬ify_url).await; @@ -2073,9 +2030,11 @@ async fn old_old_siwe_compatible() { // Register project - generating subscribe topic let subscribe_topic_response = reqwest::Client::new() - .post(format!("{}/{}/subscribe-topic", ¬ify_url, &project_id)) + .post(format!("{notify_url}/{project_id}/subscribe-topic")) .bearer_auth(&project_secret) - .json(&json!({ "appDomain": app_domain })) + .json(&SubscribeTopicRequestData { + app_domain: app_domain.to_owned(), + }) .send() .await .unwrap(); @@ -2251,10 +2210,6 @@ async fn old_old_siwe_compatible() { ); assert_eq!(sub.account, account); assert_eq!(sub.app_domain, app_domain); - assert_eq!( - sub.scope, - HashSet::from(["test".to_owned(), "test1".to_owned()]), - ); decode_key(&sub.sym_key).unwrap() }; @@ -2327,11 +2282,11 @@ async fn old_old_siwe_compatible() { // https://github.com/WalletConnect/walletconnect-docs/blob/main/docs/specs/clients/notify/notify-authentication.md#notify-message // TODO: verify issuer - assert_eq!(claims.msg, notification); + assert_eq!(*claims.msg, notification); assert_eq!(claims.sub, did_pkh); assert!(claims.iat < chrono::Utc::now().timestamp() + JWT_LEEWAY); // TODO remove leeway assert!(claims.exp > chrono::Utc::now().timestamp() - JWT_LEEWAY); // TODO remove leeway - assert_eq!(claims.app, app_domain); + assert_eq!(claims.app.as_ref(), app_domain); assert_eq!(claims.sub, did_pkh); assert_eq!(claims.act, "notify_message"); diff --git a/tests/storage.rs b/tests/storage.rs new file mode 100644 index 00000000..0679027f --- /dev/null +++ b/tests/storage.rs @@ -0,0 +1,1084 @@ +use { + chrono::{Duration, Utc}, + mongodb::{ + bson::doc, + options::{ClientOptions, ResolverConfig}, + }, + notify_server::{ + migrate::{self, ClientData, Keypair, LookupEntry, ProjectData}, + model::{ + helpers::{ + get_project_by_app_domain, get_project_by_project_id, get_project_by_topic, + get_project_topics, get_subscriber_accounts_by_project_id, get_subscriber_by_topic, + get_subscriber_topics, get_subscribers_for_project_in, + get_subscriptions_by_account, + }, + types::AccountId, + }, + }, + relay_rpc::domain::{ProjectId, Topic}, + sqlx::{postgres::PgPoolOptions, PgPool}, + std::collections::HashSet, +}; + +async fn get_dbs() -> (mongodb::Database, PgPool) { + let mongodb = mongodb::Client::with_options( + ClientOptions::parse_with_resolver_config( + &std::env::var("DATABASE_URL").unwrap(), + ResolverConfig::cloudflare(), + ) + .await + .unwrap(), + ) + .unwrap() + .database("notify"); + mongodb.drop(None).await.unwrap(); + + let postgres = PgPoolOptions::new() + .connect(&std::env::var("POSTGRES_URL").unwrap()) + .await + .unwrap(); + let mut txn = postgres.begin().await.unwrap(); + sqlx::query("DROP SCHEMA IF EXISTS public CASCADE") + .execute(&mut *txn) + .await + .unwrap(); + sqlx::query("CREATE SCHEMA public") + .execute(&mut *txn) + .await + .unwrap(); + txn.commit().await.unwrap(); + sqlx::migrate!("./migrations").run(&postgres).await.unwrap(); + + (mongodb, postgres) +} + +#[tokio::test] +async fn test_empty_projects_and_subscribers() { + let (mongodb, postgres) = get_dbs().await; + notify_server::migrate::migrate(&mongodb, &postgres) + .await + .unwrap(); + assert_eq!(get_project_topics(&postgres).await.unwrap(), vec![]); + assert_eq!(get_subscriber_topics(&postgres).await.unwrap(), vec![]); +} + +#[tokio::test] +async fn test_one_project() { + let (mongodb, postgres) = get_dbs().await; + + let topic: Topic = "project_topic".into(); + let project_id: ProjectId = "project_id".into(); + let signing_secret = "signing_secret"; + let signing_public = "signing_public"; + let identity_secret = "identity_secret"; + let identity_public = "identity_public"; + let app_domain = "app.example.com"; + mongodb + .collection::("project_data") + .insert_one( + ProjectData { + id: project_id.to_string(), + signing_keypair: Keypair { + private_key: signing_secret.to_string(), + public_key: signing_public.to_string(), + }, + identity_keypair: Keypair { + private_key: identity_secret.to_string(), + public_key: identity_public.to_string(), + }, + app_domain: app_domain.to_string(), + topic: topic.to_string(), + }, + None, + ) + .await + .unwrap(); + + assert_eq!( + mongodb + .collection::("project_data") + .count_documents(doc! {}, None) + .await + .unwrap(), + 1 + ); + + migrate::migrate(&mongodb, &postgres).await.unwrap(); + + assert_eq!( + mongodb + .collection::("project_data") + .count_documents(doc! {}, None) + .await + .unwrap(), + 0 + ); + + assert_eq!(get_subscriber_topics(&postgres).await.unwrap(), vec![]); + assert_eq!( + get_project_topics(&postgres).await.unwrap(), + vec![topic.clone()] + ); + let project = get_project_by_app_domain(app_domain, &postgres) + .await + .unwrap(); + assert_eq!(project.project_id, project_id.clone()); + assert_eq!(project.app_domain, app_domain); + assert_eq!(project.topic, topic); + assert_eq!(project.authentication_public_key, identity_public); + assert_eq!(project.authentication_private_key, identity_secret); + assert_eq!(project.subscribe_public_key, signing_public); + assert_eq!(project.subscribe_private_key, signing_secret); + + let project = get_project_by_project_id(project_id.clone(), &postgres) + .await + .unwrap(); + assert_eq!(project.project_id, project_id.clone()); + assert_eq!(project.app_domain, app_domain); + assert_eq!(project.topic, topic); + assert_eq!(project.authentication_public_key, identity_public); + assert_eq!(project.authentication_private_key, identity_secret); + assert_eq!(project.subscribe_public_key, signing_public); + assert_eq!(project.subscribe_private_key, signing_secret); + + let project = get_project_by_topic(topic.clone(), &postgres) + .await + .unwrap(); + assert_eq!(project.project_id, project_id.clone()); + assert_eq!(project.app_domain, app_domain); + assert_eq!(project.topic, topic); + assert_eq!(project.authentication_public_key, identity_public); + assert_eq!(project.authentication_private_key, identity_secret); + assert_eq!(project.subscribe_public_key, signing_public); + assert_eq!(project.subscribe_private_key, signing_secret); +} + +#[tokio::test] +async fn test_one_subscriber() { + let (mongodb, postgres) = get_dbs().await; + + let topic: Topic = "project_topic".into(); + let project_id: ProjectId = "project_id".into(); + let signing_secret = "signing_secret"; + let signing_public = "signing_public"; + let identity_secret = "identity_secret"; + let identity_public = "identity_public"; + let app_domain = "app.example.com"; + mongodb + .collection::("project_data") + .insert_one( + ProjectData { + id: project_id.to_string(), + signing_keypair: Keypair { + private_key: signing_secret.to_string(), + public_key: signing_public.to_string(), + }, + identity_keypair: Keypair { + private_key: identity_secret.to_string(), + public_key: identity_public.to_string(), + }, + app_domain: app_domain.to_string(), + topic: topic.to_string(), + }, + None, + ) + .await + .unwrap(); + + let account_id: AccountId = "eip155:1:0xfff".into(); + let subscriber_sym_key = hex::encode([0u8; 32]); + let subscriber_topic: Topic = "subscriber_topic".into(); + let subcriber_scope = HashSet::from(["scope1".to_string(), "scope2".to_string()]); + let client_data = ClientData { + id: account_id.to_string(), + relay_url: "relay_url".to_string(), + sym_key: subscriber_sym_key.to_string(), + scope: subcriber_scope.clone(), + expiry: 100, + }; + mongodb + .collection::(project_id.as_ref()) + .insert_one(client_data, None) + .await + .unwrap(); + assert_eq!( + mongodb + .collection::(project_id.as_ref()) + .count_documents(doc! {}, None) + .await + .unwrap(), + 1 + ); + mongodb + .collection::("lookup_table") + .insert_one( + LookupEntry { + topic: subscriber_topic.to_string(), + project_id: project_id.to_string(), + account: account_id.to_string(), + expiry: 100, + }, + None, + ) + .await + .unwrap(); + assert_eq!( + mongodb + .collection::("lookup_table") + .count_documents(doc! {}, None) + .await + .unwrap(), + 1 + ); + + migrate::migrate(&mongodb, &postgres).await.unwrap(); + + assert_eq!( + mongodb + .collection::(project_id.as_ref()) + .count_documents(doc! {}, None) + .await + .unwrap(), + 0 + ); + assert_eq!( + mongodb + .collection::("lookup_table") + .count_documents(doc! {}, None) + .await + .unwrap(), + 0 + ); + + let project = get_project_by_project_id(project_id.clone(), &postgres) + .await + .unwrap(); + + assert_eq!( + get_subscriber_topics(&postgres).await.unwrap(), + vec![subscriber_topic.clone()] + ); + + let subscriber = get_subscriber_by_topic(subscriber_topic.clone(), &postgres) + .await + .unwrap(); + assert_eq!(subscriber.project, project.id); + assert_eq!(subscriber.account, account_id); + assert_eq!(subscriber.sym_key, subscriber_sym_key); + assert_eq!(subscriber.topic, subscriber_topic); + assert_eq!( + subscriber.scope.into_iter().collect::>(), + subcriber_scope + ); + assert!(subscriber.expiry > Utc::now() + Duration::days(29)); + + let subscribers = get_subscribers_for_project_in(project.id, &[account_id.clone()], &postgres) + .await + .unwrap(); + assert_eq!(subscribers.len(), 1); + let subscriber = &subscribers[0]; + assert_eq!(subscriber.project, project.id); + assert_eq!(subscriber.account, account_id); + assert_eq!(subscriber.sym_key, subscriber_sym_key); + assert_eq!(subscriber.topic, subscriber_topic); + assert_eq!( + subscriber.scope.iter().cloned().collect::>(), + subcriber_scope + ); + assert!(subscriber.expiry > Utc::now() + Duration::days(29)); + + let accounts = get_subscriber_accounts_by_project_id(project_id.clone(), &postgres) + .await + .unwrap(); + assert_eq!(accounts, vec![account_id.clone()]); + + let subscribers = get_subscriptions_by_account(account_id.clone(), &postgres) + .await + .unwrap(); + assert_eq!(subscribers.len(), 1); + let subscriber = &subscribers[0]; + assert_eq!(subscriber.app_domain, project.app_domain); + assert_eq!(subscriber.account, account_id); + assert_eq!(subscriber.sym_key, subscriber_sym_key); + assert_eq!( + subscriber.scope.iter().cloned().collect::>(), + subcriber_scope + ); + assert!(subscriber.expiry > Utc::now() + Duration::days(29)); +} + +#[tokio::test] +async fn test_two_subscribers() { + let (mongodb, postgres) = get_dbs().await; + + let topic: Topic = "project_topic".into(); + let project_id: ProjectId = "project_id".into(); + let signing_secret = "signing_secret"; + let signing_public = "signing_public"; + let identity_secret = "identity_secret"; + let identity_public = "identity_public"; + let app_domain = "app.example.com"; + mongodb + .collection::("project_data") + .insert_one( + ProjectData { + id: project_id.to_string(), + signing_keypair: Keypair { + private_key: signing_secret.to_string(), + public_key: signing_public.to_string(), + }, + identity_keypair: Keypair { + private_key: identity_secret.to_string(), + public_key: identity_public.to_string(), + }, + app_domain: app_domain.to_string(), + topic: topic.to_string(), + }, + None, + ) + .await + .unwrap(); + + let account_id: AccountId = "eip155:1:0xfff".into(); + let subscriber_sym_key = hex::encode([0u8; 32]); + let subscriber_topic: Topic = "subscriber_topic".into(); + let subcriber_scope = HashSet::from(["scope1".to_string(), "scope2".to_string()]); + let client_data = ClientData { + id: account_id.to_string(), + relay_url: "relay_url".to_string(), + sym_key: subscriber_sym_key.to_string(), + scope: subcriber_scope.clone(), + expiry: 100, + }; + mongodb + .collection::(project_id.as_ref()) + .insert_one(client_data, None) + .await + .unwrap(); + mongodb + .collection::("lookup_table") + .insert_one( + LookupEntry { + topic: subscriber_topic.to_string(), + project_id: project_id.to_string(), + account: account_id.to_string(), + expiry: 100, + }, + None, + ) + .await + .unwrap(); + + let account_id2: AccountId = "eip155:1:0xEEE".into(); + let subscriber_sym_key2 = hex::encode([1u8; 32]); + let subscriber_topic2: Topic = "subscriber_topic2".into(); + let subcriber_scope2 = HashSet::from(["scope12".to_string(), "scope22".to_string()]); + let client_data2 = ClientData { + id: account_id2.to_string(), + relay_url: "relay_url".to_string(), + sym_key: subscriber_sym_key2.to_string(), + scope: subcriber_scope2.clone(), + expiry: 100, + }; + mongodb + .collection::(project_id.as_ref()) + .insert_one(client_data2, None) + .await + .unwrap(); + mongodb + .collection::("lookup_table") + .insert_one( + LookupEntry { + topic: subscriber_topic2.to_string(), + project_id: project_id.to_string(), + account: account_id2.to_string(), + expiry: 100, + }, + None, + ) + .await + .unwrap(); + + assert_eq!( + mongodb + .collection::(project_id.as_ref()) + .count_documents(doc! {}, None) + .await + .unwrap(), + 2 + ); + assert_eq!( + mongodb + .collection::("lookup_table") + .count_documents(doc! {}, None) + .await + .unwrap(), + 2 + ); + + migrate::migrate(&mongodb, &postgres).await.unwrap(); + + assert_eq!( + mongodb + .collection::(project_id.as_ref()) + .count_documents(doc! {}, None) + .await + .unwrap(), + 0 + ); + assert_eq!( + mongodb + .collection::("lookup_table") + .count_documents(doc! {}, None) + .await + .unwrap(), + 0 + ); + + let project = get_project_by_project_id(project_id.clone(), &postgres) + .await + .unwrap(); + + assert_eq!( + get_subscriber_topics(&postgres) + .await + .unwrap() + .into_iter() + .collect::>(), + HashSet::from([subscriber_topic.clone(), subscriber_topic2.clone()]) + ); + + let subscriber = get_subscriber_by_topic(subscriber_topic.clone(), &postgres) + .await + .unwrap(); + assert_eq!(subscriber.project, project.id); + assert_eq!(subscriber.account, account_id); + assert_eq!(subscriber.sym_key, subscriber_sym_key); + assert_eq!(subscriber.topic, subscriber_topic); + assert_eq!( + subscriber.scope.into_iter().collect::>(), + subcriber_scope + ); + assert!(subscriber.expiry > Utc::now() + Duration::days(29)); + + let subscriber = get_subscriber_by_topic(subscriber_topic2.clone(), &postgres) + .await + .unwrap(); + assert_eq!(subscriber.project, project.id); + assert_eq!(subscriber.account, account_id2); + assert_eq!(subscriber.sym_key, subscriber_sym_key2); + assert_eq!(subscriber.topic, subscriber_topic2); + assert_eq!( + subscriber.scope.into_iter().collect::>(), + subcriber_scope2 + ); + assert!(subscriber.expiry > Utc::now() + Duration::days(29)); + + let subscribers = get_subscribers_for_project_in( + project.id, + &[account_id.clone(), account_id2.clone()], + &postgres, + ) + .await + .unwrap(); + assert_eq!(subscribers.len(), 2); + for subscriber in subscribers { + if subscriber.account == account_id { + assert_eq!(subscriber.project, project.id); + assert_eq!(subscriber.sym_key, subscriber_sym_key); + assert_eq!(subscriber.topic, subscriber_topic); + assert_eq!( + subscriber.scope.iter().cloned().collect::>(), + subcriber_scope + ); + assert!(subscriber.expiry > Utc::now() + Duration::days(29)); + } else { + assert_eq!(subscriber.project, project.id); + assert_eq!(subscriber.sym_key, subscriber_sym_key2); + assert_eq!(subscriber.topic, subscriber_topic2); + assert_eq!( + subscriber.scope.iter().cloned().collect::>(), + subcriber_scope2 + ); + assert!(subscriber.expiry > Utc::now() + Duration::days(29)); + } + } + + let accounts = get_subscriber_accounts_by_project_id(project_id.clone(), &postgres) + .await + .unwrap(); + assert_eq!( + accounts.iter().cloned().collect::>(), + HashSet::from([account_id.clone(), account_id2.clone()]) + ); + + let subscribers = get_subscriptions_by_account(account_id.clone(), &postgres) + .await + .unwrap(); + assert_eq!(subscribers.len(), 1); + let subscriber = &subscribers[0]; + assert_eq!(subscriber.app_domain, project.app_domain); + assert_eq!(subscriber.account, account_id); + assert_eq!(subscriber.sym_key, subscriber_sym_key); + assert_eq!( + subscriber.scope.iter().cloned().collect::>(), + subcriber_scope + ); + assert!(subscriber.expiry > Utc::now() + Duration::days(29)); + + let subscribers = get_subscriptions_by_account(account_id2.clone(), &postgres) + .await + .unwrap(); + assert_eq!(subscribers.len(), 1); + let subscriber = &subscribers[0]; + assert_eq!(subscriber.app_domain, project.app_domain); + assert_eq!(subscriber.account, account_id2); + assert_eq!(subscriber.sym_key, subscriber_sym_key2); + assert_eq!( + subscriber.scope.iter().cloned().collect::>(), + subcriber_scope2 + ); + assert!(subscriber.expiry > Utc::now() + Duration::days(29)); +} + +#[tokio::test] +async fn test_one_subscriber_two_projects() { + let (mongodb, postgres) = get_dbs().await; + + let topic: Topic = "project_topic".into(); + let project_id: ProjectId = "project_id".into(); + let signing_secret = "signing_secret"; + let signing_public = "signing_public"; + let identity_secret = "identity_secret"; + let identity_public = "identity_public"; + let app_domain = "app.example.com"; + mongodb + .collection::("project_data") + .insert_one( + ProjectData { + id: project_id.to_string(), + signing_keypair: Keypair { + private_key: signing_secret.to_string(), + public_key: signing_public.to_string(), + }, + identity_keypair: Keypair { + private_key: identity_secret.to_string(), + public_key: identity_public.to_string(), + }, + app_domain: app_domain.to_string(), + topic: topic.to_string(), + }, + None, + ) + .await + .unwrap(); + + let topic2: Topic = "project_topic2".into(); + let project_id2: ProjectId = "project_id2".into(); + let signing_secret2 = "signing_secret2"; + let signing_public2 = "signing_public2"; + let identity_secret2 = "identity_secret2"; + let identity_public2 = "identity_public2"; + let app_domain2 = "app2.example.com"; + mongodb + .collection::("project_data") + .insert_one( + ProjectData { + id: project_id2.to_string(), + signing_keypair: Keypair { + private_key: signing_secret2.to_string(), + public_key: signing_public2.to_string(), + }, + identity_keypair: Keypair { + private_key: identity_secret2.to_string(), + public_key: identity_public2.to_string(), + }, + app_domain: app_domain2.to_string(), + topic: topic2.to_string(), + }, + None, + ) + .await + .unwrap(); + + let account_id: AccountId = "eip155:1:0xfff".into(); + let subscriber_sym_key = hex::encode([0u8; 32]); + let subscriber_topic: Topic = "subscriber_topic".into(); + let subcriber_scope = HashSet::from(["scope1".to_string(), "scope2".to_string()]); + let client_data = ClientData { + id: account_id.to_string(), + relay_url: "relay_url".to_string(), + sym_key: subscriber_sym_key.to_string(), + scope: subcriber_scope.clone(), + expiry: 100, + }; + mongodb + .collection::(project_id.as_ref()) + .insert_one(client_data, None) + .await + .unwrap(); + mongodb + .collection::("lookup_table") + .insert_one( + LookupEntry { + topic: subscriber_topic.to_string(), + project_id: project_id.to_string(), + account: account_id.to_string(), + expiry: 100, + }, + None, + ) + .await + .unwrap(); + let subscriber_sym_key2 = hex::encode([1u8; 32]); + let subscriber_topic2: Topic = "subscriber_topic2".into(); + let subcriber_scope2 = HashSet::from(["scope12".to_string(), "scope22".to_string()]); + let client_data2 = ClientData { + id: account_id.to_string(), + relay_url: "relay_url".to_string(), + sym_key: subscriber_sym_key2.to_string(), + scope: subcriber_scope2.clone(), + expiry: 100, + }; + mongodb + .collection::(project_id2.as_ref()) + .insert_one(client_data2, None) + .await + .unwrap(); + mongodb + .collection::("lookup_table") + .insert_one( + LookupEntry { + topic: subscriber_topic2.to_string(), + project_id: project_id2.to_string(), + account: account_id.to_string(), + expiry: 100, + }, + None, + ) + .await + .unwrap(); + assert_eq!( + mongodb + .collection::(project_id.as_ref()) + .count_documents(doc! {}, None) + .await + .unwrap(), + 1 + ); + assert_eq!( + mongodb + .collection::(project_id2.as_ref()) + .count_documents(doc! {}, None) + .await + .unwrap(), + 1 + ); + assert_eq!( + mongodb + .collection::("lookup_table") + .count_documents(doc! {}, None) + .await + .unwrap(), + 2 + ); + + migrate::migrate(&mongodb, &postgres).await.unwrap(); + + assert_eq!( + mongodb + .collection::(project_id.as_ref()) + .count_documents(doc! {}, None) + .await + .unwrap(), + 0 + ); + assert_eq!( + mongodb + .collection::("lookup_table") + .count_documents(doc! {}, None) + .await + .unwrap(), + 0 + ); + + let project = get_project_by_project_id(project_id.clone(), &postgres) + .await + .unwrap(); + let project2 = get_project_by_project_id(project_id2.clone(), &postgres) + .await + .unwrap(); + + assert_eq!( + get_subscriber_topics(&postgres) + .await + .unwrap() + .into_iter() + .collect::>(), + HashSet::from([subscriber_topic.clone(), subscriber_topic2.clone()]) + ); + + let subscriber = get_subscriber_by_topic(subscriber_topic.clone(), &postgres) + .await + .unwrap(); + assert_eq!(subscriber.project, project.id); + assert_eq!(subscriber.account, account_id); + assert_eq!(subscriber.sym_key, subscriber_sym_key); + assert_eq!(subscriber.topic, subscriber_topic); + assert_eq!( + subscriber.scope.into_iter().collect::>(), + subcriber_scope + ); + assert!(subscriber.expiry > Utc::now() + Duration::days(29)); + + let subscriber = get_subscriber_by_topic(subscriber_topic2.clone(), &postgres) + .await + .unwrap(); + assert_eq!(subscriber.project, project2.id); + assert_eq!(subscriber.account, account_id); + assert_eq!(subscriber.sym_key, subscriber_sym_key2); + assert_eq!(subscriber.topic, subscriber_topic2); + assert_eq!( + subscriber.scope.into_iter().collect::>(), + subcriber_scope2 + ); + assert!(subscriber.expiry > Utc::now() + Duration::days(29)); + + let subscribers = get_subscribers_for_project_in(project.id, &[account_id.clone()], &postgres) + .await + .unwrap(); + assert_eq!(subscribers.len(), 1); + let subscriber = &subscribers[0]; + assert_eq!(subscriber.project, project.id); + assert_eq!(subscriber.account, account_id); + assert_eq!(subscriber.sym_key, subscriber_sym_key); + assert_eq!(subscriber.topic, subscriber_topic); + assert_eq!( + subscriber.scope.iter().cloned().collect::>(), + subcriber_scope + ); + assert!(subscriber.expiry > Utc::now() + Duration::days(29)); + + let subscribers = get_subscribers_for_project_in(project2.id, &[account_id.clone()], &postgres) + .await + .unwrap(); + assert_eq!(subscribers.len(), 1); + let subscriber = &subscribers[0]; + assert_eq!(subscriber.project, project2.id); + assert_eq!(subscriber.account, account_id); + assert_eq!(subscriber.sym_key, subscriber_sym_key2); + assert_eq!(subscriber.topic, subscriber_topic2); + assert_eq!( + subscriber.scope.iter().cloned().collect::>(), + subcriber_scope2 + ); + assert!(subscriber.expiry > Utc::now() + Duration::days(29)); + + let accounts = get_subscriber_accounts_by_project_id(project_id.clone(), &postgres) + .await + .unwrap(); + assert_eq!(accounts, vec![account_id.clone()]); + let accounts = get_subscriber_accounts_by_project_id(project_id2.clone(), &postgres) + .await + .unwrap(); + assert_eq!(accounts, vec![account_id.clone()]); + + let subscribers = get_subscriptions_by_account(account_id.clone(), &postgres) + .await + .unwrap(); + assert_eq!(subscribers.len(), 2); + for subscriber in subscribers { + if subscriber.app_domain == app_domain { + assert_eq!(subscriber.app_domain, app_domain); + assert_eq!(subscriber.account, account_id); + assert_eq!(subscriber.sym_key, subscriber_sym_key); + assert_eq!( + subscriber.scope.iter().cloned().collect::>(), + subcriber_scope + ); + assert!(subscriber.expiry > Utc::now() + Duration::days(29)); + } else { + assert_eq!(subscriber.app_domain, app_domain2); + assert_eq!(subscriber.account, account_id); + assert_eq!(subscriber.sym_key, subscriber_sym_key2); + assert_eq!( + subscriber.scope.iter().cloned().collect::>(), + subcriber_scope2 + ); + assert!(subscriber.expiry > Utc::now() + Duration::days(29)); + } + } +} + +#[tokio::test] +async fn test_call_migrate_twice() { + let (mongodb, postgres) = get_dbs().await; + + let topic: Topic = "project_topic".into(); + let project_id: ProjectId = "project_id".into(); + let signing_secret = "signing_secret"; + let signing_public = "signing_public"; + let identity_secret = "identity_secret"; + let identity_public = "identity_public"; + let app_domain = "app.example.com"; + mongodb + .collection::("project_data") + .insert_one( + ProjectData { + id: project_id.to_string(), + signing_keypair: Keypair { + private_key: signing_secret.to_string(), + public_key: signing_public.to_string(), + }, + identity_keypair: Keypair { + private_key: identity_secret.to_string(), + public_key: identity_public.to_string(), + }, + app_domain: app_domain.to_string(), + topic: topic.to_string(), + }, + None, + ) + .await + .unwrap(); + + let topic2: Topic = "project_topic2".into(); + let project_id2: ProjectId = "project_id2".into(); + let signing_secret2 = "signing_secret2"; + let signing_public2 = "signing_public2"; + let identity_secret2 = "identity_secret2"; + let identity_public2 = "identity_public2"; + let app_domain2 = "app2.example.com"; + mongodb + .collection::("project_data") + .insert_one( + ProjectData { + id: project_id2.to_string(), + signing_keypair: Keypair { + private_key: signing_secret2.to_string(), + public_key: signing_public2.to_string(), + }, + identity_keypair: Keypair { + private_key: identity_secret2.to_string(), + public_key: identity_public2.to_string(), + }, + app_domain: app_domain2.to_string(), + topic: topic2.to_string(), + }, + None, + ) + .await + .unwrap(); + + let account_id: AccountId = "eip155:1:0xfff".into(); + let subscriber_sym_key = hex::encode([0u8; 32]); + let subscriber_topic: Topic = "subscriber_topic".into(); + let subcriber_scope = HashSet::from(["scope1".to_string(), "scope2".to_string()]); + let client_data = ClientData { + id: account_id.to_string(), + relay_url: "relay_url".to_string(), + sym_key: subscriber_sym_key.to_string(), + scope: subcriber_scope.clone(), + expiry: 100, + }; + mongodb + .collection::(project_id.as_ref()) + .insert_one(client_data, None) + .await + .unwrap(); + mongodb + .collection::("lookup_table") + .insert_one( + LookupEntry { + topic: subscriber_topic.to_string(), + project_id: project_id.to_string(), + account: account_id.to_string(), + expiry: 100, + }, + None, + ) + .await + .unwrap(); + let subscriber_sym_key2 = hex::encode([1u8; 32]); + let subscriber_topic2: Topic = "subscriber_topic2".into(); + let subcriber_scope2 = HashSet::from(["scope12".to_string(), "scope22".to_string()]); + let client_data2 = ClientData { + id: account_id.to_string(), + relay_url: "relay_url".to_string(), + sym_key: subscriber_sym_key2.to_string(), + scope: subcriber_scope2.clone(), + expiry: 100, + }; + mongodb + .collection::(project_id2.as_ref()) + .insert_one(client_data2, None) + .await + .unwrap(); + mongodb + .collection::("lookup_table") + .insert_one( + LookupEntry { + topic: subscriber_topic2.to_string(), + project_id: project_id2.to_string(), + account: account_id.to_string(), + expiry: 100, + }, + None, + ) + .await + .unwrap(); + assert_eq!( + mongodb + .collection::(project_id.as_ref()) + .count_documents(doc! {}, None) + .await + .unwrap(), + 1 + ); + assert_eq!( + mongodb + .collection::(project_id2.as_ref()) + .count_documents(doc! {}, None) + .await + .unwrap(), + 1 + ); + assert_eq!( + mongodb + .collection::("lookup_table") + .count_documents(doc! {}, None) + .await + .unwrap(), + 2 + ); + + migrate::migrate(&mongodb, &postgres).await.unwrap(); + + // Call it again to make sure this is idempotent + migrate::migrate(&mongodb, &postgres).await.unwrap(); + + assert_eq!( + mongodb + .collection::(project_id.as_ref()) + .count_documents(doc! {}, None) + .await + .unwrap(), + 0 + ); + assert_eq!( + mongodb + .collection::("lookup_table") + .count_documents(doc! {}, None) + .await + .unwrap(), + 0 + ); + + let project = get_project_by_project_id(project_id.clone(), &postgres) + .await + .unwrap(); + let project2 = get_project_by_project_id(project_id2.clone(), &postgres) + .await + .unwrap(); + + assert_eq!( + get_subscriber_topics(&postgres) + .await + .unwrap() + .into_iter() + .collect::>(), + HashSet::from([subscriber_topic.clone(), subscriber_topic2.clone()]) + ); + + let subscriber = get_subscriber_by_topic(subscriber_topic.clone(), &postgres) + .await + .unwrap(); + assert_eq!(subscriber.project, project.id); + assert_eq!(subscriber.account, account_id); + assert_eq!(subscriber.sym_key, subscriber_sym_key); + assert_eq!(subscriber.topic, subscriber_topic); + assert_eq!( + subscriber.scope.into_iter().collect::>(), + subcriber_scope + ); + assert!(subscriber.expiry > Utc::now() + Duration::days(29)); + + let subscriber = get_subscriber_by_topic(subscriber_topic2.clone(), &postgres) + .await + .unwrap(); + assert_eq!(subscriber.project, project2.id); + assert_eq!(subscriber.account, account_id); + assert_eq!(subscriber.sym_key, subscriber_sym_key2); + assert_eq!(subscriber.topic, subscriber_topic2); + assert_eq!( + subscriber.scope.into_iter().collect::>(), + subcriber_scope2 + ); + assert!(subscriber.expiry > Utc::now() + Duration::days(29)); + + let subscribers = get_subscribers_for_project_in(project.id, &[account_id.clone()], &postgres) + .await + .unwrap(); + assert_eq!(subscribers.len(), 1); + let subscriber = &subscribers[0]; + assert_eq!(subscriber.project, project.id); + assert_eq!(subscriber.account, account_id); + assert_eq!(subscriber.sym_key, subscriber_sym_key); + assert_eq!(subscriber.topic, subscriber_topic); + assert_eq!( + subscriber.scope.iter().cloned().collect::>(), + subcriber_scope + ); + assert!(subscriber.expiry > Utc::now() + Duration::days(29)); + + let subscribers = get_subscribers_for_project_in(project2.id, &[account_id.clone()], &postgres) + .await + .unwrap(); + assert_eq!(subscribers.len(), 1); + let subscriber = &subscribers[0]; + assert_eq!(subscriber.project, project2.id); + assert_eq!(subscriber.account, account_id); + assert_eq!(subscriber.sym_key, subscriber_sym_key2); + assert_eq!(subscriber.topic, subscriber_topic2); + assert_eq!( + subscriber.scope.iter().cloned().collect::>(), + subcriber_scope2 + ); + assert!(subscriber.expiry > Utc::now() + Duration::days(29)); + + let accounts = get_subscriber_accounts_by_project_id(project_id.clone(), &postgres) + .await + .unwrap(); + assert_eq!(accounts, vec![account_id.clone()]); + let accounts = get_subscriber_accounts_by_project_id(project_id2.clone(), &postgres) + .await + .unwrap(); + assert_eq!(accounts, vec![account_id.clone()]); + + let subscribers = get_subscriptions_by_account(account_id.clone(), &postgres) + .await + .unwrap(); + assert_eq!(subscribers.len(), 2); + for subscriber in subscribers { + if subscriber.app_domain == app_domain { + assert_eq!(subscriber.app_domain, app_domain); + assert_eq!(subscriber.account, account_id); + assert_eq!(subscriber.sym_key, subscriber_sym_key); + assert_eq!( + subscriber.scope.iter().cloned().collect::>(), + subcriber_scope + ); + assert!(subscriber.expiry > Utc::now() + Duration::days(29)); + } else { + assert_eq!(subscriber.app_domain, app_domain2); + assert_eq!(subscriber.account, account_id); + assert_eq!(subscriber.sym_key, subscriber_sym_key2); + assert_eq!( + subscriber.scope.iter().cloned().collect::>(), + subcriber_scope2 + ); + assert!(subscriber.expiry > Utc::now() + Duration::days(29)); + } + } +}