diff --git a/mise.lock b/mise.lock index 2037239..3cd025e 100644 --- a/mise.lock +++ b/mise.lock @@ -1,16 +1,37 @@ +# @generated - this file is auto-generated by `mise lock` https://mise.jdx.dev/dev-tools/mise-lock.html + [[tools.ast-grep]] version = "0.42.0" backend = "aqua:ast-grep/ast-grep" -"platforms.linux-arm64" = { checksum = "sha256:5c830eae8456569e2f7212434ed9c238f58dca412d76045418ed6d394a755836", url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-unknown-linux-gnu.zip"} -"platforms.linux-x64" = { checksum = "sha256:e825a05603f0bcc4cd9076c4cc8c9abd6d008b7cd07d9aa3cc323ba4b8606651", url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-unknown-linux-gnu.zip"} -"platforms.macos-arm64" = { checksum = "sha256:fc300d5293b1c770a5aece03a8a193b92e71e87cec726c28096990691a582620", url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-apple-darwin.zip"} -"platforms.macos-x64" = { checksum = "sha256:979ffe611327056f4730a1ae71b0209b3b830f58b22c6ed194cda34f55400db2", url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-apple-darwin.zip"} -"platforms.windows-x64" = { checksum = "sha256:55836fa1b2c65dc7d61615a4d9368622a0d2371a76d28b9a165e5a3ab6ae32a4", url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-pc-windows-msvc.zip"} + +[tools.ast-grep."platforms.linux-arm64"] +checksum = "sha256:5c830eae8456569e2f7212434ed9c238f58dca412d76045418ed6d394a755836" +url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-unknown-linux-gnu.zip" + +[tools.ast-grep."platforms.linux-x64"] +checksum = "sha256:e825a05603f0bcc4cd9076c4cc8c9abd6d008b7cd07d9aa3cc323ba4b8606651" +url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-unknown-linux-gnu.zip" + +[tools.ast-grep."platforms.macos-arm64"] +checksum = "sha256:fc300d5293b1c770a5aece03a8a193b92e71e87cec726c28096990691a582620" +url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-apple-darwin.zip" + +[tools.ast-grep."platforms.macos-x64"] +checksum = "sha256:979ffe611327056f4730a1ae71b0209b3b830f58b22c6ed194cda34f55400db2" +url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-apple-darwin.zip" + +[tools.ast-grep."platforms.windows-x64"] +checksum = "sha256:55836fa1b2c65dc7d61615a4d9368622a0d2371a76d28b9a165e5a3ab6ae32a4" +url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-pc-windows-msvc.zip" [[tools."cargo:cargo-audit"]] version = "0.22.1" backend = "cargo:cargo-audit" +[[tools."cargo:cargo-edit"]] +version = "0.13.9" +backend = "cargo:cargo-edit" + [[tools."cargo:cargo-features"]] version = "1.0.0" backend = "cargo:cargo-features" @@ -62,20 +83,50 @@ backend = "asdf:flutter" [[tools.protoc]] version = "29.6" backend = "aqua:protocolbuffers/protobuf/protoc" -"platforms.linux-arm64" = { checksum = "sha256:2594ff4fcae8cb57310d394d0961b236190ad9c5efbfdf1f597ea471d424fe79", url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-aarch_64.zip"} -"platforms.linux-x64" = { checksum = "sha256:48785a926e73ffa3f68e2f22b14e7b849620c7a1d36809ac9249a5495e280323", url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-x86_64.zip"} -"platforms.macos-arm64" = { checksum = "sha256:b9576b5fa1a1ef3fe13a8c91d9d8204b46545759bea5ae155cd6ba2ea4cdaeed", url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-osx-aarch_64.zip"} -"platforms.macos-x64" = { checksum = "sha256:312f04713946921cc0187ef34df80241ddca1bab6f564c636885fd2cc90d3f88", url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-osx-x86_64.zip"} -"platforms.windows-x64" = { checksum = "sha256:1ebd7c87baffb9f1c47169b640872bf5fb1e4408079c691af527be9561d8f6f7", url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-win64.zip"} + +[tools.protoc."platforms.linux-arm64"] +checksum = "sha256:2594ff4fcae8cb57310d394d0961b236190ad9c5efbfdf1f597ea471d424fe79" +url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-aarch_64.zip" + +[tools.protoc."platforms.linux-x64"] +checksum = "sha256:48785a926e73ffa3f68e2f22b14e7b849620c7a1d36809ac9249a5495e280323" +url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-x86_64.zip" + +[tools.protoc."platforms.macos-arm64"] +checksum = "sha256:b9576b5fa1a1ef3fe13a8c91d9d8204b46545759bea5ae155cd6ba2ea4cdaeed" +url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-osx-aarch_64.zip" + +[tools.protoc."platforms.macos-x64"] +checksum = "sha256:312f04713946921cc0187ef34df80241ddca1bab6f564c636885fd2cc90d3f88" +url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-osx-x86_64.zip" + +[tools.protoc."platforms.windows-x64"] +checksum = "sha256:1ebd7c87baffb9f1c47169b640872bf5fb1e4408079c691af527be9561d8f6f7" +url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-win64.zip" [[tools.python]] version = "3.14.3" backend = "core:python" -"platforms.linux-arm64" = { checksum = "sha256:be0f4dc2932f762292b27d46ea7d3e8e66ddf3969a5eb0254a229015ed402625", url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"} -"platforms.linux-x64" = { checksum = "sha256:0a73413f89efd417871876c9accaab28a9d1e3cd6358fbfff171a38ec99302f0", url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"} -"platforms.macos-arm64" = { checksum = "sha256:4703cdf18b26798fde7b49b6b66149674c25f97127be6a10dbcf29309bdcdcdb", url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-aarch64-apple-darwin-install_only_stripped.tar.gz"} -"platforms.macos-x64" = { checksum = "sha256:76f1cc26e3d262eae8ca546a93e8bded10cf0323613f7e246fea2e10a8115eb7", url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-apple-darwin-install_only_stripped.tar.gz"} -"platforms.windows-x64" = { checksum = "sha256:950c5f21a015c1bdd1337f233456df2470fab71e4d794407d27a84cb8b9909a0", url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-pc-windows-msvc-install_only_stripped.tar.gz"} + +[tools.python."platforms.linux-arm64"] +checksum = "sha256:be0f4dc2932f762292b27d46ea7d3e8e66ddf3969a5eb0254a229015ed402625" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz" + +[tools.python."platforms.linux-x64"] +checksum = "sha256:0a73413f89efd417871876c9accaab28a9d1e3cd6358fbfff171a38ec99302f0" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz" + +[tools.python."platforms.macos-arm64"] +checksum = "sha256:4703cdf18b26798fde7b49b6b66149674c25f97127be6a10dbcf29309bdcdcdb" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-aarch64-apple-darwin-install_only_stripped.tar.gz" + +[tools.python."platforms.macos-x64"] +checksum = "sha256:76f1cc26e3d262eae8ca546a93e8bded10cf0323613f7e246fea2e10a8115eb7" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-apple-darwin-install_only_stripped.tar.gz" + +[tools.python."platforms.windows-x64"] +checksum = "sha256:950c5f21a015c1bdd1337f233456df2470fab71e4d794407d27a84cb8b9909a0" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-pc-windows-msvc-install_only_stripped.tar.gz" [[tools.rust]] version = "1.93.0" diff --git a/mise.toml b/mise.toml index 9e1e802..3a04a6f 100644 --- a/mise.toml +++ b/mise.toml @@ -11,6 +11,7 @@ protoc = "29.6" "cargo:cargo-insta" = "1.46.3" python = "3.14.3" ast-grep = "0.42.0" +"cargo:cargo-edit" = "0.13.9" [tasks.codegen] sources = ['protobufs/*.proto'] diff --git a/protobufs/user_agent.proto b/protobufs/user_agent.proto index 397760f..84d3d3d 100644 --- a/protobufs/user_agent.proto +++ b/protobufs/user_agent.proto @@ -13,6 +13,44 @@ enum KeyType { KEY_TYPE_RSA = 3; } +// --- SDK client management --- + +enum SdkClientError { + SDK_CLIENT_ERROR_UNSPECIFIED = 0; + SDK_CLIENT_ERROR_ALREADY_EXISTS = 1; + SDK_CLIENT_ERROR_NOT_FOUND = 2; + SDK_CLIENT_ERROR_HAS_RELATED_DATA = 3; // hard-delete blocked by FK (client has grants or transaction logs) + SDK_CLIENT_ERROR_INTERNAL = 4; +} + +message SdkClientRevokeRequest { + int32 client_id = 1; +} + +message SdkClientEntry { + int32 id = 1; + bytes pubkey = 2; + int32 created_at = 3; +} + +message SdkClientList { + repeated SdkClientEntry clients = 1; +} + +message SdkClientRevokeResponse { + oneof result { + google.protobuf.Empty ok = 1; + SdkClientError error = 2; + } +} + +message SdkClientListResponse { + oneof result { + SdkClientList clients = 1; + SdkClientError error = 2; + } +} + message AuthChallengeRequest { bytes pubkey = 1; optional string bootstrap_token = 2; @@ -79,22 +117,22 @@ enum VaultState { VAULT_STATE_ERROR = 4; } -message ClientConnectionRequest { +message SdkClientConnectionRequest { bytes pubkey = 1; arbiter.client.ClientInfo info = 2; } -message ClientConnectionResponse { +message SdkClientConnectionResponse { bool approved = 1; bytes pubkey = 2; } -message ClientConnectionCancel { +message SdkClientConnectionCancel { bytes pubkey = 1; } message UserAgentRequest { - int32 id = 14; + int32 id = 16; oneof payload { AuthChallengeRequest auth_challenge_request = 1; AuthChallengeSolution auth_challenge_solution = 2; @@ -106,12 +144,14 @@ message UserAgentRequest { arbiter.evm.EvmGrantCreateRequest evm_grant_create = 8; arbiter.evm.EvmGrantDeleteRequest evm_grant_delete = 9; arbiter.evm.EvmGrantListRequest evm_grant_list = 10; - ClientConnectionResponse client_connection_response = 11; - BootstrapEncryptedKey bootstrap_encrypted_key = 12; + SdkClientConnectionResponse sdk_client_connection_response = 11; + SdkClientRevokeRequest sdk_client_revoke = 13; + google.protobuf.Empty sdk_client_list = 14; + BootstrapEncryptedKey bootstrap_encrypted_key = 15; } } message UserAgentResponse { - optional int32 id = 14; + optional int32 id = 16; oneof payload { AuthChallenge auth_challenge = 1; AuthResult auth_result = 2; @@ -123,8 +163,10 @@ message UserAgentResponse { arbiter.evm.EvmGrantCreateResponse evm_grant_create = 8; arbiter.evm.EvmGrantDeleteResponse evm_grant_delete = 9; arbiter.evm.EvmGrantListResponse evm_grant_list = 10; - ClientConnectionRequest client_connection_request = 11; - ClientConnectionCancel client_connection_cancel = 12; - BootstrapResult bootstrap_result = 13; + SdkClientConnectionRequest sdk_client_connection_request = 11; + SdkClientConnectionCancel sdk_client_connection_cancel = 12; + SdkClientRevokeResponse sdk_client_revoke_response = 13; + SdkClientListResponse sdk_client_list_response = 14; + BootstrapResult bootstrap_result = 15; } } diff --git a/server/Cargo.lock b/server/Cargo.lock index 057a88b..32a1587 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -67,13 +67,13 @@ dependencies = [ [[package]] name = "alloy-chains" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9d22005bf31b018f31ef9ecadb5d2c39cf4f6acc8db0456f72c815f3d7f757" +checksum = "9247f0a399ef71aeb68f497b2b8fb348014f742b50d3b83b1e00dfe1b7d64b3d" dependencies = [ "alloy-primitives", "num_enum", - "strum", + "strum 0.27.2", ] [[package]] @@ -100,7 +100,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -136,7 +136,7 @@ dependencies = [ "futures", "futures-util", "serde_json", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -165,7 +165,7 @@ dependencies = [ "itoa", "serde", "serde_json", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -178,7 +178,7 @@ dependencies = [ "alloy-rlp", "crc", "serde", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -203,7 +203,7 @@ dependencies = [ "alloy-rlp", "borsh", "serde", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -239,7 +239,7 @@ dependencies = [ "serde", "serde_with", "sha2 0.10.9", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -280,7 +280,7 @@ dependencies = [ "http", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", "tracing", ] @@ -307,7 +307,7 @@ dependencies = [ "futures-utils-wasm", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -382,7 +382,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", "tokio", "tracing", "url", @@ -471,11 +471,11 @@ dependencies = [ "alloy-rlp", "alloy-serde", "alloy-sol-types", - "itertools 0.14.0", + "itertools 0.13.0", "serde", "serde_json", "serde_with", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -501,7 +501,7 @@ dependencies = [ "either", "elliptic-curve", "k256", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -517,7 +517,7 @@ dependencies = [ "async-trait", "k256", "rand 0.8.5", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -578,7 +578,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6df77fea9d6a2a75c0ef8d2acbdfd92286cc599983d3175ccdc170d3433d249" dependencies = [ "serde", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -608,7 +608,7 @@ dependencies = [ "parking_lot", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", "tokio", "tower", "tracing", @@ -624,7 +624,7 @@ checksum = "aa501ad58dd20acddbfebc65b52e60f05ebf97c52fa40d1b35e91f5e2da0ad0e" dependencies = [ "alloy-json-rpc", "alloy-transport", - "itertools 0.14.0", + "itertools 0.13.0", "reqwest", "serde_json", "tower", @@ -644,7 +644,7 @@ dependencies = [ "nybbles", "serde", "smallvec", - "thiserror", + "thiserror 2.0.18", "tracing", ] @@ -678,6 +678,19 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arbiter-client" version = "0.1.0" +dependencies = [ + "alloy", + "arbiter-proto", + "async-trait", + "ed25519-dalek", + "http", + "rand 0.10.0", + "rustls-webpki", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tonic", +] [[package]] name = "arbiter-proto" @@ -691,11 +704,12 @@ dependencies = [ "miette", "prost", "prost-types", + "protoc-bin-vendored", "rand 0.10.0", "rcgen", "rstest", "rustls-pki-types", - "thiserror", + "thiserror 2.0.18", "tokio", "tokio-stream", "tonic", @@ -721,6 +735,7 @@ dependencies = [ "diesel-async", "diesel_migrations", "ed25519-dalek", + "fatality", "futures", "insta", "k256", @@ -738,9 +753,9 @@ dependencies = [ "sha2 0.10.9", "smlang", "spki", - "strum", + "strum 0.28.0", "test-log", - "thiserror", + "thiserror 2.0.18", "tokio", "tokio-stream", "tonic", @@ -977,7 +992,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror", + "thiserror 2.0.18", "time", ] @@ -1062,9 +1077,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.16.1" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" +checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -1073,9 +1088,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" +checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a" dependencies = [ "cc", "cmake", @@ -1270,19 +1285,20 @@ dependencies = [ [[package]] name = "borsh" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" dependencies = [ "borsh-derive", + "bytes", "cfg_aliases", ] [[package]] name = "borsh-derive" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" dependencies = [ "once_cell", "proc-macro-crate", @@ -1796,15 +1812,16 @@ dependencies = [ [[package]] name = "diesel-async" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13096fb8dae53f2d411c4b523bec85f45552ed3044a2ab4d85fb2092d9cb4f34" +checksum = "b95864e58597509106f1fddfe0600de7e589e1fddddd87f54eee0a49fd111bbc" dependencies = [ "bb8", "diesel", "diesel_migrations", "futures-core", "futures-util", + "pin-project-lite", "scoped-futures", "tokio", ] @@ -2034,7 +2051,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", +] + +[[package]] +name = "expander" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2c470c71d91ecbd179935b24170459e926382eaaa86b590b78814e180d8a8e2" +dependencies = [ + "blake2", + "file-guard", + "fs-err", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -2065,6 +2097,30 @@ dependencies = [ "bytes", ] +[[package]] +name = "fatality" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec6f82451ff7f0568c6181287189126d492b5654e30a788add08027b6363d019" +dependencies = [ + "fatality-proc-macro", + "thiserror 1.0.69", +] + +[[package]] +name = "fatality-proc-macro" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb42427514b063d97ce21d5199f36c0c307d981434a6be32582bc79fe5bd2303" +dependencies = [ + "expander", + "indexmap 2.13.0", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "ff" version = "0.13.1" @@ -2087,6 +2143,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" +[[package]] +name = "file-guard" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21ef72acf95ec3d7dbf61275be556299490a245f017cf084bd23b4f68cf9407c" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -2148,6 +2214,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs-err" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -2796,20 +2871,11 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jobserver" @@ -3120,7 +3186,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3197,9 +3263,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ "num_enum_derive", "rustversion", @@ -3207,9 +3273,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ "proc-macro2", "quote", @@ -3601,7 +3667,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ "heck", - "itertools 0.14.0", + "itertools 0.13.0", "log", "multimap", "petgraph", @@ -3622,7 +3688,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.13.0", "proc-macro2", "quote", "syn 2.0.117", @@ -3639,10 +3705,74 @@ dependencies = [ ] [[package]] -name = "pulldown-cmark" -version = "0.13.1" +name = "protoc-bin-vendored" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c41efbf8f90ac44de7f3a868f0867851d261b56291732d0cbf7cceaaeb55a6" +checksum = "d1c381df33c98266b5f08186583660090a4ffa0889e76c7e9a5e175f645a67fa" +dependencies = [ + "protoc-bin-vendored-linux-aarch_64", + "protoc-bin-vendored-linux-ppcle_64", + "protoc-bin-vendored-linux-s390_64", + "protoc-bin-vendored-linux-x86_32", + "protoc-bin-vendored-linux-x86_64", + "protoc-bin-vendored-macos-aarch_64", + "protoc-bin-vendored-macos-x86_64", + "protoc-bin-vendored-win32", +] + +[[package]] +name = "protoc-bin-vendored-linux-aarch_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c350df4d49b5b9e3ca79f7e646fde2377b199e13cfa87320308397e1f37e1a4c" + +[[package]] +name = "protoc-bin-vendored-linux-ppcle_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55a63e6c7244f19b5c6393f025017eb5d793fd5467823a099740a7a4222440c" + +[[package]] +name = "protoc-bin-vendored-linux-s390_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dba5565db4288e935d5330a07c264a4ee8e4a5b4a4e6f4e83fad824cc32f3b0" + +[[package]] +name = "protoc-bin-vendored-linux-x86_32" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8854774b24ee28b7868cd71dccaae8e02a2365e67a4a87a6cd11ee6cdbdf9cf5" + +[[package]] +name = "protoc-bin-vendored-linux-x86_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b38b07546580df720fa464ce124c4b03630a6fb83e05c336fea2a241df7e5d78" + +[[package]] +name = "protoc-bin-vendored-macos-aarch_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89278a9926ce312e51f1d999fee8825d324d603213344a9a706daa009f1d8092" + +[[package]] +name = "protoc-bin-vendored-macos-x86_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81745feda7ccfb9471d7a4de888f0652e806d5795b61480605d4943176299756" + +[[package]] +name = "protoc-bin-vendored-win32" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95067976aca6421a523e491fce939a3e65249bac4b977adee0ee9771568e8aa3" + +[[package]] +name = "pulldown-cmark" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14104c5a24d9bcf7eb2c24753e0f49fe14555d8bd565ea3d38e4b4303267259d" dependencies = [ "bitflags", "memchr", @@ -3678,7 +3808,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -3699,7 +3829,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -4034,7 +4164,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d" dependencies = [ "hashbrown 0.16.1", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -4155,7 +4285,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -4186,9 +4316,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ "aws-lc-rs", "ring", @@ -4571,7 +4701,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -4632,7 +4762,16 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros", + "strum_macros 0.27.2", +] + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros 0.28.0", ] [[package]] @@ -4647,6 +4786,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "subtle" version = "2.6.1" @@ -4744,7 +4895,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -4788,13 +4939,33 @@ dependencies = [ "unicode-width 0.2.2", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -4956,7 +5127,7 @@ dependencies = [ "serde_spanned", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -4970,32 +5141,32 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.0.0+spec-1.1.0" +version = "1.0.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.25.4+spec-1.1.0" +version = "0.25.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" +checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1" dependencies = [ "indexmap 2.13.0", - "toml_datetime 1.0.0+spec-1.1.0", + "toml_datetime 1.0.1+spec-1.1.0", "toml_parser", - "winnow", + "winnow 1.0.0", ] [[package]] name = "toml_parser" -version = "1.0.9+spec-1.1.0" +version = "1.0.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" dependencies = [ - "winnow", + "winnow 1.0.0", ] [[package]] @@ -5759,6 +5930,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.51.0" @@ -5888,7 +6068,7 @@ dependencies = [ "nom", "oid-registry", "rusticata-macros", - "thiserror", + "thiserror 2.0.18", "time", ] @@ -5926,18 +6106,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.42" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.42" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" dependencies = [ "proc-macro2", "quote", diff --git a/server/Cargo.toml b/server/Cargo.toml index 6556f86..ddc9416 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -9,23 +9,23 @@ disallowed-methods = "deny" [workspace.dependencies] -tonic = { version = "0.14.3", features = [ +tonic = { version = "0.14.5", features = [ "deflate", "gzip", "tls-connect-info", "zstd", ] } tracing = "0.1.44" -tokio = { version = "1.49.0", features = ["full"] } +tokio = { version = "1.50.0", features = ["full"] } ed25519-dalek = { version = "3.0.0-pre.6", features = ["rand_core"] } -chrono = { version = "0.4.43", features = ["serde"] } +chrono = { version = "0.4.44", features = ["serde"] } rand = "0.10.0" -rustls = { version = "0.23.36", features = ["aws-lc-rs"] } +rustls = { version = "0.23.37", features = ["aws-lc-rs"] } smlang = "0.8.0" miette = { version = "7.6.0", features = ["fancy", "serde"] } thiserror = "2.0.18" async-trait = "0.1.89" -futures = "0.3.31" +futures = "0.3.32" tokio-stream = { version = "0.1.18", features = ["full"] } kameo = "0.19.2" prost-types = { version = "0.14.3", features = ["chrono"] } diff --git a/server/crates/arbiter-client/Cargo.toml b/server/crates/arbiter-client/Cargo.toml index e71c9e7..f5e353b 100644 --- a/server/crates/arbiter-client/Cargo.toml +++ b/server/crates/arbiter-client/Cargo.toml @@ -5,4 +5,22 @@ edition = "2024" repository = "https://git.markettakers.org/MarketTakers/arbiter" license = "Apache-2.0" +[lints] +workspace = true + +[features] +evm = ["dep:alloy"] + [dependencies] +arbiter-proto.path = "../arbiter-proto" +alloy = { workspace = true, optional = true } +tonic.workspace = true +tonic.features = ["tls-aws-lc"] +tokio.workspace = true +tokio-stream.workspace = true +ed25519-dalek.workspace = true +thiserror.workspace = true +http = "1.4.0" +rustls-webpki = { version = "0.103.10", features = ["aws-lc-rs"] } +async-trait.workspace = true +rand.workspace = true diff --git a/server/crates/arbiter-client/src/auth.rs b/server/crates/arbiter-client/src/auth.rs new file mode 100644 index 0000000..be1a608 --- /dev/null +++ b/server/crates/arbiter-client/src/auth.rs @@ -0,0 +1,147 @@ +use arbiter_proto::{ + ClientMetadata, format_challenge, + proto::client::{ + AuthChallengeRequest, AuthChallengeSolution, AuthResult, ClientInfo as ProtoClientInfo, + ClientRequest, client_request::Payload as ClientRequestPayload, + client_response::Payload as ClientResponsePayload, + }, +}; +use ed25519_dalek::Signer as _; + +use crate::{ + storage::StorageError, + transport::{ClientTransport, next_request_id}, +}; + +#[derive(Debug, thiserror::Error)] +pub enum ConnectError { + #[error("Could not establish connection")] + Connection(#[from] tonic::transport::Error), + + #[error("Invalid server URI")] + InvalidUri(#[from] http::uri::InvalidUri), + + #[error("Invalid CA certificate")] + InvalidCaCert(#[from] webpki::Error), + + #[error("gRPC error")] + Grpc(#[from] tonic::Status), + + #[error("Auth challenge was not returned by server")] + MissingAuthChallenge, + + #[error("Client approval denied by User Agent")] + ApprovalDenied, + + #[error("No User Agents online to approve client")] + NoUserAgentsOnline, + + #[error("Unexpected auth response payload")] + UnexpectedAuthResponse, + + #[error("Signing key storage error")] + Storage(#[from] StorageError), +} + +fn map_auth_result(code: i32) -> ConnectError { + match AuthResult::try_from(code).unwrap_or(AuthResult::Unspecified) { + AuthResult::ApprovalDenied => ConnectError::ApprovalDenied, + AuthResult::NoUserAgentsOnline => ConnectError::NoUserAgentsOnline, + AuthResult::Unspecified + | AuthResult::Success + | AuthResult::InvalidKey + | AuthResult::InvalidSignature + | AuthResult::Internal => ConnectError::UnexpectedAuthResponse, + } +} + +async fn send_auth_challenge_request( + transport: &mut ClientTransport, + metadata: ClientMetadata, + key: &ed25519_dalek::SigningKey, +) -> std::result::Result<(), ConnectError> { + transport + .send(ClientRequest { + request_id: next_request_id(), + payload: Some(ClientRequestPayload::AuthChallengeRequest( + AuthChallengeRequest { + pubkey: key.verifying_key().to_bytes().to_vec(), + client_info: Some(ProtoClientInfo { + name: metadata.name, + description: metadata.description, + version: metadata.version, + }), + }, + )), + }) + .await + .map_err(|_| ConnectError::UnexpectedAuthResponse) +} + +async fn receive_auth_challenge( + transport: &mut ClientTransport, +) -> std::result::Result { + let response = transport + .recv() + .await + .map_err(|_| ConnectError::MissingAuthChallenge)?; + + let payload = response.payload.ok_or(ConnectError::MissingAuthChallenge)?; + match payload { + ClientResponsePayload::AuthChallenge(challenge) => Ok(challenge), + ClientResponsePayload::AuthResult(result) => Err(map_auth_result(result)), + _ => Err(ConnectError::UnexpectedAuthResponse), + } +} + +async fn send_auth_challenge_solution( + transport: &mut ClientTransport, + key: &ed25519_dalek::SigningKey, + challenge: arbiter_proto::proto::client::AuthChallenge, +) -> std::result::Result<(), ConnectError> { + let challenge_payload = format_challenge(challenge.nonce, &challenge.pubkey); + let signature = key.sign(&challenge_payload).to_bytes().to_vec(); + + transport + .send(ClientRequest { + request_id: next_request_id(), + payload: Some(ClientRequestPayload::AuthChallengeSolution( + AuthChallengeSolution { signature }, + )), + }) + .await + .map_err(|_| ConnectError::UnexpectedAuthResponse) +} + +async fn receive_auth_confirmation( + transport: &mut ClientTransport, +) -> std::result::Result<(), ConnectError> { + let response = transport + .recv() + .await + .map_err(|_| ConnectError::UnexpectedAuthResponse)?; + + let payload = response + .payload + .ok_or(ConnectError::UnexpectedAuthResponse)?; + match payload { + ClientResponsePayload::AuthResult(result) + if AuthResult::try_from(result).ok() == Some(AuthResult::Success) => + { + Ok(()) + } + ClientResponsePayload::AuthResult(result) => Err(map_auth_result(result)), + _ => Err(ConnectError::UnexpectedAuthResponse), + } +} + +pub(crate) async fn authenticate( + transport: &mut ClientTransport, + metadata: ClientMetadata, + key: &ed25519_dalek::SigningKey, +) -> std::result::Result<(), ConnectError> { + send_auth_challenge_request(transport, metadata, key).await?; + let challenge = receive_auth_challenge(transport).await?; + send_auth_challenge_solution(transport, key, challenge).await?; + receive_auth_confirmation(transport).await +} diff --git a/server/crates/arbiter-client/src/client.rs b/server/crates/arbiter-client/src/client.rs new file mode 100644 index 0000000..927a484 --- /dev/null +++ b/server/crates/arbiter-client/src/client.rs @@ -0,0 +1,78 @@ +use arbiter_proto::{ClientMetadata, proto::arbiter_service_client::ArbiterServiceClient, url::ArbiterUrl}; +use std::sync::Arc; +use tokio::sync::{Mutex, mpsc}; +use tokio_stream::wrappers::ReceiverStream; +use tonic::transport::ClientTlsConfig; + +use crate::{ + auth::{ConnectError, authenticate}, + storage::{FileSigningKeyStorage, SigningKeyStorage}, + transport::{BUFFER_LENGTH, ClientTransport}, +}; + +#[cfg(feature = "evm")] +use crate::wallets::evm::ArbiterEvmWallet; + +#[derive(Debug, thiserror::Error)] +pub enum ClientError { + #[error("gRPC error")] + Grpc(#[from] tonic::Status), + + #[error("Connection closed by server")] + ConnectionClosed, +} + +pub struct ArbiterClient { + #[allow(dead_code)] + transport: Arc>, +} + +impl ArbiterClient { + pub async fn connect(url: ArbiterUrl, metadata: ClientMetadata) -> Result { + let storage = FileSigningKeyStorage::from_default_location()?; + Self::connect_with_storage(url, metadata, &storage).await + } + + pub async fn connect_with_storage( + url: ArbiterUrl, + metadata: ClientMetadata, + storage: &S, + ) -> Result { + let key = storage.load_or_create()?; + Self::connect_with_key(url, metadata, key).await + } + + pub async fn connect_with_key( + url: ArbiterUrl, + metadata: ClientMetadata, + key: ed25519_dalek::SigningKey, + ) -> Result { + let anchor = webpki::anchor_from_trusted_cert(&url.ca_cert)?.to_owned(); + let tls = ClientTlsConfig::new().trust_anchor(anchor); + + let channel = tonic::transport::Channel::from_shared(format!("{}:{}", url.host, url.port))? + .tls_config(tls)? + .connect() + .await?; + + let mut client = ArbiterServiceClient::new(channel); + let (tx, rx) = mpsc::channel(BUFFER_LENGTH); + let response_stream = client.client(ReceiverStream::new(rx)).await?.into_inner(); + + let mut transport = ClientTransport { + sender: tx, + receiver: response_stream, + }; + + authenticate(&mut transport, metadata, &key).await?; + + Ok(Self { + transport: Arc::new(Mutex::new(transport)), + }) + } + + #[cfg(feature = "evm")] + pub async fn evm_wallets(&self) -> Result, ClientError> { + todo!("fetch EVM wallet list from server") + } +} diff --git a/server/crates/arbiter-client/src/lib.rs b/server/crates/arbiter-client/src/lib.rs index b93cf3f..1be4c38 100644 --- a/server/crates/arbiter-client/src/lib.rs +++ b/server/crates/arbiter-client/src/lib.rs @@ -1,14 +1,12 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} +mod auth; +mod client; +mod storage; +mod transport; +pub mod wallets; -#[cfg(test)] -mod tests { - use super::*; +pub use auth::ConnectError; +pub use client::{ArbiterClient, ClientError}; +pub use storage::{FileSigningKeyStorage, SigningKeyStorage, StorageError}; - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +#[cfg(feature = "evm")] +pub use wallets::evm::ArbiterEvmWallet; diff --git a/server/crates/arbiter-client/src/storage.rs b/server/crates/arbiter-client/src/storage.rs new file mode 100644 index 0000000..17d0bf2 --- /dev/null +++ b/server/crates/arbiter-client/src/storage.rs @@ -0,0 +1,132 @@ +use arbiter_proto::home_path; +use std::path::{Path, PathBuf}; + +#[derive(Debug, thiserror::Error)] +pub enum StorageError { + #[error("I/O error")] + Io(#[from] std::io::Error), + + #[error("Invalid signing key length in storage: expected {expected} bytes, got {actual} bytes")] + InvalidKeyLength { expected: usize, actual: usize }, +} + +pub trait SigningKeyStorage { + fn load_or_create(&self) -> std::result::Result; +} + +#[derive(Debug, Clone)] +pub struct FileSigningKeyStorage { + path: PathBuf, +} + +impl FileSigningKeyStorage { + pub const DEFAULT_FILE_NAME: &str = "sdk_client_ed25519.key"; + + pub fn new(path: impl Into) -> Self { + Self { path: path.into() } + } + + pub fn from_default_location() -> std::result::Result { + Ok(Self::new(home_path()?.join(Self::DEFAULT_FILE_NAME))) + } + + fn read_key(path: &Path) -> std::result::Result { + let bytes = std::fs::read(path)?; + let raw: [u8; 32] = + bytes + .try_into() + .map_err(|v: Vec| StorageError::InvalidKeyLength { + expected: 32, + actual: v.len(), + })?; + Ok(ed25519_dalek::SigningKey::from_bytes(&raw)) + } +} + +impl SigningKeyStorage for FileSigningKeyStorage { + fn load_or_create(&self) -> std::result::Result { + if let Some(parent) = self.path.parent() { + std::fs::create_dir_all(parent)?; + } + + if self.path.exists() { + return Self::read_key(&self.path); + } + + let key = ed25519_dalek::SigningKey::generate(&mut rand::rng()); + let raw_key = key.to_bytes(); + + // Use create_new to prevent accidental overwrite if another process creates the key first. + match std::fs::OpenOptions::new() + .create_new(true) + .write(true) + .open(&self.path) + { + Ok(mut file) => { + use std::io::Write as _; + file.write_all(&raw_key)?; + Ok(key) + } + Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => { + Self::read_key(&self.path) + } + Err(err) => Err(StorageError::Io(err)), + } + } +} + +#[cfg(test)] +mod tests { + use super::{FileSigningKeyStorage, SigningKeyStorage, StorageError}; + + fn unique_temp_key_path() -> std::path::PathBuf { + let nanos = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("clock should be after unix epoch") + .as_nanos(); + std::env::temp_dir().join(format!( + "arbiter-client-key-{}-{}.bin", + std::process::id(), + nanos + )) + } + + #[test] + fn file_storage_creates_and_reuses_key() { + let path = unique_temp_key_path(); + let storage = FileSigningKeyStorage::new(path.clone()); + + let key_a = storage + .load_or_create() + .expect("first load_or_create should create key"); + let key_b = storage + .load_or_create() + .expect("second load_or_create should read same key"); + + assert_eq!(key_a.to_bytes(), key_b.to_bytes()); + assert!(path.exists()); + + std::fs::remove_file(path).expect("temp key file should be removable"); + } + + #[test] + fn file_storage_rejects_invalid_key_length() { + let path = unique_temp_key_path(); + std::fs::write(&path, [42u8; 31]).expect("should write invalid key file"); + let storage = FileSigningKeyStorage::new(path.clone()); + + let err = storage + .load_or_create() + .expect_err("storage should reject non-32-byte key file"); + + match err { + StorageError::InvalidKeyLength { expected, actual } => { + assert_eq!(expected, 32); + assert_eq!(actual, 31); + } + other => panic!("unexpected error: {other:?}"), + } + + std::fs::remove_file(path).expect("temp key file should be removable"); + } +} diff --git a/server/crates/arbiter-client/src/transport.rs b/server/crates/arbiter-client/src/transport.rs new file mode 100644 index 0000000..d56a9f8 --- /dev/null +++ b/server/crates/arbiter-client/src/transport.rs @@ -0,0 +1,48 @@ +use arbiter_proto::proto::{ + client::{ClientRequest, ClientResponse}, +}; +use std::sync::atomic::{AtomicI32, Ordering}; +use tokio::sync::mpsc; + +pub(crate) const BUFFER_LENGTH: usize = 16; +static NEXT_REQUEST_ID: AtomicI32 = AtomicI32::new(1); + +pub(crate) fn next_request_id() -> i32 { + NEXT_REQUEST_ID.fetch_add(1, Ordering::Relaxed) +} + +#[derive(Debug, thiserror::Error)] +pub(crate) enum ClientSignError { + #[error("Transport channel closed")] + ChannelClosed, + + #[error("Connection closed by server")] + ConnectionClosed, +} + +pub(crate) struct ClientTransport { + pub(crate) sender: mpsc::Sender, + pub(crate) receiver: tonic::Streaming, +} + +impl ClientTransport { + pub(crate) async fn send( + &mut self, + request: ClientRequest, + ) -> std::result::Result<(), ClientSignError> { + self.sender + .send(request) + .await + .map_err(|_| ClientSignError::ChannelClosed) + } + + pub(crate) async fn recv( + &mut self, + ) -> std::result::Result { + match self.receiver.message().await { + Ok(Some(resp)) => Ok(resp), + Ok(None) => Err(ClientSignError::ConnectionClosed), + Err(_) => Err(ClientSignError::ConnectionClosed), + } + } +} diff --git a/server/crates/arbiter-client/src/wallets/evm.rs b/server/crates/arbiter-client/src/wallets/evm.rs new file mode 100644 index 0000000..32ae735 --- /dev/null +++ b/server/crates/arbiter-client/src/wallets/evm.rs @@ -0,0 +1,89 @@ +use alloy::{ + consensus::SignableTransaction, + network::TxSigner, + primitives::{Address, B256, ChainId, Signature}, + signers::{Error, Result, Signer}, +}; +use async_trait::async_trait; +use std::sync::Arc; +use tokio::sync::Mutex; + +use crate::transport::ClientTransport; + +pub struct ArbiterEvmWallet { + transport: Arc>, + address: Address, + chain_id: Option, +} + +impl ArbiterEvmWallet { + pub(crate) fn new(transport: Arc>, address: Address) -> Self { + Self { + transport, + address, + chain_id: None, + } + } + + pub fn address(&self) -> Address { + self.address + } + + pub fn with_chain_id(mut self, chain_id: ChainId) -> Self { + self.chain_id = Some(chain_id); + self + } + + fn validate_chain_id(&self, tx: &mut dyn SignableTransaction) -> Result<()> { + if let Some(chain_id) = self.chain_id + && !tx.set_chain_id_checked(chain_id) + { + return Err(Error::TransactionChainIdMismatch { + signer: chain_id, + tx: tx.chain_id().unwrap(), + }); + } + + Ok(()) + } +} + +#[async_trait] +impl Signer for ArbiterEvmWallet { + async fn sign_hash(&self, _hash: &B256) -> Result { + Err(Error::other( + "hash-only signing is not supported for ArbiterEvmWallet; use transaction signing", + )) + } + + fn address(&self) -> Address { + self.address + } + + fn chain_id(&self) -> Option { + self.chain_id + } + + fn set_chain_id(&mut self, chain_id: Option) { + self.chain_id = chain_id; + } +} + +#[async_trait] +impl TxSigner for ArbiterEvmWallet { + fn address(&self) -> Address { + self.address + } + + async fn sign_transaction( + &self, + tx: &mut dyn SignableTransaction, + ) -> Result { + let _transport = self.transport.lock().await; + self.validate_chain_id(tx)?; + + Err(Error::other( + "transaction signing is not supported by current arbiter.client protocol", + )) + } +} diff --git a/server/crates/arbiter-client/src/wallets/mod.rs b/server/crates/arbiter-client/src/wallets/mod.rs new file mode 100644 index 0000000..b2c917e --- /dev/null +++ b/server/crates/arbiter-client/src/wallets/mod.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "evm")] +pub mod evm; diff --git a/server/crates/arbiter-proto/Cargo.toml b/server/crates/arbiter-proto/Cargo.toml index 88676a0..8299691 100644 --- a/server/crates/arbiter-proto/Cargo.toml +++ b/server/crates/arbiter-proto/Cargo.toml @@ -10,7 +10,7 @@ tonic.workspace = true tokio.workspace = true futures.workspace = true hex = "0.4.3" -tonic-prost = "0.14.3" +tonic-prost = "0.14.5" prost = "0.14.3" kameo.workspace = true url = "2.5.8" @@ -24,7 +24,8 @@ async-trait.workspace = true tokio-stream.workspace = true [build-dependencies] -tonic-prost-build = "0.14.3" +tonic-prost-build = "0.14.5" +protoc-bin-vendored = "3" [dev-dependencies] rstest.workspace = true @@ -33,5 +34,3 @@ rcgen.workspace = true [package.metadata.cargo-shear] ignored = ["tonic-prost", "prost", "kameo"] - - diff --git a/server/crates/arbiter-proto/src/lib.rs b/server/crates/arbiter-proto/src/lib.rs index cf90576..732ee65 100644 --- a/server/crates/arbiter-proto/src/lib.rs +++ b/server/crates/arbiter-proto/src/lib.rs @@ -19,6 +19,13 @@ pub mod proto { } } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ClientMetadata { + pub name: String, + pub description: Option, + pub version: Option, +} + pub static BOOTSTRAP_PATH: &str = "bootstrap_token"; pub fn home_path() -> Result { diff --git a/server/crates/arbiter-server/Cargo.toml b/server/crates/arbiter-server/Cargo.toml index 62a9afa..8996fce 100644 --- a/server/crates/arbiter-server/Cargo.toml +++ b/server/crates/arbiter-server/Cargo.toml @@ -9,8 +9,8 @@ license = "Apache-2.0" workspace = true [dependencies] -diesel = { version = "2.3.6", features = ["chrono", "returning_clauses_for_sqlite_3_35", "serde_json", "time", "uuid"] } -diesel-async = { version = "0.7.4", features = [ +diesel = { version = "2.3.7", features = ["chrono", "returning_clauses_for_sqlite_3_35", "serde_json", "time", "uuid"] } +diesel-async = { version = "0.8.0", features = [ "bb8", "migrations", "sqlite", @@ -27,6 +27,7 @@ rustls.workspace = true smlang.workspace = true miette.workspace = true thiserror.workspace = true +fatality = "0.1.1" diesel_migrations = { version = "2.3.1", features = ["sqlite"] } async-trait.workspace = true secrecy = "0.10.3" @@ -43,7 +44,7 @@ x25519-dalek.workspace = true chacha20poly1305 = { version = "0.10.1", features = ["std"] } argon2 = { version = "0.5.3", features = ["zeroize"] } restructed = "0.2.2" -strum = { version = "0.27.2", features = ["derive"] } +strum = { version = "0.28.0", features = ["derive"] } pem = "3.0.6" k256.workspace = true rsa.workspace = true diff --git a/server/crates/arbiter-server/migrations/2026-02-14-171124-0000_init/up.sql b/server/crates/arbiter-server/migrations/2026-02-14-171124-0000_init/up.sql index 6eed200..79b1f7a 100644 --- a/server/crates/arbiter-server/migrations/2026-02-14-171124-0000_init/up.sql +++ b/server/crates/arbiter-server/migrations/2026-02-14-171124-0000_init/up.sql @@ -80,6 +80,9 @@ create table if not exists program_client ( updated_at integer not null default(unixepoch ('now')) ) STRICT; +create unique index if not exists program_client_public_key_unique + on program_client (public_key); + create unique index if not exists uniq_program_client_public_key on program_client (public_key); create table if not exists evm_wallet ( diff --git a/server/crates/arbiter-server/src/actors/client/auth.rs b/server/crates/arbiter-server/src/actors/client/auth.rs index 67a94a4..4fd53bf 100644 --- a/server/crates/arbiter-server/src/actors/client/auth.rs +++ b/server/crates/arbiter-server/src/actors/client/auth.rs @@ -1,6 +1,5 @@ use arbiter_proto::{ - format_challenge, - transport::{Bi, expect_message}, + ClientMetadata, format_challenge, transport::{Bi, expect_message} }; use chrono::Utc; use diesel::{ @@ -24,13 +23,6 @@ use crate::{ }, }; -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ClientMetadata { - pub name: String, - pub description: Option, - pub version: Option, -} - #[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)] pub enum Error { #[error("Database pool unavailable")] @@ -72,9 +64,17 @@ pub enum Outbound { AuthSuccess, } +pub struct ClientInfo { + pub id: i32, + pub current_nonce: i32, +} + /// Atomically reads and increments the nonce for a known client. /// Returns `None` if the pubkey is not registered. -async fn get_nonce(db: &db::DatabasePool, pubkey: &VerifyingKey) -> Result, Error> { +async fn get_client_and_nonce( + db: &db::DatabasePool, + pubkey: &VerifyingKey, +) -> Result, Error> { let pubkey_bytes = pubkey.as_bytes().to_vec(); let mut conn = db.get().await.map_err(|e| { @@ -85,10 +85,10 @@ async fn get_nonce(db: &db::DatabasePool, pubkey: &VerifyingKey) -> Result(conn) + .select((program_client::id, program_client::nonce)) + .first::<(i32, i32)>(conn) .await .optional()? else { @@ -101,7 +101,10 @@ async fn get_nonce(db: &db::DatabasePool, pubkey: &VerifyingKey) -> Result Result<(), Error> { - use crate::db::schema::client_metadata; - +) -> Result { + use crate::db::schema::{client_metadata, program_client}; let mut conn = db.get().await.map_err(|e| { error!(error = ?e, "Database pool error"); Error::DatabasePoolUnavailable @@ -160,38 +162,22 @@ async fn insert_client( Error::DatabaseOperationFailed })?; - insert_into(program_client::table) + let client_id = insert_into(program_client::table) .values(( program_client::public_key.eq(pubkey.as_bytes().to_vec()), program_client::metadata_id.eq(metadata_id), program_client::nonce.eq(1), // pre-incremented; challenge uses 0 )) - .execute(&mut conn) + .on_conflict_do_nothing() + .returning(program_client::id) + .get_result::(&mut conn) .await .map_err(|e| { - error!(error = ?e, "Failed to insert new client"); + error!(error = ?e, "Failed to insert client metadata"); Error::DatabaseOperationFailed })?; - Ok(()) -} - -async fn get_client_id(db: &db::DatabasePool, pubkey: &VerifyingKey) -> Result, Error> { - let mut conn = db.get().await.map_err(|e| { - error!(error = ?e, "Database pool error"); - Error::DatabasePoolUnavailable - })?; - - program_client::table - .filter(program_client::public_key.eq(pubkey.as_bytes().to_vec())) - .select(program_client::id) - .first::(&mut conn) - .await - .optional() - .map_err(|e| { - error!(error = ?e, "Database error"); - Error::DatabaseOperationFailed - }) + Ok(client_id) } async fn sync_client_metadata( @@ -312,7 +298,7 @@ where return Err(Error::Transport); }; - let nonce = match get_nonce(&props.db, &pubkey).await? { + let info = match get_client_and_nonce(&props.db, &pubkey).await? { Some(nonce) => nonce, None => { approve_new_client( @@ -323,17 +309,18 @@ where }, ) .await?; - insert_client(&props.db, &pubkey, &metadata).await?; - 0 + let client_id = insert_client(&props.db, &pubkey, &metadata).await?; + ClientInfo { + id: client_id, + current_nonce: 0, + } } }; - let client_id = get_client_id(&props.db, &pubkey) - .await? - .ok_or(Error::DatabaseOperationFailed)?; - sync_client_metadata(&props.db, client_id, &metadata).await?; + sync_client_metadata(&props.db, info.id, &metadata).await?; - challenge_client(transport, pubkey, nonce).await?; + challenge_client(transport, pubkey, info.current_nonce).await?; + transport .send(Ok(Outbound::AuthSuccess)) .await diff --git a/server/crates/arbiter-server/src/actors/client/mod.rs b/server/crates/arbiter-server/src/actors/client/mod.rs index 2ad1413..f60e90a 100644 --- a/server/crates/arbiter-server/src/actors/client/mod.rs +++ b/server/crates/arbiter-server/src/actors/client/mod.rs @@ -1,9 +1,9 @@ -use arbiter_proto::transport::Bi; +use arbiter_proto::{ClientMetadata, transport::Bi}; use kameo::actor::Spawn; use tracing::{error, info}; use crate::{ - actors::{GlobalActors, client::{auth::ClientMetadata, session::ClientSession}}, + actors::{GlobalActors, client::{ session::ClientSession}}, db, }; diff --git a/server/crates/arbiter-server/src/actors/user_agent/session.rs b/server/crates/arbiter-server/src/actors/user_agent/session.rs index d44ab3b..70e4668 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/session.rs +++ b/server/crates/arbiter-server/src/actors/user_agent/session.rs @@ -3,7 +3,7 @@ use std::{borrow::Cow, collections::HashMap}; use arbiter_proto::transport::Sender; use async_trait::async_trait; use ed25519_dalek::VerifyingKey; -use kameo::{Actor, actor::ActorRef, messages, prelude::Context}; +use kameo::{Actor, actor::ActorRef, messages}; use thiserror::Error; use tracing::error; diff --git a/server/crates/arbiter-server/src/grpc/client/auth.rs b/server/crates/arbiter-server/src/grpc/client/auth.rs index 31779cc..c711520 100644 --- a/server/crates/arbiter-server/src/grpc/client/auth.rs +++ b/server/crates/arbiter-server/src/grpc/client/auth.rs @@ -1,12 +1,11 @@ use arbiter_proto::{ - proto::client::{ + ClientMetadata, proto::client::{ AuthChallenge as ProtoAuthChallenge, AuthChallengeRequest as ProtoAuthChallengeRequest, AuthChallengeSolution as ProtoAuthChallengeSolution, AuthResult as ProtoAuthResult, ClientInfo as ProtoClientInfo, ClientRequest, ClientResponse, client_request::Payload as ClientRequestPayload, client_response::Payload as ClientResponsePayload, - }, - transport::{Bi, Error as TransportError, Receiver, Sender, grpc::GrpcBi}, + }, transport::{Bi, Error as TransportError, Receiver, Sender, grpc::GrpcBi} }; use async_trait::async_trait; use tonic::Status; @@ -170,8 +169,8 @@ impl Receiver for AuthTransportAdapter<'_> { impl Bi> for AuthTransportAdapter<'_> {} -fn client_metadata_from_proto(metadata: ProtoClientInfo) -> auth::ClientMetadata { - auth::ClientMetadata { +fn client_metadata_from_proto(metadata: ProtoClientInfo) -> ClientMetadata { + ClientMetadata { name: metadata.name, description: metadata.description, version: metadata.version, diff --git a/server/crates/arbiter-server/src/grpc/user_agent.rs b/server/crates/arbiter-server/src/grpc/user_agent.rs index 03dd200..e80370b 100644 --- a/server/crates/arbiter-server/src/grpc/user_agent.rs +++ b/server/crates/arbiter-server/src/grpc/user_agent.rs @@ -20,15 +20,18 @@ use arbiter_proto::{ }, user_agent::{ BootstrapEncryptedKey as ProtoBootstrapEncryptedKey, - BootstrapResult as ProtoBootstrapResult, ClientConnectionCancel, - ClientConnectionRequest, UnsealEncryptedKey as ProtoUnsealEncryptedKey, - UnsealResult as ProtoUnsealResult, UnsealStart, UserAgentRequest, UserAgentResponse, - VaultState as ProtoVaultState, user_agent_request::Payload as UserAgentRequestPayload, + BootstrapResult as ProtoBootstrapResult, + SdkClientConnectionCancel as ProtoSdkClientConnectionCancel, + SdkClientConnectionRequest as ProtoSdkClientConnectionRequest, + UnsealEncryptedKey as ProtoUnsealEncryptedKey, UnsealResult as ProtoUnsealResult, + UnsealStart, UserAgentRequest, UserAgentResponse, VaultState as ProtoVaultState, + user_agent_request::Payload as UserAgentRequestPayload, user_agent_response::Payload as UserAgentResponsePayload, }, }, transport::{Error as TransportError, Receiver, Sender, grpc::GrpcBi}, }; +use prost_types::{Timestamp as ProtoTimestamp, }; use async_trait::async_trait; use chrono::{TimeZone, Utc}; use kameo::{ @@ -261,12 +264,14 @@ async fn dispatch_conn_message( actor.ask(HandleGrantDelete { grant_id }).await, )) } - UserAgentRequestPayload::ClientConnectionResponse(resp) => { + UserAgentRequestPayload::SdkClientConnectionResponse(resp) => { let pubkey_bytes: [u8; 32] = match resp.pubkey.try_into() { Ok(bytes) => bytes, Err(_) => { let _ = bi - .send(Err(Status::invalid_argument("Invalid Ed25519 public key length"))) + .send(Err(Status::invalid_argument( + "Invalid Ed25519 public key length", + ))) .await; return Err(()); } @@ -289,13 +294,18 @@ async fn dispatch_conn_message( .await { warn!(?err, "Failed to process client connection response"); - let _ = bi.send(Err(Status::internal("Failed to process response"))).await; + let _ = bi + .send(Err(Status::internal("Failed to process response"))) + .await; return Err(()); } return Ok(()); } - UserAgentRequestPayload::AuthChallengeRequest(..) | UserAgentRequestPayload::AuthChallengeSolution(..) => { + UserAgentRequestPayload::SdkClientRevoke(_sdk_client_revoke_request) => todo!(), + UserAgentRequestPayload::SdkClientList(_) => todo!(), + UserAgentRequestPayload::AuthChallengeRequest(..) + | UserAgentRequestPayload::AuthChallengeSolution(..) => { warn!(?payload, "Unsupported post-auth user agent request"); let _ = bi .send(Err(Status::invalid_argument( @@ -304,7 +314,7 @@ async fn dispatch_conn_message( .await; return Err(()); } - + }; bi.send(Ok(UserAgentResponse { @@ -321,7 +331,7 @@ async fn send_out_of_band( ) -> Result<(), ()> { let payload = match oob { OutOfBand::ClientConnectionRequest { profile } => { - UserAgentResponsePayload::ClientConnectionRequest(ClientConnectionRequest { + UserAgentResponsePayload::SdkClientConnectionRequest(ProtoSdkClientConnectionRequest { pubkey: profile.pubkey.to_bytes().to_vec(), info: Some(ProtoClientMetadata { name: profile.metadata.name, @@ -331,7 +341,7 @@ async fn send_out_of_band( }) } OutOfBand::ClientConnectionCancel { pubkey } => { - UserAgentResponsePayload::ClientConnectionCancel(ClientConnectionCancel { + UserAgentResponsePayload::SdkClientConnectionCancel(ProtoSdkClientConnectionCancel { pubkey: pubkey.to_bytes().to_vec(), }) } @@ -435,9 +445,7 @@ fn u256_from_proto_bytes(bytes: &[u8]) -> Result { Ok(U256::from_be_slice(bytes)) } -fn proto_timestamp_to_utc( - timestamp: prost_types::Timestamp, -) -> Result, Status> { +fn proto_timestamp_to_utc(timestamp: ProtoTimestamp) -> Result, Status> { Utc.timestamp_opt(timestamp.seconds, timestamp.nanos as u32) .single() .ok_or_else(|| Status::invalid_argument("Invalid timestamp")) @@ -447,11 +455,11 @@ fn shared_settings_to_proto(shared: SharedGrantSettings) -> ProtoSharedSettings ProtoSharedSettings { wallet_access_id: shared.wallet_access_id, chain_id: shared.chain, - valid_from: shared.valid_from.map(|time| prost_types::Timestamp { + valid_from: shared.valid_from.map(|time| ProtoTimestamp { seconds: time.timestamp(), nanos: time.timestamp_subsec_nanos() as i32, }), - valid_until: shared.valid_until.map(|time| prost_types::Timestamp { + valid_until: shared.valid_until.map(|time| ProtoTimestamp { seconds: time.timestamp(), nanos: time.timestamp_subsec_nanos() as i32, }), diff --git a/server/crates/arbiter-server/src/lib.rs b/server/crates/arbiter-server/src/lib.rs index 13941e4..9b2695e 100644 --- a/server/crates/arbiter-server/src/lib.rs +++ b/server/crates/arbiter-server/src/lib.rs @@ -1,6 +1,4 @@ #![forbid(unsafe_code)] -#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)] - use crate::context::ServerContext; pub mod actors; diff --git a/server/crates/arbiter-server/tests/client/auth.rs b/server/crates/arbiter-server/tests/client/auth.rs index 1839e7c..4d8bebd 100644 --- a/server/crates/arbiter-server/tests/client/auth.rs +++ b/server/crates/arbiter-server/tests/client/auth.rs @@ -1,3 +1,4 @@ +use arbiter_proto::ClientMetadata; use arbiter_proto::transport::{Receiver, Sender}; use arbiter_server::actors::GlobalActors; use arbiter_server::{ @@ -10,8 +11,8 @@ use ed25519_dalek::Signer as _; use super::common::ChannelTransport; -fn metadata(name: &str, description: Option<&str>, version: Option<&str>) -> auth::ClientMetadata { - auth::ClientMetadata { +fn metadata(name: &str, description: Option<&str>, version: Option<&str>) -> ClientMetadata { + ClientMetadata { name: name.to_owned(), description: description.map(str::to_owned), version: version.map(str::to_owned), @@ -21,7 +22,7 @@ fn metadata(name: &str, description: Option<&str>, version: Option<&str>) -> aut async fn insert_registered_client( db: &db::DatabasePool, pubkey: Vec, - metadata: &auth::ClientMetadata, + metadata: &ClientMetadata, ) { use arbiter_server::db::schema::{client_metadata, program_client};