diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 0000000..11ab0ed --- /dev/null +++ b/.cargo/config @@ -0,0 +1,3 @@ +[registries.CLOUDSMITH] +index = "sparse+https://cargo.cloudsmith.io/integrationos/repository/" +credential-provider = "cargo:token" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..feb9533 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,72 @@ +name: CI +on: pull_request + +env: + CARGO_REGISTRIES_CLOUDSMITH_TOKEN: ${{ secrets.CLOUDSMITH_API_KEY }} +jobs: + fmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - uses: Swatinem/rust-cache@v2 + env: + CARGO_REGISTRIES_CLOUDSMITH_TOKEN: ${{ secrets.CLOUDSMITH_API_KEY }} + id: rust-cache + - name: Check for cache hit + run: echo "cache-hit=${{ steps.rust-cache.outputs.cache-hit }}" + - run: cargo fmt --check + + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install protoc + run: sudo apt-get update && sudo apt-get install -y protobuf-compiler + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + env: + CARGO_REGISTRIES_CLOUDSMITH_TOKEN: ${{ secrets.CLOUDSMITH_API_KEY }} + id: rust-cache + - name: Check for cache hit + run: echo "cache-hit=${{ steps.rust-cache.outputs.cache-hit }}" + - run: cargo check + + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install protoc + run: sudo apt-get update && sudo apt-get install -y protobuf-compiler + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - uses: Swatinem/rust-cache@v2 + env: + CARGO_REGISTRIES_CLOUDSMITH_TOKEN: ${{ secrets.CLOUDSMITH_API_KEY }} + id: rust-cache + - name: Check for cache hit + run: echo "cache-hit=${{ steps.rust-cache.outputs.cache-hit }}" + - run: cargo clippy + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Starting up Docker 🐳 + run: docker compose -f resource/docker-compose.test.yaml up -d + - uses: actions/checkout@v3 + - name: Install protoc + run: sudo apt-get update && sudo apt-get install -y protobuf-compiler + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + env: + CARGO_REGISTRIES_CLOUDSMITH_TOKEN: ${{ secrets.CLOUDSMITH_API_KEY }} + id: rust-cache + - name: Check for cache hit + run: echo "cache-hit=${{ steps.rust-cache.outputs.cache-hit }}" + - run: cargo test + env: + MARK_FLAKY_TESTS_STRICT: "true" diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml new file mode 100644 index 0000000..b942b89 --- /dev/null +++ b/.github/workflows/deployment.yml @@ -0,0 +1,38 @@ +name: Deployment +on: + push: + branches: + - main + tags: + - "[0-9]+.[0-9]+.[0-9]+" + +env: + docker_image_tag: ${{ github.ref == 'refs/heads/main' && github.sha || github.ref_name }} + +jobs: + build: + runs-on: ubuntu-latest + + permissions: + contents: read + id-token: write + + steps: + - uses: actions/checkout@v3 + - uses: integration-os/google-artifact-registry-action@v1 + with: + image: "us-east4-docker.pkg.dev/buildable-production/event-docker/oauth:${{ env.docker_image_tag }}" + build-args: "CARGO_REGISTRIES_CLOUDSMITH_TOKEN=${{ secrets.CLOUDSMITH_API_KEY }}" + + deploy: + if: ${{ github.ref == 'refs/heads/main' }} + needs: build + runs-on: ubuntu-latest + + steps: + - uses: integration-os/development-environment-deploy-action@v1 + with: + ssh_key: ${{ secrets.INFRASTRUCTURE_DEPLOY_KEY }} + service_name: oauth-api + yaml_path: .event.services.oauthApi.image.tag + docker_image_tag: ${{ env.docker_image_tag }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8c15851 --- /dev/null +++ b/.gitignore @@ -0,0 +1,208 @@ +package-lock.json + +# Docker Cleanup +cleanup.sh + +# Json files +connection_oauth_definition.json + +# Editor : JetBrains +.idea/ + +# Editor : VS Code +.vscode/ + +# Created by https://www.toptal.com/developers/gitignore/api/node +# Edit at https://www.toptal.com/developers/gitignore?templates=node + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# End of https://www.toptal.com/developers/gitignore/api/node + +# Created by https://www.toptal.com/developers/gitignore/api/macos +# Edit at https://www.toptal.com/developers/gitignore?templates=macos + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +# End of https://www.toptal.com/developers/gitignore/api/macos + +# Created by https://www.toptal.com/developers/gitignore/api/windows +# Edit at https://www.toptal.com/developers/gitignore?templates=windows + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/windows + +#Added by cargo + +target/ + +.pnp.* +.yarn/ + +*.node + +*/generated_test_output/* diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..6241047 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4082 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "actix" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cba56612922b907719d4a01cf11c8d5b458e7d3dba946d0435f20f58d6795ed2" +dependencies = [ + "actix-macros", + "actix-rt", + "actix_derive", + "bitflags 2.4.2", + "bytes", + "crossbeam-channel", + "futures-core", + "futures-sink", + "futures-task", + "futures-util", + "log", + "once_cell", + "parking_lot", + "pin-project-lite", + "smallvec", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags 2.4.2", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-cors" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e772b3bcafe335042b5db010ab7c09013dad6eac4915c91d8d50902769f331" +dependencies = [ + "actix-utils", + "actix-web", + "derive_more", + "futures-util", + "log", + "once_cell", + "smallvec", +] + +[[package]] +name = "actix-governor" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2e7b88f3804e01bd4191fdb08650430bbfcb43d3d9b2890064df3551ec7d25b" +dependencies = [ + "actix-http", + "actix-web", + "futures", + "governor", +] + +[[package]] +name = "actix-http" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d223b13fd481fc0d1f83bb12659ae774d9e3601814c68a0bc539731698cca743" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash", + "base64 0.21.7", + "bitflags 2.4.2", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.48", +] + +[[package]] +name = "actix-router" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22475596539443685426b6bdadb926ad0ecaefdfc5fb05e5e3441f15463c511" +dependencies = [ + "bytestring", + "http", + "regex", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb13e7eef0423ea6eab0e59f6c72e7cb46d33691ad56a726b3cd07ddec2c2d4" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2 0.5.5", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a6556ddebb638c2358714d853257ed226ece6023ef9364f23f0c70737ea984" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "ahash", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "futures-core", + "futures-util", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.5.5", + "time", + "url", +] + +[[package]] +name = "actix-web-actors" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf6e9ccc371cfddbed7aa842256a4abc7a6dcac9f3fce392fe1d0f68cfd136b2" +dependencies = [ + "actix", + "actix-codec", + "actix-http", + "actix-web", + "bytes", + "bytestring", + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-web-codegen" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1f50ebbb30eca122b188319a4398b3f7bb4a8cdf50ecfb73bfc6a3c3ce54f5" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "actix-web-lab" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7675c1a84eec1b179c844cdea8488e3e409d8e4984026e92fa96c87dd86f33c6" +dependencies = [ + "actix-http", + "actix-router", + "actix-service", + "actix-utils", + "actix-web", + "actix-web-lab-derive", + "ahash", + "arc-swap", + "async-trait", + "bytes", + "bytestring", + "csv", + "derive_more", + "futures-core", + "futures-util", + "http", + "impl-more", + "itertools 0.12.1", + "local-channel", + "mediatype", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "serde", + "serde_html_form", + "serde_json", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "actix-web-lab-derive" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa0b287c8de4a76b691f29dbb5451e8dd5b79d777eaf87350c9b0cbfdb5e968" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "actix_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c7db3d5a9718568e4cf4a537cfd7070e6e6ff7481510d0237fb529ac850f6d3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "ahash" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" + +[[package]] +name = "arc-swap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-recursion" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "async-trait" +version = "0.1.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bson" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce21468c1c9c154a85696bb25c20582511438edb6ad67f846ba1378ffdd80222" +dependencies = [ + "ahash", + "base64 0.13.1", + "bitvec", + "hex", + "indexmap", + "js-sys", + "once_cell", + "rand", + "serde", + "serde_bytes", + "serde_json", + "time", + "uuid", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytecount" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "bytestring" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72" +dependencies = [ + "bytes", +] + +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ceed8ef69d8518a5dda55c07425450b58a4e1946f4951eab6d7191ee86c2443d" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.21", + "serde", + "serde_json", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.52.0", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cooked-waker" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147be55d677052dabc6b22252d5dd0fd4c29c8c27aa4f2fbef0f94aa003b406f" + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core 0.13.4", + "darling_macro 0.13.4", +] + +[[package]] +name = "darling" +version = "0.20.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc5d6b04b3fd0ba9926f945895de7d806260a2d7431ba82e7edaecb043c4c6b8" +dependencies = [ + "darling_core 0.20.5", + "darling_macro 0.20.5", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_core" +version = "0.20.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e48a959bcd5c761246f5d090ebc2fbf7b9cd527a492b07a67510c108f1e7e3" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.48", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core 0.13.4", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.20.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1545d67a2149e1d93b7e5c7752dce5a7426eb5d1357ddcfd89336b94444f77" +dependencies = [ + "darling_core 0.20.5", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "serde", + "uuid", +] + +[[package]] +name = "deno_core" +version = "0.238.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ded8b759e4435aa0929913909dd6c482ed6042dae19c53260e1caf9d55b37a9" +dependencies = [ + "anyhow", + "bytes", + "cooked-waker", + "deno_ops", + "deno_unsync", + "futures", + "libc", + "log", + "parking_lot", + "pin-project", + "serde", + "serde_json", + "serde_v8", + "smallvec", + "sourcemap", + "static_assertions", + "tokio", + "url", + "v8", +] + +[[package]] +name = "deno_ops" +version = "0.114.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168a929496191fdd8e91f898c8454429df4d5489597777d89f47897f6a37da6b" +dependencies = [ + "proc-macro-rules", + "proc-macro2", + "quote", + "strum", + "strum_macros", + "syn 2.0.48", + "thiserror", +] + +[[package]] +name = "deno_unsync" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30dff7e03584dbae188dae96a0f1876740054809b2ad0cf7c9fc5d361f20e739" +dependencies = [ + "tokio", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e3ae26c830a573f2e231fc2475f71fce4705609097cb9523abfc4007caed0b" + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.0", + "syn 1.0.109", +] + +[[package]] +name = "deunicode" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae2a35373c5c74340b79ae6780b498b2b183915ec5dacf263aac5a099bf485a" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "dummy" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e57e12b69e57fad516e01e2b3960f122696fdb13420e1a88ed8e210316f2876" +dependencies = [ + "darling 0.20.5", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum-as-inner" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "envconfig" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea81cc7e21f55a9d9b1efb6816904978d0bfbe31a50347cb24b2e75564bcac9b" +dependencies = [ + "envconfig_derive", +] + +[[package]] +name = "envconfig_derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfca278e5f84b45519acaaff758ebfa01f18e96998bc24b8f1b722dd804b9bf" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "version_check", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fake" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26221445034074d46b276e13eb97a265ebdb8ed8da705c4dddd3dd20b66b45d2" +dependencies = [ + "chrono", + "deunicode", + "dummy", + "http", + "rand", + "semver 1.0.21", + "serde_json", + "url-escape", + "uuid", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "features" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83072b3c84e55f9d0c0ff36a4575d0fd2e543ae4a56e04e7f5a9222188d574e3" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fslock" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57eafdd0c16f57161105ae1b98a1238f97645f2f588438b2949c99a2af9616bf" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "governor" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "821239e5672ff23e2a7060901fa622950bbd80b649cdaadd78d1c1767ed14eb4" +dependencies = [ + "cfg-if", + "dashmap", + "futures", + "futures-timer", + "no-std-compat", + "nonzero_ext", + "parking_lot", + "quanta 0.11.1", + "rand", + "smallvec", +] + +[[package]] +name = "h2" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "handlebars" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faa67bab9ff362228eb3d00bd024a4965d8231bbb7921167f0cfa66c6626b225" +dependencies = [ + "log", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "handlebars" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c73166c591e67fb4bf9bc04011b4e35f12e89fe8d676193aa263df065955a379" +dependencies = [ + "log", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-serde-ext" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94a985b23074de11e6d99712873131c3a3d12cf482406de7e0a2ec8a4cd1943" +dependencies = [ + "http", + "serde", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.5", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + +[[package]] +name = "impl-more" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206ca75c9c03ba3d4ace2460e57b189f39f43de612c2f85836e65c929701bb2d" + +[[package]] +name = "indexmap" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "integrationos-domain" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d59e3478f25e9aefea50eb59f3252988be8c9969bdbd1809c86c8ca7e365ab4" +dependencies = [ + "actix-web", + "aes", + "anyhow", + "async-recursion", + "async-trait", + "base64 0.21.7", + "base64ct", + "bson", + "chrono", + "ctr", + "downcast-rs", + "envconfig", + "fake", + "futures", + "handlebars 4.5.0", + "http", + "http-serde-ext", + "indexmap", + "js-sandbox-ios", + "jsonpath_lib", + "moka", + "mongodb", + "openapiv3", + "pin-project", + "prost", + "rand", + "reqwest", + "semver 1.0.21", + "serde", + "serde_json", + "sha2", + "sha3", + "strum", + "thiserror", + "tokio", + "tracing", + "uuid", +] + +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2 0.5.5", + "widestring", + "windows-sys 0.48.0", + "winreg", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sandbox-ios" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e3dd0f703d6d50aef7889fe55933fa3110e37d97ceeecab2c1c0a9de2f1eeac" +dependencies = [ + "deno_core", + "js-sandbox-macros", + "serde", + "serde_json", +] + +[[package]] +name = "js-sandbox-macros" +version = "0.2.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0445474069bad4c2b01856976d83c796521ae9298ba718ecf26041767eb68b2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "js-sys" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonpath_lib" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" +dependencies = [ + "log", + "serde", + "serde_json", +] + +[[package]] +name = "jsonwebtoken" +version = "9.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7ea04a7c5c055c175f189b6dc6ba036fd62306b58c66c9f6389036c503a3f4" +dependencies = [ + "base64 0.21.7", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + +[[package]] +name = "mark-flaky-tests" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "586c4a7c95a05351b04ebbe4f60edc53c3bb611651b1bf0be54a9575a95b4930" +dependencies = [ + "futures", + "mark-flaky-tests-macro", +] + +[[package]] +name = "mark-flaky-tests-macro" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078b1ffd16b299642ebc515a520c3793f6fe0db38c2e6a2abf686db22e58ab84" +dependencies = [ + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "mediatype" +version = "0.19.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8878cd8d1b3c8c8ae4b2ba0a36652b7cf192f618a599a7fbdfa25cffd4ea72dd" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "moka" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1911e88d5831f748a4097a43862d129e3c6fca831eecac9b8db6d01d93c9de2" +dependencies = [ + "async-lock", + "async-trait", + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "futures-util", + "once_cell", + "parking_lot", + "quanta 0.12.2", + "rustc_version 0.4.0", + "skeptic", + "smallvec", + "tagptr", + "thiserror", + "triomphe", + "uuid", +] + +[[package]] +name = "mongodb" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c30763a5c6c52079602be44fa360ca3bfacee55fca73f4734aecd23706a7f2" +dependencies = [ + "async-trait", + "base64 0.13.1", + "bitflags 1.3.2", + "bson", + "chrono", + "derivative", + "derive_more", + "futures-core", + "futures-executor", + "futures-io", + "futures-util", + "hex", + "hmac", + "lazy_static", + "md-5", + "pbkdf2", + "percent-encoding", + "rand", + "rustc_version_runtime", + "rustls", + "rustls-pemfile", + "serde", + "serde_bytes", + "serde_with", + "sha-1", + "sha2", + "socket2 0.4.10", + "stringprep", + "strsim", + "take_mut", + "thiserror", + "tokio", + "tokio-rustls", + "tokio-util", + "trust-dns-proto", + "trust-dns-resolver", + "typed-builder", + "uuid", + "webpki-roots", +] + +[[package]] +name = "mutually_exclusive_features" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d02c0b00610773bb7fc61d85e13d86c7858cbdf00e1a120bfc41bc055dbaa0e" + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + +[[package]] +name = "nonzero_ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", + "rand", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "oauth-api" +version = "0.1.0" +dependencies = [ + "actix", + "actix-cors", + "actix-governor", + "actix-web", + "actix-web-actors", + "actix-web-lab", + "anyhow", + "chrono", + "derive", + "dotenvy", + "envconfig", + "fake", + "features", + "futures", + "handlebars 5.1.1", + "integrationos-domain", + "jsonwebtoken", + "mark-flaky-tests", + "moka", + "mongodb", + "once_cell", + "rand", + "reqwest", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "tracing-actix-web", + "tracing-bunyan-formatter", + "tracing-log 0.2.0", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openapiv3" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc02deea53ffe807708244e5914f6b099ad7015a207ee24317c22112e17d9c5c" +dependencies = [ + "indexmap", + "serde", + "serde_json", +] + +[[package]] +name = "openssl" +version = "0.10.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", +] + +[[package]] +name = "pem" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" +dependencies = [ + "base64 0.21.7", + "serde", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f200d8d83c44a45b21764d1916299752ca035d15ecd46faca3e9a2a2bf6ad06" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcd6ab1236bbdb3a49027e920e693192ebfe8913f6d60e294de57463a493cfde" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a31940305ffc96863a735bef7c7994a00b325a7138fdbc5bda0f1a0476d3275" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "pest_meta" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7ff62f5259e53b78d1af898941cdcdccfae7385cf7d793a6e55de5d05bb4b7d" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "pin-project" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-rules" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c277e4e643ef00c1233393c673f655e3672cf7eb3ba08a00bdd0ea59139b5f" +dependencies = [ + "proc-macro-rules-macros", + "proc-macro2", + "syn 2.0.48", +] + +[[package]] +name = "proc-macro-rules-macros" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "207fffb0fe655d1d47f6af98cc2793405e85929bdbc420d685554ff07be27ac7" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +dependencies = [ + "anyhow", + "itertools 0.11.0", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "pulldown-cmark" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" +dependencies = [ + "bitflags 2.4.2", + "memchr", + "unicase", +] + +[[package]] +name = "quanta" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17e662a7a8291a865152364c20c7abc5e60486ab2001e8ec10b24862de0b9ab" +dependencies = [ + "crossbeam-utils", + "libc", + "mach2", + "once_cell", + "raw-cpuid 10.7.0", + "wasi", + "web-sys", + "winapi", +] + +[[package]] +name = "quanta" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca0b7bac0b97248c40bb77288fc52029cf1459c0461ea1b05ee32ccf011de2c" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid 11.0.1", + "wasi", + "web-sys", + "winapi", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "raw-cpuid" +version = "11.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d86a7c4638d42c44551f4791a20e687dbb4c3de1f33c43dd71e355cd429def1" +dependencies = [ + "bitflags 2.4.2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.5", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "reqwest" +version = "0.11.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error", +] + +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.21", +] + +[[package]] +name = "rustc_version_runtime" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d31b7153270ebf48bf91c65ae5b0c00e749c4cfad505f66530ac74950249582f" +dependencies = [ + "rustc_version 0.2.3", + "semver 0.9.0", +] + +[[package]] +name = "rustix" +version = "0.38.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "serde_html_form" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20e1066e1cfa6692a722cf40386a2caec36da5ddc4a2c16df592f0f609677e8c" +dependencies = [ + "form_urlencoded", + "indexmap", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_json" +version = "1.0.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_v8" +version = "0.147.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af950d83e1c70b762d48fa7a869d6db9a4f191548dfd666fa4e62f2229e1dce" +dependencies = [ + "bytes", + "derive_more", + "num-bigint", + "serde", + "smallvec", + "thiserror", + "v8", +] + +[[package]] +name = "serde_with" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +dependencies = [ + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +dependencies = [ + "darling 0.13.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + +[[package]] +name = "skeptic" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" +dependencies = [ + "bytecount", + "cargo_metadata", + "error-chain", + "glob", + "pulldown-cmark", + "tempfile", + "walkdir", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "sourcemap" +version = "7.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10da010a590ed2fa9ca8467b00ce7e9c5a8017742c0c09c45450efc172208c4b" +dependencies = [ + "data-encoding", + "debugid", + "if_chain", + "rustc_version 0.2.3", + "serde", + "serde_json", + "unicode-id", + "url", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stringprep" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" +dependencies = [ + "finl_unicode", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.48", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +dependencies = [ + "deranged", + "itoa", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.35.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.5.5", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-actix-web" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe0d5feac3f4ca21ba33496bcb1ccab58cca6412b1405ae80f0581541e0ca78" +dependencies = [ + "actix-web", + "mutually_exclusive_features", + "pin-project", + "tracing", + "uuid", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tracing-bunyan-formatter" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5c266b9ac83dedf0e0385ad78514949e6d89491269e7065bee51d2bb8ec7373" +dependencies = [ + "ahash", + "gethostname", + "log", + "serde", + "serde_json", + "time", + "tracing", + "tracing-core", + "tracing-log 0.1.4", + "tracing-subscriber", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log 0.2.0", +] + +[[package]] +name = "triomphe" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" + +[[package]] +name = "trust-dns-proto" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c31f240f59877c3d4bb3b3ea0ec5a6a0cff07323580ff8c7a605cd7d08b255d" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.2.3", + "ipnet", + "lazy_static", + "log", + "rand", + "smallvec", + "thiserror", + "tinyvec", + "tokio", + "url", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ba72c2ea84515690c9fcef4c6c660bb9df3036ed1051686de84605b74fd558" +dependencies = [ + "cfg-if", + "futures-util", + "ipconfig", + "lazy_static", + "log", + "lru-cache", + "parking_lot", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "trust-dns-proto", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typed-builder" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-id" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1b6def86329695390197b82c1e244a54a131ceb66c996f2088a3876e2ae083f" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna 0.5.0", + "percent-encoding", + "serde", +] + +[[package]] +name = "url-escape" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44e0ce4d1246d075ca5abec4b41d33e87a6054d08e2366b63205665e950db218" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "uuid" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +dependencies = [ + "atomic", + "getrandom", + "md-5", + "serde", + "sha1_smol", +] + +[[package]] +name = "v8" +version = "0.82.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f53dfb242f4c0c39ed3fc7064378a342e57b5c9bd774636ad34ffe405b808121" +dependencies = [ + "bitflags 1.3.2", + "fslock", + "once_cell", + "which", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.48", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" + +[[package]] +name = "web-sys" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "widestring" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "winnow" +version = "0.5.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cad8365489051ae9f054164e459304af2e7e9bb407c958076c8bf4aef52da5" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "zstd" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..585bcfc --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "oauth-api" +version = "0.1.0" +edition = "2021" + +[profile.release] +lto = "thin" + +[dependencies] +actix = "0.13.1" +actix-cors = "0.7.0" +actix-governor = "0.5.0" +actix-web = "4.5.1" +actix-web-actors = "4.2.0" +actix-web-lab = "0.20.2" +anyhow = "1.0.79" +chrono = { version = "0.4.33", features = ["serde"] } +dotenvy = "0.15.7" +envconfig = "0.10.0" +futures = "0.3.30" +handlebars = "5.1.1" +integrationos-domain = { version = "0.1.3", features = ["dummy", "actix-error"] } +jsonwebtoken = "9.2.0" +moka = { version = "0.12.5", features = ["future"] } +mongodb = "2.8.0" +reqwest = "0.11.24" +serde = { version = "1.0.196", features = ["derive"] } +serde_json = "1.0.113" +thiserror = "1.0.56" +tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"] } +tracing = { version = "0.1.40", features = ["log"] } +tracing-actix-web = "0.7.9" +tracing-bunyan-formatter = "0.3.9" +tracing-log = "0.2.0" +tracing-subscriber = { version = "0.3.18", features = ["registry", "env-filter"] } + +[lib] +path = "src/lib.rs" + +[[bin]] +name = "oauth-api" +path = "src/main.rs" + +[dev-dependencies] +derive = "1.0.0" +fake = { version = "=2.9.1", features = ["dummy"] } +features = "0.10.0" +mark-flaky-tests = { version = "1.0.2", features = ["tokio"] } +once_cell = "1.19.0" +rand = "0.8.5" +uuid = { version = "1.7.0", features = ["v4"] } diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..befe68e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef +WORKDIR /app/oauth-api + +FROM chef AS planner +COPY . . +RUN cargo chef prepare --recipe-path recipe.json + +FROM chef AS builder +COPY --from=planner /app/oauth-api/recipe.json recipe.json +# Build dependencies - this is the caching Docker layer! +RUN cargo chef cook --release --recipe-path recipe.json --bin oauth-api +# Build application +COPY . . +RUN cargo build --release --bin oauth-api + +FROM debian:bookworm-slim AS runtime +RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/* +WORKDIR /app +COPY --from=builder /app/oauth-api/target/release/oauth-api /usr/local/bin +ENTRYPOINT /usr/local/bin/oauth-api diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e72bfdd --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ccc72a7 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# OAuth API + +This is an API that uses OAuth2.0 blueprints to both refresh tokens and provide user authentication +for more complex workflows (like Xero OAuth). diff --git a/resource/docker-compose.test.yaml b/resource/docker-compose.test.yaml new file mode 100644 index 0000000..fd26609 --- /dev/null +++ b/resource/docker-compose.test.yaml @@ -0,0 +1,26 @@ +version: "3.9" + +services: + mongo: + image: mongo + restart: always + networks: + - oauth + ports: + - "27017:27017" + + mockserver: + restart: always + image: mockserver/mockserver:latest + environment: + MOCKSERVER_INITIALIZATION_JSON_PATH: /resource/mockserver.json + volumes: + - .:/resource + networks: + - oauth + ports: + - "1080:1080" + command: -logLevel DEBUG -serverPort 1080 + +networks: + oauth: diff --git a/resource/mockserver.json b/resource/mockserver.json new file mode 100644 index 0000000..5919d4c --- /dev/null +++ b/resource/mockserver.json @@ -0,0 +1,12 @@ +[ + { + "httpRequest": { + "path": "/health", + "method": "GET" + }, + "httpResponse": { + "body": "{\"message\":\"success\"}", + "statusCode": 200 + } + } +] diff --git a/src/algebra/mod.rs b/src/algebra/mod.rs new file mode 100644 index 0000000..e3eec19 --- /dev/null +++ b/src/algebra/mod.rs @@ -0,0 +1,48 @@ +mod parameter; +mod refresh; +mod token; +mod trigger; + +pub use parameter::*; +pub use refresh::*; +pub use token::*; +pub use trigger::*; + +use chrono::{DateTime, Utc}; +use integrationos_domain::{ + algebra::adapter::StoreAdapter, error::IntegrationOSError as Error, mongo::MongoDbStore, + Connection, Id, +}; +use mongodb::bson::doc; + +pub async fn get_connections_to_refresh( + collection: &MongoDbStore, + refresh_before: &DateTime, + refresh_after: &DateTime, +) -> Result, Error> { + collection + .get_many( + Some(doc! { + "oauth.enabled.expires_at": doc! { + "$gt": refresh_before.timestamp(), + "$lte": refresh_after.timestamp(), + }, + }), + None, + None, + None, + None, + ) + .await +} + +pub async fn get_connection_to_trigger( + collection: &MongoDbStore, + id: Id, +) -> Result, Error> { + collection + .get_one(doc! { + "_id": id.to_string(), + }) + .await +} diff --git a/src/algebra/parameter.rs b/src/algebra/parameter.rs new file mode 100644 index 0000000..6e1bf7e --- /dev/null +++ b/src/algebra/parameter.rs @@ -0,0 +1,207 @@ +use handlebars::Handlebars; +use integrationos_domain::{ + connection_oauth_definition::{Computation, ComputeRequest, ConnectionOAuthDefinition}, + error::IntegrationOSError as Error, + oauth_secret::OAuthSecret, + InternalError, +}; +use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; +use serde_json::Value; +use std::{ + collections::{BTreeMap, HashMap}, + str::FromStr, +}; +use tracing::warn; + +pub trait Parameter { + fn headers(&self, computation: Option<&Computation>) -> Result, Error>; + fn body(&self, secret: &OAuthSecret) -> Result, Error>; + fn query(&self, computation: Option<&Computation>) -> Result, Error>; +} + +impl Parameter for ConnectionOAuthDefinition { + fn headers(&self, computation: Option<&Computation>) -> Result, Error> { + headers(self, computation) + } + + fn body(&self, secret: &OAuthSecret) -> Result, Error> { + body(secret, &self.compute.refresh) + } + + fn query(&self, computation: Option<&Computation>) -> Result, Error> { + query(self, computation) + } +} + +fn body(secret: &OAuthSecret, refresh: &ComputeRequest) -> Result, Error> { + let payload = serde_json::to_value(secret).map_err(|e| { + warn!("Failed to serialize secret: {}", e); + InternalError::encryption_error("Failed to serialize secret", None) + })?; + let computation = refresh + .computation + .clone() + .map(|computation| computation.compute::(&payload)) + .transpose() + .map_err(|e| { + warn!("Failed to compute oauth payload: {}", e); + InternalError::encryption_error("Failed to parse computation payload", None) + })?; + computation + .clone() + .map(|computation| computation.body) + .map(|body| { + let handlebars = Handlebars::new(); + + let body_str = serde_json::to_string_pretty(&body).map_err(|e| { + warn!("Failed to serialize body: {}", e); + InternalError::encryption_error("Failed to serialize body", None) + })?; + + let body = handlebars + .render_template(&body_str, &payload) + .map_err(|e| { + warn!("Failed to render body: {}", e); + InternalError::encryption_error("Failed to render body template", None) + })?; + + serde_json::from_str(&body).map_err(|e| { + warn!("Failed to deserialize body: {}", e); + InternalError::encryption_error("Failed to deserialize body", None) + }) + }) + .transpose() + .map_err(|e| { + warn!("Failed to compute body: {}", e); + InternalError::encryption_error("Failed to compute body", None) + }) +} + +fn query( + definition: &ConnectionOAuthDefinition, + computation: Option<&Computation>, +) -> Result, Error> { + let query_params = definition + .configuration + .refresh + .query_params + .as_ref() + .map(|query_params| { + let mut map = HashMap::new(); + for (key, value) in query_params { + let key = key.to_string(); + let value = value.as_str(); + + map.insert(key, value.to_string()); + } + map + }); + + computation + .and_then(|computation| computation.clone().query_params) + .map(|payload| { + let handlebars = handlebars::Handlebars::new(); + + let query_params_str = serde_json::to_string_pretty(&query_params).map_err(|e| { + warn!("Failed to serialize query params: {}", e); + InternalError::encryption_error("Failed to serialize query params", None) + })?; + + let query_params = handlebars + .render_template(&query_params_str, &payload) + .map_err(|e| { + warn!("Failed to render query params: {}", e); + InternalError::encryption_error("Failed to render query params template", None) + })?; + + let query_params: BTreeMap = serde_json::from_str(&query_params) + .map_err(|e| { + warn!("Failed to deserialize query params: {}", e); + InternalError::encryption_error("Failed to deserialize query params", None) + })?; + + let query_params: Result = Ok(serde_json::to_value(query_params) + .map_err(|e| { + warn!("Failed to serialize query params: {}", e); + InternalError::encryption_error("Failed to serialize query params", None) + })?); + + query_params + }) + .transpose() + .map_err(|e| { + warn!("Failed to compute query params: {}", e); + InternalError::encryption_error("Failed to compute query params", None) + }) +} + +fn headers( + definition: &ConnectionOAuthDefinition, + computation: Option<&Computation>, +) -> Result, Error> { + let headers = definition + .configuration + .refresh + .headers + .as_ref() + .and_then(|headers| { + let mut map = HashMap::new(); + for (key, value) in headers { + let key = key.to_string(); + let value = value.to_str().ok()?; + + map.insert(key, value.to_string()); + } + Some(map) + }); + + computation + .and_then(|computation| computation.clone().headers) + .map(|payload| { + let handlebars = handlebars::Handlebars::new(); + + let headers_str = serde_json::to_string_pretty(&headers).map_err(|e| { + warn!("Failed to serialize headers: {}", e); + InternalError::encryption_error("Failed to serialize headers", None) + })?; + + let headers = handlebars + .render_template(&headers_str, &payload) + .map_err(|e| { + warn!("Failed to render headers: {}", e); + InternalError::encryption_error("Failed to render headers template", None) + })?; + + let headers: BTreeMap = + serde_json::from_str(&headers).map_err(|e| { + warn!("Failed to deserialize headers: {}", e); + InternalError::encryption_error("Failed to deserialize headers", None) + })?; + + let headers: Result = + headers + .iter() + .try_fold(HeaderMap::new(), |mut header_map, (key, value)| { + let key = HeaderName::from_str(key).map_err(|e| { + warn!("Failed to parse header name: {}", e); + InternalError::encryption_error("Failed to parse header name", None) + })?; + + let value = HeaderValue::from_str(value).map_err(|e| { + warn!("Failed to parse header value: {}", e); + InternalError::encryption_error("Failed to parse header value", None) + })?; + + header_map.insert(key, value); + + Ok(header_map) + }); + + headers + }) + .transpose() + .map_err(|e| { + warn!("Failed to compute headers: {}", e); + InternalError::encryption_error("Failed to compute headers", None) + }) +} diff --git a/src/algebra/refresh.rs b/src/algebra/refresh.rs new file mode 100644 index 0000000..c839da4 --- /dev/null +++ b/src/algebra/refresh.rs @@ -0,0 +1,125 @@ +use crate::prelude::{ + get_connections_to_refresh, Query, Refresh, StatefulActor, Trigger, TriggerActor, Unit, +}; +use actix::prelude::*; +use chrono::{Duration, Utc}; +use futures::lock::Mutex; +use integrationos_domain::{ + connection_oauth_definition::ConnectionOAuthDefinition, error::IntegrationOSError as Error, + mongo::MongoDbStore, service::secrets_client::SecretsClient, Connection, InternalError, +}; +use reqwest::Client; +use std::sync::Arc; + +pub struct RefreshActor { + connections: Arc>, + oauths: Arc>, + secrets: Arc, + client: Client, + state: Arc>, +} + +impl RefreshActor { + pub fn new( + oauths: Arc>, + connections: Arc>, + secrets: Arc, + client: Client, + ) -> Self { + Self { + connections, + oauths, + secrets, + client, + state: StatefulActor::empty(), + } + } +} + +impl Actor for RefreshActor { + type Context = Context; + + fn started(&mut self, ctx: &mut Self::Context) { + tracing::info!("RefreshActor started with id {:?}", ctx.address()); + } +} + +impl Supervised for RefreshActor {} + +impl Handler for RefreshActor { + type Result = ResponseFuture>; + + fn handle(&mut self, msg: Refresh, _: &mut Self::Context) -> Self::Result { + let refresh_before = Utc::now(); + let refresh_after = refresh_before + Duration::minutes(msg.refresh_before_in_minutes()); + tracing::info!( + "Searching for connections to refresh between {} and {}", + refresh_before, + refresh_after + ); + + let secrets = self.secrets.clone(); + let client = self.client.clone(); + let connections_store = self.connections.clone(); + let oauths_store = self.oauths.clone(); + let state = self.state.clone(); + + Box::pin(async move { + let connections = + get_connections_to_refresh(&connections_store, &refresh_before, &refresh_after) + .await?; + + tracing::info!("Found {} connections to refresh", connections.len()); + + let mut requests = vec![]; + for connection in &connections { + let trigger_message = Trigger::new(connection.clone()); + let actor = TriggerActor::new( + connections_store.clone(), + oauths_store.clone(), + secrets.clone(), + client.clone(), + None, + ) + .start(); + let future = actor.send(trigger_message); + requests.push(future); + } + + match futures::future::join_all(requests) + .await + .into_iter() + .collect::, _>>() + { + Ok(vec) => { + let vec_as_json = serde_json::to_value(&vec).map_err(|e| { + InternalError::encryption_error( + "Failed to serialize outcome", + Some(e.to_string().as_str()), + ) + })?; + StatefulActor::update(vec_as_json, state).await; + + tracing::info!( + "Refreshed {} connections with outcome: {:?}", + vec.len(), + vec + ); + + Ok(()) + } + Err(err) => Err(InternalError::io_err(err.to_string().as_str(), None)), + } + }) + } +} + +impl Handler for RefreshActor { + type Result = ResponseFuture; + + fn handle(&mut self, _: Query, _: &mut Self::Context) -> Self::Result { + let state = self.state.clone(); + + Box::pin(async move { state.lock().await.clone() }) + } +} diff --git a/src/algebra/token.rs b/src/algebra/token.rs new file mode 100644 index 0000000..b09d570 --- /dev/null +++ b/src/algebra/token.rs @@ -0,0 +1,45 @@ +use crate::prelude::Config; +use chrono::{Duration, Utc}; +use integrationos_domain::{Claims, IntegrationOSError as Error, InternalError}; +use jsonwebtoken::{encode, EncodingKey, Header}; + +pub trait TokenGenerator { + fn generate(&self, configuration: Config, expiration: i64) -> Result; +} + +#[derive(Debug, Default)] +pub struct JwtTokenGenerator; + +impl TokenGenerator for JwtTokenGenerator { + fn generate(&self, configuration: Config, expiration: i64) -> Result { + let key = configuration.server().admin_secret(); + let key = key.as_bytes(); + let key = EncodingKey::from_secret(key); + let now = Utc::now(); + let iat = now.timestamp(); + let exp = (now + Duration::days(expiration)).timestamp(); + let header = Header::default(); + + let claims = Claims { + id: "ADMIN".into(), + email: "admin@integrationos.com".into(), + username: "admin".into(), + user_key: "admin".into(), + first_name: "admin".into(), + last_name: "admin".into(), + buildable_id: "".into(), + container_id: "".into(), + pointers: vec![], + is_buildable_core: false, + iat, + exp, + aud: "integration-team".into(), + iss: "oauth-integrationos".into(), + }; + + let token = encode(&header, &claims, &key) + .map_err(|e| InternalError::encryption_error(e.to_string().as_str(), None))?; + + Ok(token) + } +} diff --git a/src/algebra/trigger.rs b/src/algebra/trigger.rs new file mode 100644 index 0000000..5113f5f --- /dev/null +++ b/src/algebra/trigger.rs @@ -0,0 +1,239 @@ +use super::Parameter; +use crate::prelude::{Outcome, Trigger}; +use actix::prelude::*; +use chrono::Duration; +use integrationos_domain::{ + algebra::adapter::StoreAdapter, + api_model_config::ContentType, + connection_oauth_definition::{Computation, ConnectionOAuthDefinition, OAuthResponse}, + error::IntegrationOSError as Error, + get_secret_request::GetSecretRequest, + mongo::MongoDbStore, + oauth_secret::OAuthSecret, + service::secrets_client::SecretsClient, + ApplicationError, Connection, Id, InternalError, OAuth, +}; +use mongodb::bson::{self, doc}; +use reqwest::Client; +use serde_json::json; +use std::sync::Arc; +use tracing::warn; +use tracing_actix_web::RequestId; + +pub struct TriggerActor { + connections: Arc>, + oauths: Arc>, + secrets_client: Arc, + request_id: Option, + client: Client, +} + +impl TriggerActor { + pub fn new( + connections: Arc>, + oauths: Arc>, + secrets_client: Arc, + client: Client, + request_id: Option, + ) -> Self { + Self { + connections, + oauths, + secrets_client, + request_id, + client, + } + } +} + +impl Actor for TriggerActor { + type Context = Context; + + fn started(&mut self, ctx: &mut Self::Context) { + let request_id = self.request_id.map(|id| id.to_string()); + + tracing::info!( + request_id = request_id.as_deref(), + "TriggerActor started with address: {:?}", + ctx.address() + ); + } +} + +impl Supervised for TriggerActor {} + +impl Handler for TriggerActor { + type Result = ResponseFuture; + + #[tracing::instrument( + name = "TriggerActor handle", skip(self, msg), fields(request_id = self.request_id.map(|id| id.to_string()).as_deref()) + )] + fn handle(&mut self, msg: Trigger, _: &mut Self::Context) -> Self::Result { + let oauths = self.oauths.clone(); + let secrets_client = self.secrets_client.clone(); + let connections = self.connections.clone(); + let client = self.client.clone(); + let request_id = self.request_id.map(|id| id.to_string()); + + let future = async move { + let ask = || async { + let id = match &msg.connection().oauth { + Some(OAuth::Enabled { + connection_oauth_definition_id, + .. + }) => Ok(connection_oauth_definition_id), + _ => Err(ApplicationError::not_found( + format!("Connection {} has no oauth", msg.connection().id).as_str(), + None, + )), + }?; + + let definition = oauths + .get_one(doc! { + "_id": id.to_string(), + }) + .await + .map_err(|e| { + warn!("Failed to get connection oauth definition: {}", e); + ApplicationError::not_found( + format!("Connection oauth definition not found: {}", e).as_str(), + None, + ) + })? + .ok_or(ApplicationError::not_found( + format!("Connection oauth definition not found: {}", id).as_str(), + None, + ))?; + + let secret: OAuthSecret = secrets_client + .get_secret::(&GetSecretRequest { + id: msg.connection().secrets_service_id.clone(), + buildable_id: msg.connection().ownership.client_id.clone(), + }) + .await + .map_err(|e| { + warn!("Failed to get secret: {}", e); + ApplicationError::not_found( + format!("Failed to get secret: {}", e).as_str(), + None, + ) + })?; + + let compute_payload = serde_json::to_value(&secret).map_err(|e| { + warn!("Failed to serialize secret: {}", e); + InternalError::encryption_error("Failed to serialize secret", None) + })?; + + let computation = definition + .compute + .refresh + .computation + .clone() + .map(|computation| computation.compute::(&compute_payload)) + .transpose() + .map_err(|e| { + warn!("Failed to compute oauth payload: {}", e); + InternalError::encryption_error("Failed to parse computation payload", None) + })?; + + let body = definition.body(&secret)?; + let query = definition.query(computation.as_ref())?; + let headers = definition.headers(computation.as_ref())?; + + let request = client + .post(definition.configuration.refresh.uri()) + .headers(headers.unwrap_or_default()); + let request = match definition.configuration.refresh.content { + Some(ContentType::Json) => request.json(&body).query(&query), + Some(ContentType::Form) => request.form(&body).query(&query), + _ => request.query(&query), + } + .build() + .map_err(|e| { + warn!("Failed to build request: {}", e); + InternalError::io_err("Failed to build request", None) + })?; + + let response = client.execute(request).await.map_err(|e| { + warn!("Failed to execute request: {}", e); + InternalError::io_err("Failed to execute request", None) + })?; + + let json = response.json::().await.map_err(|e| { + warn!("Failed to parse response: {}", e); + InternalError::decryption_error("Failed to parse response", None) + })?; + + let decoded: OAuthResponse = definition + .compute + .refresh + .response + .compute(&json) + .map_err(|e| { + warn!("Failed to decode oauth response from {}: {}", json, e); + InternalError::decryption_error("Failed to decode oauth response", None) + })?; + + let oauth_secret = secret.from_refresh(decoded, None, None, json); + + let secret = secrets_client + .create_secret( + msg.connection().clone().ownership.client_id, + &oauth_secret.as_json(), + ) + .await + .map_err(|e| { + warn!("Failed to create oauth secret: {}", e); + InternalError::io_err("Failed to create oauth secret", None) + })?; + + let set = OAuth::Enabled { + connection_oauth_definition_id: *id, + expires_at: Some( + (chrono::Utc::now() + Duration::seconds(oauth_secret.expires_in as i64)) + .timestamp(), + ), + expires_in: Some(oauth_secret.expires_in), + }; + + let data = doc! { + "$set": { + "oauth": bson::to_bson(&set).map_err(|e| { + warn!("Failed to serialize oauth: {}", e); + InternalError::encryption_error("Failed to serialize oauth", None) + })?, + "secretsServiceId": secret.id, + } + }; + + connections + .update_one(&msg.connection().id.to_string(), data) + .await + .map_err(|e| { + warn!("Failed to update connection: {}", e); + InternalError::io_err("Failed to update connection", None) + })?; + + tracing::info!( + request_id = request_id.as_deref(), + "Connection {} updated", + msg.connection().id + ); + + Ok::(*id) + }; + + match ask().await { + Ok(id) => { + Outcome::success(id.to_string().as_str(), json!({ "id": id.to_string() })) + } + Err(e) => Outcome::failure( + msg.connection().id.to_string().as_str(), + json!({ "error": e.to_string() }), + ), + } + }; + + Box::pin(future) + } +} diff --git a/src/domain/mod.rs b/src/domain/mod.rs new file mode 100644 index 0000000..8e8c94a --- /dev/null +++ b/src/domain/mod.rs @@ -0,0 +1,11 @@ +mod outcome; +mod query; +mod refresh; +mod state; +mod trigger; + +pub use outcome::*; +pub use query::*; +pub use refresh::*; +pub use state::*; +pub use trigger::*; diff --git a/src/domain/outcome.rs b/src/domain/outcome.rs new file mode 100644 index 0000000..0a030cc --- /dev/null +++ b/src/domain/outcome.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum Outcome { + Success { message: String, metadata: Value }, + Failure { message: String, metadata: Value }, +} + +impl Outcome { + pub fn success(message: &str, metadata: Value) -> Self { + Self::Success { + message: message.to_string(), + metadata, + } + } + + pub fn failure(message: &str, metadata: Value) -> Self { + Self::Failure { + message: message.to_string(), + metadata, + } + } +} diff --git a/src/domain/query.rs b/src/domain/query.rs new file mode 100644 index 0000000..bea6e81 --- /dev/null +++ b/src/domain/query.rs @@ -0,0 +1,6 @@ +use super::state::StatefulActor; +use actix::prelude::*; + +#[derive(Message, Debug, Clone)] +#[rtype(result = "StatefulActor")] +pub struct Query; diff --git a/src/domain/refresh.rs b/src/domain/refresh.rs new file mode 100644 index 0000000..fe1bb26 --- /dev/null +++ b/src/domain/refresh.rs @@ -0,0 +1,21 @@ +use crate::prelude::Unit; +use actix::prelude::*; +use integrationos_domain::error::IntegrationOSError as Error; + +#[derive(Message, Debug, Clone)] +#[rtype(result = "Result")] +pub struct Refresh { + refresh_before_in_minutes: i64, +} + +impl Refresh { + pub fn new(refresh_before_in_minutes: i64) -> Self { + Self { + refresh_before_in_minutes, + } + } + + pub fn refresh_before_in_minutes(&self) -> i64 { + self.refresh_before_in_minutes + } +} diff --git a/src/domain/state.rs b/src/domain/state.rs new file mode 100644 index 0000000..f89cd92 --- /dev/null +++ b/src/domain/state.rs @@ -0,0 +1,54 @@ +use std::sync::Arc; + +use chrono::{DateTime, Utc}; +use futures::lock::Mutex; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct StatefulActor { + /// The state of the actor + state: Value, + /// The last time the actor was updated + last_updated: DateTime, +} + +impl StatefulActor { + /// Create a new empty stateful actor + /// + /// An `Arc` is used to share the state between actors, + /// when `clone` is called on the `Arc` the reference count + /// is increased by one rather than creating a new copy of + /// the state. + /// + /// Whilst a `Mutex` is used to ensure that only one actor + /// can access the state for mutation at a time. + /// + /// Hence, the combination of `Arc` and `Mutex` allows + /// multiple actors to share the same state and mutate it + /// in a thread-safe manner. + pub fn empty() -> Arc> { + Arc::new(Mutex::new(StatefulActor { + state: Value::Null, + last_updated: Utc::now(), + })) + } + + /// Returns the state of the actor + pub fn state(&self) -> &Value { + &self.state + } + + /// Returns the last time the actor was updated + pub fn last_updated(&self) -> &DateTime { + &self.last_updated + } + + /// Updates the state of the actor + pub async fn update(value: Value, state: Arc>) { + let mut actor = state.lock().await; + actor.state = value; + actor.last_updated = Utc::now(); + } +} diff --git a/src/domain/trigger.rs b/src/domain/trigger.rs new file mode 100644 index 0000000..4cc8082 --- /dev/null +++ b/src/domain/trigger.rs @@ -0,0 +1,19 @@ +use crate::prelude::Outcome; +use actix::prelude::*; +use integrationos_domain::Connection; + +#[derive(Message, Debug, Clone, Eq, PartialEq)] +#[rtype(result = "Outcome")] +pub struct Trigger { + connection: Connection, +} + +impl Trigger { + pub fn new(connection: Connection) -> Self { + Self { connection } + } + + pub fn connection(&self) -> &Connection { + &self.connection + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..e3cb5cf --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,11 @@ +mod algebra; +mod domain; +mod service; + +pub mod prelude { + pub use super::algebra::*; + pub use super::domain::*; + pub use super::service::*; + + pub type Unit = (); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..d1aecdc --- /dev/null +++ b/src/main.rs @@ -0,0 +1,24 @@ +use dotenvy::dotenv; +use oauth_api::prelude::{ + get_subscriber, init_subscriber, Application, Config, OAuthConfig, ServerConfig, +}; + +#[actix_web::main] +async fn main() -> anyhow::Result<()> { + dotenv().ok(); + + let suscriber = get_subscriber("oauth_api".into(), "info".into(), std::io::stdout); + init_subscriber(suscriber); + + let oauth = OAuthConfig::load().expect("Failed to read configuration."); + let server = ServerConfig::load().expect("Failed to read configuration."); + let configuration = Config::new(oauth, server); + + let address = configuration.server().app_url().to_string(); + let application = Application::start(&configuration).await?; + + tracing::info!("Starting server at {}", &address); + application.spawn().await?; + + Ok(()) +} diff --git a/src/service/configuration/mod.rs b/src/service/configuration/mod.rs new file mode 100644 index 0000000..9a777aa --- /dev/null +++ b/src/service/configuration/mod.rs @@ -0,0 +1,330 @@ +mod telemetry; + +use actix_governor::{KeyExtractor, PeerIpKeyExtractor, SimpleKeyExtractionError}; +use actix_web::dev::ServiceRequest; +pub use telemetry::*; + +use envconfig::Envconfig; +use integrationos_domain::{ + database::DatabaseConfig, environment::Environment, secrets::SecretsConfig, +}; +use std::collections::HashMap; +use std::fmt::Debug; +use std::net::IpAddr; + +#[derive(Clone, Envconfig)] +pub struct OAuthConfig { + #[envconfig(env = "REFRESH_BEFORE_IN_MINUTES", default = "10")] + refresh_before: i64, + #[envconfig(env = "SLEEP_TIMER_IN_SECONDS", default = "20")] + sleep_timer: u64, + #[envconfig(nested = true)] + database: DatabaseConfig, + #[envconfig(nested = true)] + secrets_config: SecretsConfig, +} + +impl Debug for OAuthConfig { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("OAuthConfig") + .field("refresh_before", &self.refresh_before) + .field("sleep_timer", &self.sleep_timer) + .finish() + } +} + +impl OAuthConfig { + pub fn refresh_before(&self) -> i64 { + self.refresh_before + } + + pub fn sleep_timer(&self) -> u64 { + self.sleep_timer + } + + pub fn database(&self) -> &DatabaseConfig { + &self.database + } + + pub fn secrets_config(&self) -> &SecretsConfig { + &self.secrets_config + } + + pub fn load() -> Result { + // dotenv().ok() is already called in the main.rs + OAuthConfig::init_from_env() + } +} + +#[derive(Clone, Envconfig, Debug)] +pub struct ServerConfig { + #[envconfig(from = "ENVIRONMENT", default = "test")] + /// The environment for the server + environment: Environment, + #[envconfig(from = "HOST", default = "localhost")] + /// The host for the server + host: String, + #[envconfig(from = "PORT", default = "3007")] + /// The port for the server + port: u16, + #[envconfig(from = "APP_URL", default = "http://localhost:3007")] + /// The URL for the server + app_url: String, + #[envconfig( + from = "JWT_SECRET", + default = "2thZ2UiOnsibmFtZSI6IlN0YXJ0dXBsa3NoamRma3NqZGhma3NqZGhma3NqZG5jhYtggfaP9ubmVjdGlvbnMiOjUwMDAwMCwibW9kdWxlcyI6NSwiZW5kcG9pbnRzIjo3b4e05e2-f050-401f-9822-44f43f71753c" + )] + /// The secret for the JWT + jwt_secret: String, + #[envconfig(from = "TIMEOUT", default = "30000")] + timeout: u64, + #[envconfig( + from = "SECRET_ADMIN", + default = "my_admin_secret_super_extra_secure_key_to_verify_admin_sessions_this_one_must_be_at_least_51_characters" + )] + admin_secret: String, + /// Burst rate limit + #[envconfig(from = "BURST_RATE_LIMIT", default = "10")] + burst_rate_limit: u64, + /// Burst size limit + #[envconfig(from = "BURST_SIZE_LIMIT", default = "15")] + burst_size_limit: u32, + #[envconfig(from = "HEADER_AUTH", default = "x-integrationos-secret")] + pub auth_header: String, + #[envconfig(from = "HEADER_ADMIN", default = "x-integrationos-admin-token")] + pub admin_header: String, + #[envconfig(from = "CACHE_SIZE", default = "10000")] + pub cache_size: u64, +} + +impl ServerConfig { + pub fn host(&self) -> &str { + &self.host + } + + pub fn environment(&self) -> &Environment { + &self.environment + } + + pub fn port(&self) -> u16 { + self.port + } + + pub fn app_url(&self) -> &str { + &self.app_url + } + + pub fn is_development(&self) -> bool { + self.environment == Environment::Development || self.environment == Environment::Test + } + + pub fn jwt_secret(&self) -> &str { + &self.jwt_secret + } + + pub fn timeout(&self) -> u64 { + self.timeout + } + + pub fn cache_size(&self) -> u64 { + self.cache_size + } + + pub fn burst_rate_limit(&self) -> u64 { + self.burst_rate_limit + } + + pub fn auth_header(&self) -> &str { + &self.auth_header + } + + pub fn burst_size_limit(&self) -> u32 { + self.burst_size_limit + } + + pub fn admin_header(&self) -> &str { + &self.admin_header + } + + pub fn admin_secret(&self) -> &str { + &self.admin_secret + } + + pub fn load() -> Result { + // dotenv().ok() is already called in the main.rs + ServerConfig::init_from_env() + } +} + +#[derive(Clone)] +pub struct Config { + oauth: OAuthConfig, + server: ServerConfig, +} + +impl Debug for Config { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let _ = f + .debug_struct("OAuthConfig") + .field("refresh_before", &self.oauth.refresh_before) + .field("sleep_timer", &self.oauth.sleep_timer) + .finish(); + + writeln!(f)?; + + f.debug_struct("ServerConfig") + .field("environment", &self.server.environment) + .field("host", &self.server.host) + .field("port", &self.server.port) + .field("jwt_secret", &"[REDACTED]") + .field("admin_secret", &"[REDACTED]") + .field("app_url", &self.server.app_url) + .field("timeout", &self.server.timeout) + .field("burst_rate_limit", &self.server.burst_rate_limit) + .field("burst_size_limit", &self.server.burst_size_limit) + .field("auth_header", &self.server.auth_header) + .field("cache_size", &self.server.cache_size) + .finish() + } +} + +impl Config { + pub fn new(oauth: OAuthConfig, server: ServerConfig) -> Self { + Self { oauth, server } + } + + pub fn oauth(&self) -> &OAuthConfig { + &self.oauth + } + + pub fn server(&self) -> &ServerConfig { + &self.server + } +} + +impl From> for OAuthConfig { + fn from(value: HashMap<&str, &str>) -> Self { + let refresh_before = value + .get("REFRESH_BEFORE_IN_MINUTES") + .and_then(|value| value.parse().ok()) + .unwrap_or(10); + + let sleep_timer = value + .get("SLEEP_TIMER_IN_SECONDS") + .and_then(|value| value.parse().ok()) + .unwrap_or(20); + + let owned = value + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(); + let database = DatabaseConfig::init_from_hashmap(&owned).unwrap_or_default(); + let secrets_config = SecretsConfig::init_from_hashmap(&owned).unwrap_or_default(); + + Self { + refresh_before, + sleep_timer, + database, + secrets_config, + } + } +} + +impl From> for ServerConfig { + fn from(value: HashMap<&str, &str>) -> Self { + let environment = value + .get("ENVIRONMENT") + .and_then(|value| value.parse().ok()) + .unwrap_or("test".parse().unwrap()); + let host = value.get("HOST").unwrap_or(&"localhost").to_string(); + let port = value + .get("PORT") + .and_then(|value| value.parse().ok()) + .unwrap_or(3008); + let app_url = value + .get("APP_URL") + .unwrap_or(&"http://localhost:3008") + .to_string(); + let jwt_secret = value + .get("JWT_SECRET") + .unwrap_or( + &"2thZ2UiOnsibmFtZSI6IlN0YXJ0dXBsa3NoamRma3NqZGhma3NqZGhma3NqZG5jhYtggfaP9ubmVjdGlvbnMiOjUwMDAwMCwibW9kdWxlcyI6NSwiZW5kcG9pbnRzIjo3b4e05e2-f050-401f-9822-44f43f71753c" + ) + .to_string(); + let timeout = value + .get("TIMEOUT") + .and_then(|value| value.parse().ok()) + .unwrap_or(30000); + let burst_rate_limit = value + .get("BURST_RATE_LIMIT") + .and_then(|value| value.parse().ok()) + .unwrap_or(10); + let burst_size_limit = value + .get("BURST_SIZE_LIMIT") + .and_then(|value| value.parse().ok()) + .unwrap_or(15); + let auth_header = value + .get("HEADER_AUTH") + .unwrap_or(&"x-integrationos-secret") + .to_string(); + let cache_size = value + .get("CACHE_SIZE") + .and_then(|value| value.parse().ok()) + .unwrap_or(10000); + let secret_admin = value + .get("SECRET_ADMIN") + .unwrap_or( + &"my_admin_secret_super_extra_secure_key_to_verify_admin_sessions_this_one_must_be_at_least_51_characters" + ) + .to_string(); + let admin_header = value + .get("HEADER_ADMIN") + .unwrap_or(&"x-integrationos-admin-token") + .to_string(); + + Self { + environment, + host, + port, + admin_header, + app_url, + jwt_secret, + timeout, + burst_rate_limit, + admin_secret: secret_admin, + burst_size_limit, + auth_header, + cache_size, + } + } +} + +impl From> for Config { + fn from(value: HashMap<&str, &str>) -> Self { + let oauth = OAuthConfig::from(value.clone()); + let server = ServerConfig::from(value); + Self { oauth, server } + } +} + +#[derive(Clone)] +pub struct WhiteListKeyExtractor; + +impl KeyExtractor for WhiteListKeyExtractor { + type Key = IpAddr; + type KeyExtractionError = SimpleKeyExtractionError<&'static str>; + + fn extract(&self, req: &ServiceRequest) -> Result { + PeerIpKeyExtractor.extract(req) + } + + fn whitelisted_keys(&self) -> Vec { + // In case we want to add more private networks remember that the CIDR notation for + // 172s is 172.16.0.0/12 and for 192s is 192.168.0.0/16 + + "10.0.0.0/8" + .parse() + .map(|ip| vec![ip]) + .unwrap_or_else(|_| vec![]) + } +} diff --git a/src/service/configuration/telemetry.rs b/src/service/configuration/telemetry.rs new file mode 100644 index 0000000..0db11cd --- /dev/null +++ b/src/service/configuration/telemetry.rs @@ -0,0 +1,103 @@ +use actix_web::body::MessageBody; +use actix_web::dev::{ServiceRequest, ServiceResponse}; +use tracing::subscriber::set_global_default; +use tracing::Level; +use tracing::Span; +use tracing_actix_web::{DefaultRootSpanBuilder, RootSpanBuilder, TracingLogger}; +use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer}; +use tracing_log::LogTracer; +use tracing_subscriber::fmt::MakeWriter; +use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry}; + +use crate::prelude::PREFIX; + +pub struct Telemetry +where + T: SubscriberExt + Send + Sync + 'static, +{ + pub subscriber: T, +} + +/// Compose multiple layers into a `tracing`'s subscriber. +/// +/// # Implementation Notes +/// +/// We are using `impl Subscriber` as return type to avoid having to spell out the actual +/// type of the returned subscriber, which is indeed quite complex. +pub fn get_subscriber( + name: String, + env_filter: String, + sink: Sink, +) -> Telemetry +where + Sink: for<'a> MakeWriter<'a> + Send + Sync + 'static, +{ + // JSON formatting layer + let formatting_layer: BunyanFormattingLayer = BunyanFormattingLayer::new( + name, // Output the JSON logs to the stdout. + sink, + ); + + // Filter Layer + let filter_layer = + EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(env_filter)); + + Telemetry { + subscriber: Registry::default() + .with(filter_layer) + .with(JsonStorageLayer) + .with(formatting_layer), + } +} + +/// Register a subscriber as global default to process span data. +/// +/// It should only be called once! +pub fn init_subscriber(subscriber: Telemetry) { + LogTracer::init().expect("Failed to set logger"); + set_global_default(subscriber.subscriber).expect("Failed to set subscriber"); +} + +pub struct SpanBuilder; + +impl RootSpanBuilder for SpanBuilder { + fn on_request_start(request: &ServiceRequest) -> Span { + let level = if request.path() == PREFIX.to_owned() + "/health_check" + || request.path() == PREFIX.to_owned() + "/metrics" + { + Level::TRACE + } else { + Level::INFO + }; + tracing_actix_web::root_span!(level = level, request) + } + + fn on_request_end( + span: Span, + outcome: &Result, actix_web::Error>, + ) { + DefaultRootSpanBuilder::on_request_end(span, outcome); + } +} + +pub struct Tracer { + tracer: TracingLogger, +} + +impl Tracer { + pub fn new() -> Self { + let tracer = TracingLogger::::new(); + + Tracer { tracer } + } + + pub fn tracer(&self) -> TracingLogger { + self.tracer.clone() + } +} + +impl Default for Tracer { + fn default() -> Self { + Self::new() + } +} diff --git a/src/service/http/admin/mod.rs b/src/service/http/admin/mod.rs new file mode 100644 index 0000000..278e0b5 --- /dev/null +++ b/src/service/http/admin/mod.rs @@ -0,0 +1,3 @@ +mod refresh; + +pub use refresh::*; diff --git a/src/service/http/admin/refresh.rs b/src/service/http/admin/refresh.rs new file mode 100644 index 0000000..e1e6511 --- /dev/null +++ b/src/service/http/admin/refresh.rs @@ -0,0 +1,17 @@ +use crate::prelude::Query as RefreshQuery; +use crate::prelude::{AppState, ResponseType, ServerResponse}; +use actix_web::{get, web::Data, HttpResponse}; +use integrationos_domain::error::IntegrationOSError as Error; +use integrationos_domain::InternalError; + +#[tracing::instrument(skip(state))] +#[get("/get_state")] +pub async fn get_state(state: Data) -> Result { + let response = state + .refresh_actor + .send(RefreshQuery) + .await + .map_err(|e| InternalError::io_err(e.to_string().as_str(), None))?; + + Ok(HttpResponse::Ok().json(ServerResponse::new(ResponseType::Query, response))) +} diff --git a/src/service/http/application.rs b/src/service/http/application.rs new file mode 100644 index 0000000..e01e825 --- /dev/null +++ b/src/service/http/application.rs @@ -0,0 +1,132 @@ +use super::{admin_event_middleware, admin_middleware, get_state, health_check, trigger_refresh}; +use crate::prelude::{AppState, Config, Refresh, Tracer, Unit, WhiteListKeyExtractor}; +use actix_cors::Cors; +use actix_governor::{Governor, GovernorConfigBuilder}; +use actix_web::{ + dev::Server, + web::{scope, Data}, + App, HttpServer, +}; +use actix_web_lab::middleware::from_fn; +use anyhow::Context; +use futures::Future; +use std::{net::TcpListener, pin::Pin, time::Duration}; + +pub const PREFIX: &str = "/v1"; +pub const ADMIN_PREFIX: &str = "/admin"; +pub const INTEGRATION_PREFIX: &str = "/integration"; +type Task = Pin + Send + Sync>>; + +pub struct Application { + port: u16, + server: Server, + task: Task, +} + +impl Application { + pub async fn start(configuration: &Config) -> Result { + tracing::info!( + "Starting application with configuration: {}{:#?}{}", + "\n", + &configuration, + "\n" + ); + let address = format!( + "{}:{}", + configuration.server().host(), + configuration.server().port() + ); + let listener = TcpListener::bind(&address)?; + let port = listener.local_addr()?.port(); + let state = AppState::try_from(configuration.clone()).await?; + + let sleep_timer = Duration::from_secs(configuration.oauth().sleep_timer()); + let refresh_before = configuration.oauth().refresh_before(); + let refresh_actor = state.refresh_actor().clone(); + let task = Box::pin(async move { + loop { + let message = Refresh::new(refresh_before); + let res = refresh_actor.send(message).await; + + if let Err(e) = res { + tracing::warn!("Failed to send refresh message: {:?}", e); + } + + tracing::info!("Sleeping for {} seconds", sleep_timer.as_secs()); + tokio::time::sleep(sleep_timer).await; + } + }); + + let server = run(listener, configuration.clone(), state).await?; + + Ok(Self { port, server, task }) + } + + pub fn port(&self) -> u16 { + self.port + } + + pub fn handler(self) -> (Server, Task) { + (self.server, self.task) + } + + pub async fn spawn(self) -> Result<(), anyhow::Error> { + let (server, task) = self.handler(); + let task = tokio::spawn(task); + let http = tokio::spawn(server); + + tokio::select! { + res = http => { + res.context("Failed to spawn application.")?.context("Failed to spawn application.") + }, + res = task => { + res.context("Failed to spawn application.") + } + } + } +} + +async fn run( + listener: TcpListener, + configuration: Config, + state: AppState, +) -> Result { + let governor = GovernorConfigBuilder::default() + .key_extractor(WhiteListKeyExtractor) + .per_second(configuration.server().burst_rate_limit()) + .permissive(configuration.server().is_development()) + .burst_size(configuration.server().burst_size_limit()) + .finish() + .context("Failed to create governor.")?; + + let server = HttpServer::new(move || { + let trace: Tracer = Tracer::default(); + App::new() + .wrap(trace.tracer()) + .wrap( + Cors::default() + .allowed_methods(vec!["GET", "POST"]) + .allow_any_origin() + .allow_any_header() + .supports_credentials() + .max_age(3600), + ) + .wrap(Governor::new(&governor)) + .service( + scope(&(PREFIX.to_owned() + ADMIN_PREFIX)) // /v1/admin + .wrap(from_fn(admin_middleware)) + .service(get_state), + ) + .service( + scope(&(PREFIX.to_owned() + INTEGRATION_PREFIX)) // /v1/integration + .wrap(from_fn(admin_event_middleware)) + .service(trigger_refresh), + ) + .service(scope(PREFIX).service(health_check)) // /v1 + .app_data(Data::new(state.clone())) + }) + .listen(listener)? + .run(); + + Ok(server) +} diff --git a/src/service/http/middleware.rs b/src/service/http/middleware.rs new file mode 100644 index 0000000..fed5109 --- /dev/null +++ b/src/service/http/middleware.rs @@ -0,0 +1,130 @@ +use crate::prelude::AppState; +use actix_web::{ + body::MessageBody, + dev::{ServiceRequest, ServiceResponse}, + error::ErrorUnauthorized, + web::Data, + Error as ActixWebError, HttpMessage, +}; +use actix_web_lab::middleware::Next; +use integrationos_domain::{algebra::adapter::StoreAdapter, event_access::EventAccess, Claims}; +use jsonwebtoken::{decode, DecodingKey, Validation}; +use mongodb::bson::doc; +use std::sync::Arc; + +pub async fn admin_middleware( + req: ServiceRequest, + next: Next, +) -> Result, ActixWebError> { + let state = req.app_data::>(); + let state = match state { + None => return Err(ErrorUnauthorized("No state found")), + Some(state) => state, + }; + + let extracted_info = extract_admin_info(&req, state); + + match extracted_info { + Ok(claims) => { + req.extensions_mut().insert(claims.to_owned()); + next.call(req).await + } + Err(err) => Err(ErrorUnauthorized(err)), + } +} + +pub async fn admin_event_middleware( + req: ServiceRequest, + next: Next, +) -> Result, ActixWebError> { + let state = req.app_data::>(); + let state = match state { + None => return Err(ErrorUnauthorized("No state found")), + Some(state) => state, + }; + + let event_access = extract_event_info(&req, state).await; + let claims = extract_admin_info(&req, state); + + match (event_access, claims) { + (Ok(event_access), Ok(claims)) => { + req.extensions_mut().insert(claims.to_owned()); + req.extensions_mut().insert(event_access.to_owned()); + next.call(req).await + } + (Err(err), _) | (_, Err(err)) => Err(ErrorUnauthorized(err)), + } +} + +fn extract_admin_info( + req: &ServiceRequest, + state: &Data, +) -> Result { + let token = req + .headers() + .get(state.configuration().server().admin_header()) + .and_then(|header| header.to_str().ok()) + .map(|h| h.to_string().split_at(7).1.to_string()); + + let token = match token { + Some(token) => token, + None => return Err(ErrorUnauthorized("No token found")), + }; + + let mut validator = Validation::default(); + validator.set_audience(&["integration-team", "oauth-integrationos"]); + + let claims = decode::( + &token, + &DecodingKey::from_secret(state.configuration().server().admin_secret().as_ref()), + &validator, + ) + .map_err(|_| ErrorUnauthorized("Invalid token"))?; + + Ok(claims.claims) +} + +async fn extract_event_info( + req: &ServiceRequest, + state: &Data, +) -> Result, ActixWebError> { + let Some(auth_header) = req + .headers() + .get(state.configuration().server().auth_header()) + else { + Err(ErrorUnauthorized("No auth header found"))? + }; + + let event_access = state + .cache() + .try_get_with_by_ref(auth_header, async { + let key = auth_header + .to_str() + .map_err(|e| format!("Invalid auth header: {}", e))?; + + if let Some(event_access) = state + .event_access() + .get_one(doc! { + "accessKey": key, + "deleted": false + }) + .await + .map_err(|e| { + tracing::warn!("{}", e); + format!("{}", e) + })? + { + Ok(Arc::new(event_access)) + } else { + Err(format!("No event access found for key: {}", key)) + } + }) + .await; + + let event_access: Arc = match event_access { + Ok(event_access) => event_access, + Err(err) => Err(ErrorUnauthorized(err))?, + }; + + Ok(event_access) +} diff --git a/src/service/http/mod.rs b/src/service/http/mod.rs new file mode 100644 index 0000000..1d7d3e7 --- /dev/null +++ b/src/service/http/mod.rs @@ -0,0 +1,71 @@ +mod admin; +mod application; +mod middleware; +mod public; + +pub use admin::*; +pub use application::*; +pub use middleware::*; +pub use public::*; + +#[derive(serde::Serialize, serde::Deserialize, Debug, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub enum ResponseType { + Health, + Trigger, + Query, + Error, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ServerResponse +where + T: serde::Serialize, +{ + #[serde(rename = "type")] + pub response_type: ResponseType, + pub args: T, +} + +impl ServerResponse +where + T: serde::Serialize, +{ + pub fn new(response_type: ResponseType, args: T) -> Self { + Self { + response_type, + args, + } + } +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ServerError { + pub message: Vec, +} + +impl ServerError { + pub fn new(message: Vec) -> Self { + Self { message } + } +} + +impl From> for ServerResponse { + fn from(message: Vec) -> Self { + Self { + response_type: ResponseType::Error, + args: ServerError::new(message.iter().map(|s| s.to_string()).collect()), + } + } +} + +impl<'a> From> for ServerResponse { + fn from(message: Vec<&'a str>) -> Self { + Self { + response_type: ResponseType::Error, + args: ServerError::new(message.iter().map(|s| s.to_string()).collect()), + } + } +} diff --git a/src/service/http/public/health.rs b/src/service/http/public/health.rs new file mode 100644 index 0000000..ddf0de8 --- /dev/null +++ b/src/service/http/public/health.rs @@ -0,0 +1,10 @@ +use crate::prelude::{ResponseType, ServerResponse}; +use actix_web::{get, HttpResponse}; + +#[get("/health_check")] +pub async fn health_check() -> HttpResponse { + HttpResponse::Ok().json(ServerResponse::new( + ResponseType::Health, + "I'm alive!".to_string(), + )) +} diff --git a/src/service/http/public/mod.rs b/src/service/http/public/mod.rs new file mode 100644 index 0000000..de0a99f --- /dev/null +++ b/src/service/http/public/mod.rs @@ -0,0 +1,5 @@ +mod health; +mod trigger; + +pub use health::*; +pub use trigger::*; diff --git a/src/service/http/public/trigger.rs b/src/service/http/public/trigger.rs new file mode 100644 index 0000000..6192226 --- /dev/null +++ b/src/service/http/public/trigger.rs @@ -0,0 +1,54 @@ +use crate::prelude::{ + get_connection_to_trigger, AppState, ResponseType, ServerResponse, Trigger, TriggerActor, +}; +use actix::Actor; +use actix_web::{ + post, + web::{Data, Path}, + HttpResponse, +}; +use integrationos_domain::{ + error::IntegrationOSError as Error, ApplicationError, Id, InternalError, +}; +use serde_json::json; +use tracing_actix_web::RequestId; + +#[tracing::instrument(name = "Trigger refresh", skip(state, request_id))] +#[post("/trigger/{id}")] +pub async fn trigger_refresh( + request_id: RequestId, + state: Data, + id: Path, +) -> Result { + let connection = get_connection_to_trigger(state.connections(), *id) + .await? + .ok_or(ApplicationError::not_found( + format!("Connection with id {} not found", id).as_str(), + None, + ))?; + + tracing::info!("Triggering refresh for connection {}", connection.id); + + let actor = TriggerActor::new( + state.connections().clone(), + state.oauths().clone(), + state.secrets().clone(), + state.client().clone(), + Some(request_id), + ) + .start(); + let id = connection.id; + let trigger = Trigger::new(connection); + + let outcome = actor + .send(trigger) + .await + .map_err(|e| InternalError::io_err(e.to_string().as_str(), None))?; + + let json = json!({ + "id": id, + "outcome": outcome, + }); + + Ok(HttpResponse::Ok().json(ServerResponse::new(ResponseType::Trigger, json))) +} diff --git a/src/service/mod.rs b/src/service/mod.rs new file mode 100644 index 0000000..11f2443 --- /dev/null +++ b/src/service/mod.rs @@ -0,0 +1,131 @@ +mod configuration; +mod http; + +pub use configuration::*; +pub use http::*; + +use crate::prelude::RefreshActor; +use actix::{Addr, Supervisor}; +use integrationos_domain::{ + connection_oauth_definition::ConnectionOAuthDefinition, error::IntegrationOSError as Error, + event_access::EventAccess, mongo::MongoDbStore, service::secrets_client::SecretsClient, + Connection, InternalError, Store, +}; +use moka::future::Cache; +use mongodb::options::FindOptions; +use reqwest::{header::HeaderValue, Client}; +use std::{sync::Arc, time::Duration}; +use tokio::time::timeout; + +#[derive(Clone, Debug)] +pub struct AppState { + configuration: Config, + cache: Cache>, + client: Client, + secrets: Arc, + connections: Arc>, + oauths: Arc>, + event_access: Arc>, + refresh_actor: Addr, +} + +impl AppState { + pub async fn try_from(config: Config) -> Result { + let client = Client::builder() + .timeout(Duration::from_millis(config.server().timeout())) + .build() + .map_err(|e| InternalError::io_err(e.to_string().as_str(), None))?; + let mongo_client = mongodb::Client::with_uri_str(&config.oauth().database().control_db_url) + .await + .map_err(|e| InternalError::io_err(e.to_string().as_str(), None))?; + + timeout(Duration::from_millis(config.server().timeout()), async { + mongo_client + .database("admin") + .collection::("system.users") + .find( + None, + FindOptions::builder() + .limit(1) + .max_time(Duration::from_secs(1)) + .max_await_time(Duration::from_secs(1)) + .build(), + ) + .await + }) + .await + .expect( + "Failed to connect to MongoDB within 5 seconds. Please check your connection string.", + ) + .ok(); + + let database = mongo_client.database(config.oauth().database().control_db_name.as_ref()); + let secrets = SecretsClient::new(config.oauth().secrets_config())?; + let oauths = MongoDbStore::::new_with_db( + database.clone(), + Store::ConnectionOAuthDefinitions, + ) + .await?; + let connections = + MongoDbStore::::new_with_db(database.clone(), Store::Connections).await?; + let cache = Cache::new(config.server().cache_size()); + let event_access = + MongoDbStore::::new_with_db(database.clone(), Store::EventAccess).await?; + + let oauths = Arc::new(oauths); + let connections = Arc::new(connections); + let secrets = Arc::new(secrets); + let event_access = Arc::new(event_access); + + let actor = RefreshActor::new( + oauths.clone(), + connections.clone(), + secrets.clone(), + client.clone(), + ); + let refresh_actor = Supervisor::start(move |_| actor); + + Ok(AppState { + configuration: config, + cache, + event_access, + connections, + client, + oauths, + secrets, + refresh_actor, + }) + } + + pub fn configuration(&self) -> &Config { + &self.configuration + } + + pub fn client(&self) -> &Client { + &self.client + } + + pub fn connections(&self) -> &Arc> { + &self.connections + } + + pub fn cache(&self) -> &Cache> { + &self.cache + } + + pub fn oauths(&self) -> &Arc> { + &self.oauths + } + + pub fn event_access(&self) -> &Arc> { + &self.event_access + } + + pub fn secrets(&self) -> &Arc { + &self.secrets + } + + pub fn refresh_actor(&self) -> &Addr { + &self.refresh_actor + } +} diff --git a/tests/http/health.rs b/tests/http/health.rs new file mode 100644 index 0000000..e08027f --- /dev/null +++ b/tests/http/health.rs @@ -0,0 +1,13 @@ +use crate::suite::TestApp; +use std::collections::HashMap; + +#[actix::test] +async fn health_check_works() { + // Arrange + let application = TestApp::spawn(HashMap::new()).await; + // Act + let response = application.get("health_check").await; + // Assert + assert!(response.status().is_success()); + assert_eq!(Some(37), response.content_length()); +} diff --git a/tests/http/mod.rs b/tests/http/mod.rs new file mode 100644 index 0000000..e0d7220 --- /dev/null +++ b/tests/http/mod.rs @@ -0,0 +1,2 @@ +mod health; +mod trigger; diff --git a/tests/http/trigger.rs b/tests/http/trigger.rs new file mode 100644 index 0000000..3279d10 --- /dev/null +++ b/tests/http/trigger.rs @@ -0,0 +1,147 @@ +use crate::suite::TestApp; +use integrationos_domain::{prefix::IdPrefix, Id}; +use mark_flaky_tests::flaky; +use oauth_api::prelude::{JwtTokenGenerator, TokenGenerator}; +use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; +use std::collections::HashMap; +use uuid::Uuid; + +#[actix::test] +async fn returns_401_for_missing_headers() { + // Arrange + let application = TestApp::spawn(HashMap::new()).await; + // Act + let path = format!("integration/trigger/{}", Uuid::nil()); + let response = application.post(path, "", None).await; + // Assert + assert_eq!(401, response.status().as_u16()); +} + +#[actix::test] +async fn returns_404_for_invalid_prefix_id() { + // Arrange + let application = TestApp::spawn(HashMap::new()).await; + let event_access = application.insert_event_access().await; + let event_access_token = event_access.access_key; + let token = JwtTokenGenerator + .generate(application.configuration().clone(), 1) + .expect("Failed to generate token"); + + let headers = HeaderMap::from_iter(vec![ + ( + HeaderName::from_static("x-integrationos-secret"), + HeaderValue::from_str(&event_access_token).expect("Failed to create header value"), + ), + ( + HeaderName::from_static("x-integrationos-admin-token"), + HeaderValue::from_str(&format!("Bearer {}", token)) + .expect("Failed to create header value"), + ), + ]); + let path = format!("integration/trigger/{}", Uuid::nil()); + // Act + let response = application.post(path, "", Some(headers)).await; + // Assert + assert_eq!( + "text/plain; charset=utf-8", + response + .headers() + .get("content-type") + .expect("Failed to get content type") + .to_str() + .expect("Failed to convert content type to string") + ); + assert_eq!( + "Argument provided is invalid: Invalid ID prefix: 00000000-0000-0000-0000-000000000000", + response.text().await.expect("Failed to get response text") + ); +} + +#[actix::test] +async fn returns_401_for_non_existent_event_access() { + // Arrange + let application = TestApp::spawn(HashMap::new()).await; + let event_access = "sk_live_1_Gom7umYOtRPyCbx4o2XNIlM32-2wf2dPI6s7nsdlWeXuhRj1rgDEvFeYAVckQvwG-5IUzRHGWnloNx2fci7IdFcdlTqYAuUuj6QQZPOvS2sxGK4YKnkmS1UFqcXFDCsSYZxASBaqJaBZA1HMEVuv61-cepuCBJccX90hXqQlKZvZ5s0i8hRZszeCA9b3H18paLy7"; + let token = JwtTokenGenerator + .generate(application.configuration().clone(), 1) + .expect("Failed to generate token"); + let headers = HeaderMap::from_iter(vec![ + ( + HeaderName::from_static("x-integrationos-secret"), + HeaderValue::from_str(event_access).expect("Failed to create header value"), + ), + ( + HeaderName::from_static("x-integrationos-admin-token"), + HeaderValue::from_str(&format!("Bearer {}", token)) + .expect("Failed to create header value"), + ), + ]); + let id = Id::now(IdPrefix::ConnectionModelDefinition); + let path = format!("integration/trigger/{}", id); + // Act + let response = application.post(path, "", Some(headers)).await; + // Assert + assert_eq!(401, response.status().as_u16()); + assert_eq!( + "text/plain; charset=utf-8", + response + .headers() + .get("content-type") + .expect("Failed to get content type") + .to_str() + .expect("Failed to convert content type to string") + ); + assert_eq!( + format!("No event access found for key: {}", event_access), + response.text().await.expect("Failed to get response text") + ); +} + +#[actix::test] +#[flaky] +async fn returns_404_inexistent_event() { + // Arrange + let application = TestApp::spawn(HashMap::new()).await; + let event_access = application.insert_event_access().await; + let event_access_token = event_access.access_key; + + let token = JwtTokenGenerator + .generate(application.configuration().clone(), 1) + .expect("Failed to generate token"); + + let headers = HeaderMap::from_iter(vec![ + ( + HeaderName::from_static("x-integrationos-secret"), + HeaderValue::from_str(&event_access_token).expect("Failed to create header value"), + ), + ( + HeaderName::from_static("x-integrationos-admin-token"), + HeaderValue::from_str(&format!("Bearer {}", token)) + .expect("Failed to create header value"), + ), + ]); + + let id = Id::now(IdPrefix::ConnectionModelDefinition); + let path = format!("integration/trigger/{}", id); + // Act + let response = application.post(path, "", Some(headers)).await; + // Assert + let msg = format!( + "{{\"passthrough\":{{\"notFound\":{{\"message\":\"Connection with id {} not found\",\"subtype\":null}}}}}}", + id + ); + assert_eq!(404, response.status().as_u16()); + assert_eq!( + "application/json", + response + .headers() + .get("content-type") + .expect("Failed to get content type") + .to_str() + .expect("Failed to convert content type to string") + ); + assert_eq!( + msg, + response.text().await.expect("Failed to get response text") + ); +} diff --git a/tests/lib.rs b/tests/lib.rs new file mode 100644 index 0000000..9c7b31c --- /dev/null +++ b/tests/lib.rs @@ -0,0 +1,2 @@ +pub mod http; +pub mod suite; diff --git a/tests/suite.rs b/tests/suite.rs new file mode 100644 index 0000000..8e183d2 --- /dev/null +++ b/tests/suite.rs @@ -0,0 +1,198 @@ +use chrono::{DateTime, NaiveDateTime, TimeZone, Utc}; +use fake::{Fake, Faker}; +use integrationos_domain::{ + access_key_data::AccessKeyData, access_key_prefix::AccessKeyPrefix, + connection_model_definition::ConnectionModelDefinition, encrypted_data::PASSWORD_LENGTH, + environment::Environment, event_access::EventAccess, event_type::EventType, AccessKey, Id, + Store, +}; +use mongodb::{Client as MongoClient, Database}; +use oauth_api::prelude::{Application, Config}; +use once_cell::sync::Lazy; +use rand::Rng; +use reqwest::{header::HeaderMap, Client}; +use std::collections::HashMap; +use uuid::Uuid; + +pub struct TestApp { + client: Client, + address: String, + configuration: Config, + mongo: Database, +} + +static IV: Lazy<[u8; 16]> = Lazy::new(|| rand::thread_rng().gen::<[u8; 16]>()); +pub static EPOCH: Lazy> = Lazy::new(|| { + TimeZone::from_utc_datetime( + &Utc, + &NaiveDateTime::from_timestamp_opt(0, 0).expect("Failed to create timestamp"), + ) +}); +pub static ID: Lazy = Lazy::new(|| { + Id::new_with_uuid( + integrationos_domain::prefix::IdPrefix::ConnectionModelDefinition, + *EPOCH, + Uuid::nil(), + ) +}); +pub static EVENT_ACCESS_PASSWORD: Lazy<[u8; PASSWORD_LENGTH]> = Lazy::new(|| { + "32KFFT_i4UpkJmyPwY2TGzgHpxfXs7zS" + .as_bytes() + .try_into() + .expect("Failed to convert password to array") +}); + +impl TestApp { + #[cfg(test)] + pub async fn get>(&self, path: T) -> reqwest::Response { + let path = path.into(); + self.client + .get(format!("{}/v1/{}", self.address, path)) + .send() + .await + .expect("Failed to execute request") + } + + #[cfg(test)] + pub async fn post, B: Into>( + &self, + path: T, + body: B, + headers: Option, + ) -> reqwest::Response { + let path = path.into(); + let headers = headers.unwrap_or_default(); + self.client + .post(format!("{}/v1/{}", self.address, path)) + .headers(headers) + .body(body.into()) + .send() + .await + .expect("Failed to execute request") + } + + #[cfg(test)] + pub async fn spawn(config: HashMap<&str, &str>) -> Self { + use std::collections::hash_map::RandomState; + + let url = "mongodb://127.0.0.1:27017/?directConnection=true"; + let uuid = Uuid::new_v4().to_string(); + + let configuration = Config::from( + HashMap::<&str, &str, RandomState>::from_iter([ + ("HOST", "localhost"), + ("PORT", "0"), + ("CONTROL_DATABASE_URL", url), + ("EVENT_DATABASE_URL", url), + ("CONTEXT_DATABASE_URL", url), + ("UDM_DATABASE_URL", url), + ("EVENT_DATABASE_NAME", uuid.as_str()), + ("CONTEXT_DATABASE_NAME", uuid.as_str()), + ("CONTROL_DATABASE_NAME", uuid.as_str()), + ("UDM_DATABASE_NAME", uuid.as_str()), + ("SECRETS_SERVICE_BASE_URL", "http://localhost:1080/"), + ("SECRETS_SERVICE_GET_PATH", "v1/secrets/get/"), + ("SECRETS_SERVICE_CREATE_PATH", "v1/secrets/create/"), + ("REFRESH_BEFORE_IN_MINUTES", "10"), + ("SLEEP_TIMER_IN_SECONDS", "20"), + ("ENVIRONMENT", "development"), + ]) + .into_iter() + .chain(config.into_iter()) + .collect::>(), + ); + + let application = Application::start(&configuration) + .await + .expect("Failed to start app"); + let address = format!("http://localhost:{}", application.port()); + tokio::spawn(application.spawn()); + + let client = MongoClient::with_uri_str(url) + .await + .expect("Failed to create database client") + .database(uuid.as_str()); + + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + + Self { + client: Client::new(), + address, + configuration, + mongo: client, + } + } + + pub async fn insert_connection_definition(&self) { + let mut stripe_model_config: ConnectionModelDefinition = Faker.fake(); + + stripe_model_config.id = *ID; + + let _ = self + .mongo + .collection("connection_model_definition") + .insert_one(stripe_model_config, None) + .await + .expect("Failed to insert into database"); + } + + pub async fn insert_event_access(&self) -> EventAccess { + let mut event_access: EventAccess = Faker.fake(); + + let access_key = self.access_key_encoded(); + + event_access.access_key = access_key; + event_access.record_metadata.deleted = false; + + let _ = self + .mongo + .collection::(Store::EventAccess.to_string().as_str()) + .insert_one(event_access.clone(), None) + .await + .expect("Failed to insert into database"); + + event_access + } + + fn access_key_encoded(&self) -> String { + let access_key = AccessKey { + prefix: AccessKeyPrefix { + environment: Environment::Test, + event_type: EventType::SecretKey, + version: 1, + }, + data: AccessKeyData { + id: Uuid::new_v4().to_string(), + namespace: "namespace".to_string(), + event_type: "event_type".to_string(), + group: "group".to_string(), + event_path: "event_path".to_string(), + event_object_id_path: None, + timestamp_path: None, + parent_access_key: None, + }, + }; + + let access_key_encoded = access_key + .encode(&EVENT_ACCESS_PASSWORD, &IV) + .expect("Failed to encode access key"); + + access_key_encoded.to_string() + } + + pub fn client(&self) -> &Client { + &self.client + } + + pub fn address(&self) -> &str { + &self.address + } + + pub fn configuration(&self) -> &Config { + &self.configuration + } + + pub fn mongo(&self) -> &Database { + &self.mongo + } +}