Parcourir la source

Implement exchange protocol that is used by key-exchange-server

	* Split codebase to different source files
	* Modified integration test
develop
Silvestr Predko il y a 2 ans
Parent
révision
6052aa41d5

+ 9
- 11
.github/workflows/ci.yml Voir le fichier

@@ -50,7 +50,7 @@ jobs:
50 50
         with:
51 51
           toolchain: stable
52 52
       - name: Run tests for [common-api]
53
-        run: cargo test -p common-api --tests --all-features 
53
+        run: cargo test -p common-api --tests --all-features
54 54
   tests-near:
55 55
     name: tests-near
56 56
     needs: [clippy, fmt]
@@ -62,17 +62,15 @@ jobs:
62 62
         with:
63 63
           toolchain: stable
64 64
           target: wasm32-unknown-unknown
65
-      - name: Set up ssh access
66
-        env:
67
-          SSH_AUTH_SOCK: /tmp/ssh_agent.sock
68
-        run: |
69
-          mkdir -p -m 0700 ~/.ssh
70
-          ssh-keyscan github.com >> ~/.ssh/known_hosts
71
-          ssh-agent -a $SSH_AUTH_SOCK > /dev/null
72
-          ssh-add - <<< "${{ secrets.SMARTCONTRACTS_SSH_PRIVATE_KEY }}"
65
+      - name: Setup ssh access for a private repositories
66
+        uses: webfactory/ssh-agent@v0.7.0
67
+        with:
68
+          ssh-private-key: |
69
+            ${{ secrets.CLIENT_SSH_PRIVATE_KEY }}
70
+            ${{ secrets.SMARTCONTRACTS_SSH_PRIVATE_KEY }}
73 71
       - name: Run tests for [near-rpc]
74 72
         env:
75
-          SSH_AUTH_SOCK: /tmp/ssh_agent.sock
73
+          CARGO_NET_GIT_FETCH_WITH_CLI: true
76 74
         run: cargo test -p near-rpc --tests
77 75
   tests-web:
78 76
     name: tests-web
@@ -89,7 +87,7 @@ jobs:
89 87
           username: ${{ github.actor }}
90 88
           password: ${{ secrets.GITHUB_TOKEN }}
91 89
       - name: Setup ssh access for a private repositories
92
-        uses: webfactory/ssh-agent@v0.5.4
90
+        uses: webfactory/ssh-agent@v0.7.0
93 91
         with:
94 92
           ssh-private-key: |
95 93
             ${{ secrets.CLIENT_SSH_PRIVATE_KEY }}

+ 1
- 1
Dockerfile Voir le fichier

@@ -20,4 +20,4 @@ RUN apt-get install -y chromium-driver
20 20
 ENV WASM_BINDGEN_TEST_TIMEOUT=120
21 21
 
22 22
 # Run tests
23
-CMD wasm-pack test --headless --chrome --tests
23
+CMD wasm-pack test --headless --chrome

+ 1
- 2
near-rpc/Cargo.toml Voir le fichier

@@ -8,7 +8,7 @@ The basic implementation of NEAR RPC.
8 8
 """
9 9
 
10 10
 [dependencies]
11
-base64 = { version = "0.13" }
11
+base64 = { version = "0.20" }
12 12
 bs58 = "0.4"
13 13
 borsh = "0.9"
14 14
 common-api = { path = "../common-api" }
@@ -23,7 +23,6 @@ thiserror = "1"
23 23
 url = "2"
24 24
 
25 25
 [dev-dependencies]
26
-git2 = "0.15.0"
27 26
 reqwest = { version = "0.11", features = ["json"] }
28 27
 rand = "0.8.5"
29 28
 rand_chacha = "0.3"

+ 23
- 24
near-rpc/tests/rpc.rs Voir le fichier

@@ -1,15 +1,19 @@
1
-use common_api::crypto::prelude::*;
2
-use git2::{Cred, RemoteCallbacks};
3
-use near_primitives_light::{types::Finality, views::AccessKeyView};
4 1
 use near_rpc::{
5 2
     client::{NearClient, Signer},
6 3
     utils::{ViewAccessKey, ViewAccessKeyResult},
7 4
 };
5
+
6
+use std::{
7
+    fs::{create_dir_all, write},
8
+    str::FromStr,
9
+};
10
+
11
+use common_api::crypto::prelude::*;
12
+use near_primitives_light::{types::Finality, views::AccessKeyView};
8 13
 use rand::{RngCore, SeedableRng};
9 14
 use rand_chacha::ChaChaRng;
10 15
 use reqwest::Url;
11 16
 use serde_json::json;
12
-use std::{fs::write, path::Path, str::FromStr};
13 17
 use workspaces::{network::Sandbox, types::SecretKey, AccountId, Worker};
14 18
 
15 19
 // auxiliary structs and methods
@@ -60,30 +64,25 @@ async fn clone_and_compile_wasm() -> Vec<u8> {
60 64
     let tmp_path = temp_dir().into_path();
61 65
     let repo_path = format!("{}/near-smartcontracts", tmp_path.to_string_lossy());
62 66
     let target_path = format!("{repo_path}/test-contract");
63
-    let repo_url = "git@github.com:Relayz-io/near-smartcontracts.git";
64
-
65
-    // Prepare callbacks.
66
-    let mut callbacks = RemoteCallbacks::new();
67
-    callbacks.credentials(|_, username_from_url, _| {
68
-        let username = username_from_url
69
-            .ok_or_else(|| git2::Error::from_str("Parsing the given URL is failed"))
70
-            .unwrap();
71
-        Cred::ssh_key_from_agent(username)
72
-    });
73 67
 
74
-    // Prepare fetch options.
75
-    let mut fo = git2::FetchOptions::new();
76
-    fo.remote_callbacks(callbacks);
68
+    create_dir_all(repo_path.clone()).unwrap();
77 69
 
78
-    // Prepare builder.
79
-    let mut builder = git2::build::RepoBuilder::new();
80
-    builder.fetch_options(fo);
81
-    builder.branch("main");
70
+    let status = std::process::Command::new("git")
71
+        .arg("clone")
72
+        .arg("git@github.com:Relayz-io/near-smartcontracts.git")
73
+        .arg(&repo_path)
74
+        .spawn()
75
+        .unwrap()
76
+        .wait()
77
+        .unwrap();
82 78
 
83
-    // Clone the project.
84
-    builder.clone(repo_url, Path::new(&repo_path)).unwrap();
79
+    if !status.success() {
80
+        panic!("Failed to clone a near-smartcontracts");
81
+    }
85 82
 
86
-    workspaces::compile_project(&target_path).await.unwrap()
83
+    workspaces::compile_project(target_path.as_str())
84
+        .await
85
+        .unwrap()
87 86
 }
88 87
 
89 88
 fn random_bits() -> [u8; ED25519_SECRET_KEY_LENGTH] {

+ 11
- 12
web-client/Cargo.toml Voir le fichier

@@ -11,32 +11,31 @@ crate-type = ["cdylib", "rlib"]
11 11
 
12 12
 [dependencies]
13 13
 aes-gcm = "0.10"
14
-blake2 = "0.10"
15
-base64 = "0.13"
16
-console_error_panic_hook = "0.1.7"
14
+blake3 = "1.3"
15
+base64 = "0.20"
16
+console_error_panic_hook = "0.1"
17 17
 common-api = { path = "../common-api" }
18
+console_log = { version = "0.2", features = ["color"] }
18 19
 futures = "0.3"
19
-gloo-timers = { version = "0.2.4", features = ["futures-core", "futures"] }
20
+gloo-timers = { version = "0.2", features = ["futures-core", "futures"] }
20 21
 itertools = "0.10"
21 22
 js-sys = "0.3"
23
+log = "0.4"
22 24
 near-primitives-light = { path = "../near-primitives-light" }
23 25
 near-primitives-core = "0.15"
24 26
 near-units = "0.2"
25 27
 near-rpc = { path = "../near-rpc" }
26
-rand = { version = "0.8.5" }
27
-reqwest = { version = "0.11", features = ["json"] }
28
+rand = { version = "0.8" }
28 29
 rand_chacha = "0.3"
29
-serde-wasm-bindgen = "0.4.3"
30
+reqwest = { version = "0.11", features = ["json"] }
31
+serde-wasm-bindgen = "0.4"
30 32
 serde = { version = "1", default-features = false, features = ["derive"] }
31 33
 serde_json = { version = "1", default-features = false }
32 34
 uuid = { version = "1.1.2", features = ["v4", "serde", "js"] }
33 35
 url = "2.2"
34 36
 wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
35
-wasm-bindgen-test = "0.3"
36
-wasm-bindgen-futures = "0.4.32"
37
-web-sys = { version = "0.3", features = ["console", "WebSocket", "WebSocketDict", "WebSocketElement", "Window"] }
38
-
37
+wasm-bindgen-futures = "0.4"
38
+web-sys = { version = "0.3", features = ["console"] }
39 39
 
40 40
 [dev-dependencies]
41
-anyhow = "1"
42 41
 wasm-bindgen-test = "0.3"

+ 152
- 0
web-client/src/contract.rs Voir le fichier

@@ -0,0 +1,152 @@
1
+use near_rpc::{
2
+    client::{NearClient, Signer},
3
+    Finality,
4
+};
5
+
6
+use crate::{error::ApiError, Handler};
7
+use common_api::crypto::prelude::*;
8
+use near_primitives_core::{hash::CryptoHash, types::AccountId};
9
+use near_units::parse_gas;
10
+use std::collections::HashSet;
11
+use uuid::Uuid;
12
+use wasm_bindgen::prelude::*;
13
+
14
+pub async fn init_meeting(
15
+    Handler {
16
+        contract_id,
17
+        rpc_url,
18
+        ..
19
+    }: &Handler,
20
+    signer: &Signer,
21
+    participants: HashSet<AccountId>,
22
+) -> Result<(Uuid, CryptoHash), JsValue> {
23
+    let client = NearClient::new(rpc_url.clone()).map_err(ApiError::from)?;
24
+    let meet_id = Uuid::new_v4();
25
+
26
+    let transaction_id = client
27
+        .function_call(signer, contract_id, "init_meeting")
28
+        .args(serde_json::json!({
29
+            "meet_id": meet_id,
30
+            "participants": participants
31
+        }))
32
+        .gas(parse_gas!("300 T") as u64)
33
+        .commit(Finality::None)
34
+        .await
35
+        .map_err(ApiError::from)?
36
+        .id();
37
+
38
+    Ok((meet_id, transaction_id))
39
+}
40
+
41
+pub async fn add_participant(
42
+    Handler {
43
+        contract_id,
44
+        rpc_url,
45
+        ..
46
+    }: &Handler,
47
+    signer: &Signer,
48
+    meet_id: Uuid,
49
+    participant: AccountId,
50
+) -> Result<CryptoHash, ApiError> {
51
+    let client = NearClient::new(rpc_url.clone())?;
52
+
53
+    let transaction_id = client
54
+        .function_call(signer, contract_id, "add_participant")
55
+        .args(serde_json::json!({
56
+            "meet_id": meet_id,
57
+            "participant": participant
58
+        }))
59
+        .commit(Finality::None)
60
+        .await?
61
+        .id();
62
+
63
+    Ok(transaction_id)
64
+}
65
+
66
+pub async fn view_moderator_account(
67
+    Handler {
68
+        contract_id,
69
+        rpc_url,
70
+        ..
71
+    }: &Handler,
72
+    meet_id: Uuid,
73
+) -> Result<Option<AccountId>, ApiError> {
74
+    let client = NearClient::new(rpc_url.clone())?;
75
+
76
+    let moderator = client
77
+        .view::<Option<AccountId>>(
78
+            contract_id,
79
+            Finality::None,
80
+            "view_moderator_account",
81
+            Some(serde_json::json!({ "meet_id": meet_id })),
82
+        )
83
+        .await?
84
+        .data();
85
+
86
+    Ok(moderator)
87
+}
88
+
89
+pub async fn view_meet_participants(
90
+    Handler {
91
+        contract_id,
92
+        rpc_url,
93
+        ..
94
+    }: &Handler,
95
+    meet_id: Uuid,
96
+) -> Result<Option<HashSet<AccountId>>, ApiError> {
97
+    let client = NearClient::new(rpc_url.clone())?;
98
+
99
+    let participants = client
100
+        .view::<Option<HashSet<AccountId>>>(
101
+            contract_id,
102
+            Finality::None,
103
+            "view_meet_participants",
104
+            Some(serde_json::json!({ "meet_id": meet_id })),
105
+        )
106
+        .await?
107
+        .data();
108
+
109
+    Ok(participants)
110
+}
111
+
112
+pub async fn is_meet_participant(
113
+    Handler {
114
+        contract_id,
115
+        rpc_url,
116
+        ..
117
+    }: &Handler,
118
+    meet_id: Uuid,
119
+    participant: AccountId,
120
+) -> Result<bool, ApiError> {
121
+    let client = NearClient::new(rpc_url.clone())?;
122
+
123
+    let is_session_participant = *client
124
+        .view::<bool>(
125
+            contract_id,
126
+            Finality::None,
127
+            "is_meet_participant",
128
+            Some(serde_json::json!({
129
+                "meet_id": meet_id,
130
+                "participant": participant
131
+            })),
132
+        )
133
+        .await?;
134
+
135
+    Ok(is_session_participant)
136
+}
137
+
138
+pub async fn view_server_key(
139
+    Handler {
140
+        contract_id,
141
+        rpc_url,
142
+        ..
143
+    }: &Handler,
144
+) -> Result<Ed25519PublicKey, ApiError> {
145
+    let client = NearClient::new(rpc_url.clone())?;
146
+
147
+    let public_key = *client
148
+        .view::<Ed25519PublicKey>(contract_id, Finality::None, "view_server_key", None)
149
+        .await?;
150
+
151
+    Ok(public_key)
152
+}

+ 62
- 0
web-client/src/crypto.rs Voir le fichier

@@ -0,0 +1,62 @@
1
+use crate::error::ApiError;
2
+use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit, Nonce};
3
+use common_api::{crypto::prelude::*, crypto::Error};
4
+use rand::{RngCore, SeedableRng};
5
+use rand_chacha::ChaChaRng;
6
+use uuid::Uuid;
7
+
8
+fn dhx(
9
+    sk: &Ed25519SecretKey,
10
+    other_pk: Ed25519PublicKey,
11
+) -> Result<[u8; SECRET_KEY_LENGTH], Error> {
12
+    let secret_key = SecretKey::try_from(Ed25519SecretKey::try_from_bytes(sk.as_bytes())?)?;
13
+    let other_public_key = PublicKey::try_from(other_pk)?;
14
+
15
+    Ok(secret_key.exchange(&other_public_key))
16
+}
17
+
18
+fn kdf(input: [u8; SECRET_KEY_LENGTH]) -> [u8; blake3::OUT_LEN] {
19
+    // TODO: insert a commit hash to make it up to date
20
+    blake3::derive_key("sample", &input)
21
+}
22
+
23
+pub fn secret() -> [u8; 32] {
24
+    let mut chacha = ChaChaRng::from_entropy();
25
+    let mut bytes = [0_u8; 32];
26
+    chacha.fill_bytes(&mut bytes);
27
+    bytes
28
+}
29
+
30
+pub fn encrypt(
31
+    sk: &Ed25519SecretKey,
32
+    other_pk: Ed25519PublicKey,
33
+    _exchange_id: Uuid,
34
+    secret: &[u8],
35
+) -> Result<Vec<u8>, ApiError> {
36
+    let bytes = dhx(sk, other_pk).map_err(ApiError::from)?;
37
+    let secret_key = kdf(bytes);
38
+    let aes = Aes256Gcm::new_from_slice(&secret_key)
39
+        .map_err(|err| ApiError::AesKeyCreation(err.to_string()))?;
40
+    let msg = aes
41
+        .encrypt(Nonce::from_slice(b"unique nonce"), secret) // TODO: create unique nonce for each encrypt-decrypt pair
42
+        .map_err(|err| ApiError::MessageEncryption(err.to_string()))?;
43
+
44
+    Ok(msg)
45
+}
46
+
47
+pub fn decrypt(
48
+    sk: &Ed25519SecretKey,
49
+    other_pk: Ed25519PublicKey,
50
+    _exchange_id: Uuid,
51
+    msg: Vec<u8>,
52
+) -> Result<Vec<u8>, ApiError> {
53
+    let bytes = dhx(sk, other_pk).map_err(ApiError::from)?;
54
+    let secret_key = kdf(bytes);
55
+    let aes = Aes256Gcm::new_from_slice(&secret_key)
56
+        .map_err(|err| ApiError::AesKeyCreation(err.to_string()))?;
57
+    let secret = aes
58
+        .decrypt(Nonce::from_slice(b"unique nonce"), msg.as_slice())
59
+        .map_err(|err| ApiError::MessageEncryption(err.to_string()))?;
60
+
61
+    Ok(secret)
62
+}

+ 73
- 0
web-client/src/error.rs Voir le fichier

@@ -0,0 +1,73 @@
1
+use common_api::crypto::Error as CryptoErr;
2
+use near_primitives_core::account::id::ParseAccountError;
3
+use near_rpc::Error;
4
+use reqwest::Error as ExchangeError;
5
+use serde::{Deserialize, Serialize};
6
+use serde_json::Error as SerializationError;
7
+use wasm_bindgen::prelude::*;
8
+
9
+#[derive(Clone, Debug, Deserialize, Serialize)]
10
+#[serde(tag = "error", content = "body")]
11
+#[serde(rename_all = "snake_case")]
12
+pub enum ApiError {
13
+    KeyExchange(String),
14
+    DeserializeResponse(String),
15
+    SerializeData(String),
16
+    NearClient(String),
17
+    InvalidAccountId(String),
18
+    InvalidSessionUuid(String),
19
+    CryptoError(String),
20
+    CreateSignatureHeader,
21
+    VerifySignatureHeader,
22
+    NoSignatureHeader,
23
+    CallTimeout(String),
24
+    AesKeyCreation(String),
25
+    MessageEncryption(String),
26
+    MessageDecryption(String),
27
+    Other(String),
28
+}
29
+
30
+impl From<Error> for ApiError {
31
+    fn from(err: Error) -> Self {
32
+        Self::NearClient(err.to_string())
33
+    }
34
+}
35
+
36
+impl From<ParseAccountError> for ApiError {
37
+    fn from(err: ParseAccountError) -> Self {
38
+        Self::InvalidAccountId(err.to_string())
39
+    }
40
+}
41
+
42
+impl From<CryptoErr> for ApiError {
43
+    fn from(err: CryptoErr) -> Self {
44
+        Self::CryptoError(err.to_string())
45
+    }
46
+}
47
+
48
+impl From<ExchangeError> for ApiError {
49
+    fn from(err: ExchangeError) -> Self {
50
+        Self::KeyExchange(err.to_string())
51
+    }
52
+}
53
+
54
+impl From<SerializationError> for ApiError {
55
+    fn from(err: SerializationError) -> Self {
56
+        Self::DeserializeResponse(err.to_string())
57
+    }
58
+}
59
+
60
+impl From<uuid::Error> for ApiError {
61
+    fn from(err: uuid::Error) -> Self {
62
+        Self::InvalidSessionUuid(err.to_string())
63
+    }
64
+}
65
+
66
+impl From<ApiError> for JsValue {
67
+    fn from(api_err: ApiError) -> Self {
68
+        match serde_wasm_bindgen::to_value(&api_err) {
69
+            Ok(value) => value,
70
+            Err(err) => err.into(),
71
+        }
72
+    }
73
+}

+ 0
- 61
web-client/src/errors.rs Voir le fichier

@@ -1,61 +0,0 @@
1
-use near_primitives_core::account::id::ParseAccountError;
2
-use near_rpc::Error;
3
-use wasm_bindgen::convert::{FromWasmAbi, IntoWasmAbi};
4
-use wasm_bindgen::prelude::*;
5
-
6
-#[wasm_bindgen]
7
-#[derive(Clone, Debug)]
8
-pub struct ApiError {
9
-    r#type: ErrorType,
10
-    msg: String,
11
-}
12
-
13
-#[wasm_bindgen]
14
-impl ApiError {
15
-    pub fn err_msg(&self) -> String {
16
-        self.msg.clone()
17
-    }
18
-
19
-    pub fn err_type(&self) -> ErrorType {
20
-        self.r#type
21
-    }
22
-}
23
-
24
-impl ApiError {
25
-    pub fn new(r#type: ErrorType, msg: String) -> Self {
26
-        Self { r#type, msg }
27
-    }
28
-}
29
-
30
-impl From<JsValue> for ApiError {
31
-    fn from(value: JsValue) -> Self {
32
-        // Should be used only for tests.
33
-        // No other purpose to use it in code shouldn't appear
34
-        unsafe { Self::from_abi(value.into_abi()) }
35
-    }
36
-}
37
-
38
-#[wasm_bindgen]
39
-#[derive(Clone, Copy, Debug)]
40
-pub enum ErrorType {
41
-    NearClient,
42
-    Other,
43
-}
44
-
45
-impl From<Error> for ApiError {
46
-    fn from(error: Error) -> Self {
47
-        ApiError {
48
-            r#type: ErrorType::NearClient,
49
-            msg: error.to_string(),
50
-        }
51
-    }
52
-}
53
-
54
-impl From<ParseAccountError> for ApiError {
55
-    fn from(err: ParseAccountError) -> Self {
56
-        Self::new(
57
-            ErrorType::NearClient,
58
-            format!("AccountId has wrong format. Cause: {err}"),
59
-        )
60
-    }
61
-}

+ 165
- 0
web-client/src/exchange_client.rs Voir le fichier

@@ -0,0 +1,165 @@
1
+use common_api::{
2
+    api::{Data, ExchangeMessage, ParticipantInfo, PublicKeys},
3
+    headers::{SignatureHeader, SIGNATURE_HEADER_NAME},
4
+};
5
+
6
+use reqwest::{
7
+    header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE},
8
+    Client, ClientBuilder, Response,
9
+};
10
+
11
+use crate::{contract::view_server_key, error::ApiError, Handler};
12
+use near_primitives_core::types::AccountId;
13
+use near_rpc::client::Signer;
14
+use std::{collections::HashSet, time::Duration};
15
+use uuid::Uuid;
16
+use wasm_bindgen::JsValue;
17
+
18
+type Result<T> = std::result::Result<T, JsValue>;
19
+
20
+fn client() -> Result<Client> {
21
+    let mut headers = HeaderMap::new();
22
+    headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
23
+
24
+    ClientBuilder::new()
25
+        .default_headers(headers)
26
+        .build()
27
+        .map_err(|err| ApiError::Other(err.to_string()).into())
28
+}
29
+
30
+pub async fn public_keys(
31
+    handler: &Handler,
32
+    signer: &Signer,
33
+    meet_id: Uuid,
34
+    participants: HashSet<AccountId>,
35
+) -> Result<Vec<ParticipantInfo>> {
36
+    let client = client()?;
37
+
38
+    let public_keys = PublicKeys {
39
+        timeout: Duration::from_secs(10),
40
+        participants,
41
+    };
42
+
43
+    let signature_header = signature_header(&public_keys, signer)?;
44
+
45
+    // TODO: Handle Server errors
46
+    let response = client
47
+        .post(handler.add_path("v1/public-keys"))
48
+        .query(&[("meet_id", meet_id)])
49
+        .header(
50
+            HeaderName::from_static(SIGNATURE_HEADER_NAME),
51
+            HeaderValue::from_str(&signature_header)
52
+                .map_err(|_| ApiError::CreateSignatureHeader)?,
53
+        )
54
+        .json(&public_keys)
55
+        .send()
56
+        .await
57
+        .and_then(Response::error_for_status)
58
+        .map_err(ApiError::from)?;
59
+
60
+    let infos =
61
+        serde_json::from_slice::<Vec<ParticipantInfo>>(&verify_response(handler, response).await?)
62
+            .map_err(ApiError::from)?;
63
+
64
+    Ok(infos)
65
+}
66
+
67
+pub async fn receive(handler: &Handler, signer: &Signer, meet_id: Uuid) -> Result<Data> {
68
+    let client = client()?;
69
+
70
+    let duration = Duration::from_secs(10);
71
+    let signature_header = signature_header(&duration, signer)?;
72
+
73
+    // TODO: Handle Server errors
74
+    let response = client
75
+        .post(handler.add_path("v1/receive"))
76
+        .query(&[("meet_id", meet_id)])
77
+        .header(
78
+            HeaderName::from_static(SIGNATURE_HEADER_NAME),
79
+            HeaderValue::from_str(&signature_header)
80
+                .map_err(|_| ApiError::CreateSignatureHeader)?,
81
+        )
82
+        .json(&duration)
83
+        .send()
84
+        .await
85
+        .and_then(Response::error_for_status)
86
+        .map_err(ApiError::from)?;
87
+
88
+    let data = serde_json::from_slice::<Data>(&verify_response(handler, response).await?)
89
+        .map_err(ApiError::from)?;
90
+
91
+    Ok(data)
92
+}
93
+
94
+pub async fn exchange(
95
+    handler: &Handler,
96
+    signer: &Signer,
97
+    meet_id: Uuid,
98
+    messages: Vec<ExchangeMessage>,
99
+) -> Result<()> {
100
+    let client = client()?;
101
+
102
+    let signature_header = signature_header(&messages, signer)?;
103
+
104
+    let response = client
105
+        .post(handler.add_path("v1/exchange"))
106
+        .query(&[("meet_id", meet_id)])
107
+        .header(
108
+            HeaderName::from_static(SIGNATURE_HEADER_NAME),
109
+            HeaderValue::from_str(&signature_header)
110
+                .map_err(|_| ApiError::CreateSignatureHeader)?,
111
+        )
112
+        .json(&messages)
113
+        .send()
114
+        .await
115
+        .and_then(Response::error_for_status)
116
+        .map_err(ApiError::from)?;
117
+
118
+    let _ = verify_response(handler, response).await?;
119
+
120
+    Ok(())
121
+}
122
+
123
+fn signature_header<T: serde::Serialize>(value: &T, signer: &Signer) -> Result<String> {
124
+    serde_json::to_vec(value)
125
+        .map_err(|_| ApiError::CreateSignatureHeader.into())
126
+        .and_then(|bytes| {
127
+            let header = SignatureHeader {
128
+                public_key: *signer.public_key(),
129
+                account_id: signer.account().to_owned(),
130
+                signature: signer.sign(&bytes),
131
+            };
132
+
133
+            serde_json::to_vec(&header).map_err(|_| ApiError::CreateSignatureHeader.into())
134
+        })
135
+        .map(base64::encode)
136
+}
137
+
138
+async fn verify_response(handler: &Handler, response: Response) -> Result<Vec<u8>> {
139
+    let Some(header) = response.headers().get(HeaderName::from_static(SIGNATURE_HEADER_NAME)) else {
140
+        return Err(ApiError::NoSignatureHeader.into())
141
+    };
142
+
143
+    let SignatureHeader { signature, .. } = header
144
+        .to_str()
145
+        .map_err(|_| ApiError::VerifySignatureHeader)
146
+        .and_then(|header_str| {
147
+            base64::decode(header_str).map_err(|_| ApiError::VerifySignatureHeader)
148
+        })
149
+        .and_then(|bytes| {
150
+            serde_json::from_slice::<SignatureHeader>(&bytes)
151
+                .map_err(|_| ApiError::VerifySignatureHeader)
152
+        })?;
153
+
154
+    let bytes = response
155
+        .bytes()
156
+        .await
157
+        .map_err(|_| ApiError::VerifySignatureHeader)?;
158
+
159
+    let server_public_key = view_server_key(handler).await?;
160
+    server_public_key
161
+        .verify(&bytes, &signature)
162
+        .map_err(|_| ApiError::VerifySignatureHeader)?;
163
+
164
+    Ok(bytes.to_vec())
165
+}

+ 142
- 370
web-client/src/lib.rs Voir le fichier

@@ -1,57 +1,73 @@
1
-use aes_gcm::{
2
-    aead::{Aead, KeyInit},
3
-    Aes256Gcm, Nonce as AesNonce,
1
+mod contract;
2
+mod crypto;
3
+pub mod error;
4
+mod exchange_client;
5
+
6
+pub use contract::{
7
+    add_participant, init_meeting, is_meet_participant, view_meet_participants,
8
+    view_moderator_account,
4 9
 };
5
-use futures::{lock::Mutex, select, FutureExt};
6
-use itertools::Itertools;
7
-use rand::{RngCore, SeedableRng};
8
-use serde_json::json;
9
-use wasm_bindgen::prelude::*;
10
-
11
-use near_primitives_core::{account::id::AccountId, types::Nonce};
12
-use near_primitives_light::types::Finality;
13
-use near_rpc::client::{NearClient, Signer};
14
-
15
-use js_sys::Promise;
16
-
17
-use std::{ops::DerefMut, str::FromStr, sync::Arc};
18 10
 
19
-use blake2::{
20
-    digest::{Update, VariableOutput},
21
-    Blake2bVar,
22
-};
23
-
24
-pub mod errors;
25
-use common_api::crypto::prelude::*;
26
-
27
-use errors::{ApiError, ErrorType};
11
+use common_api::api::{Data, ExchangeMessage};
12
+use crypto::{decrypt, encrypt, secret};
13
+use error::ApiError;
14
+use exchange_client::{exchange, public_keys, receive};
15
+use futures::{select, FutureExt};
28 16
 use gloo_timers::future::TimeoutFuture;
29
-
30
-#[allow(unused_macros)]
31
-macro_rules! console_log {
32
-    ($($t:tt)*) => (web_sys::console::log_1(&format!($($t)*).into()))
33
-}
17
+use itertools::Itertools;
18
+use js_sys::Promise;
19
+use log::{info, warn};
20
+use near_primitives_core::{account::id::AccountId, hash::CryptoHash, types::Nonce};
21
+use near_rpc::client::Signer;
22
+use serde::{Deserialize, Serialize};
23
+use std::{collections::HashSet, str::FromStr, sync::Arc};
24
+use url::Url;
25
+use uuid::Uuid;
26
+use wasm_bindgen::prelude::*;
34 27
 
35 28
 type Result<T> = std::result::Result<T, ApiError>;
36 29
 
37 30
 #[wasm_bindgen(start)]
38
-pub fn start() -> std::result::Result<(), JsValue> {
31
+pub fn start() -> Result<()> {
39 32
     console_error_panic_hook::set_once();
33
+    console_log::init().unwrap();
40 34
     Ok(())
41 35
 }
42 36
 
43
-struct Handler {
37
+fn to_value<T: Serialize>(value: &T) -> JsValue {
38
+    match serde_wasm_bindgen::to_value(value) {
39
+        Ok(value) => value,
40
+        Err(err) => err.into(),
41
+    }
42
+}
43
+
44
+pub struct Handler {
44 45
     contract_id: AccountId,
45
-    owner_sk: SecretKey,
46
-    url: url::Url,
46
+    secret: [u8; 32],
47
+    exchange_url: url::Url,
48
+    rpc_url: url::Url,
49
+}
50
+
51
+impl Handler {
52
+    pub fn add_path(&self, path: &str) -> url::Url {
53
+        let mut url = self.exchange_url.clone();
54
+        url.set_path(path);
55
+        url
56
+    }
57
+}
58
+
59
+#[derive(Clone, Debug, Serialize, Deserialize)]
60
+pub struct Meeting {
61
+    pub meet_id: Uuid,
62
+    pub transaction_id: CryptoHash,
47 63
 }
48 64
 
49 65
 #[wasm_bindgen]
50 66
 #[derive(Debug, Clone)]
51 67
 pub struct ProvisionerConfig {
52 68
     contract_id: AccountId,
53
-    rpc_url: url::Url,
54
-    key_exchange_url: url::Url,
69
+    rpc_url: Url,
70
+    exchange_url: Url,
55 71
 }
56 72
 
57 73
 #[wasm_bindgen]
@@ -62,25 +78,23 @@ impl ProvisionerConfig {
62 78
         rpc_url: &str,
63 79
         exchange_url: &str,
64 80
     ) -> Result<ProvisionerConfig> {
65
-        let rpc_url = url::Url::from_str(rpc_url)
66
-            .map_err(|err| ApiError::new(ErrorType::NearClient, err.to_string()))?;
67
-        let key_exchange_url = url::Url::from_str(exchange_url)
68
-            .map_err(|err| ApiError::new(ErrorType::NearClient, err.to_string()))?;
69
-        let contract_id = AccountId::from_str(&contract_id).map_err(Into::<ApiError>::into)?;
81
+        let rpc_url = Url::from_str(rpc_url)
82
+            .map_err(|err| ApiError::Other(format!("Bad rpc url, cause {err}")))?;
83
+        let exchange_url = Url::from_str(exchange_url)
84
+            .map_err(|err| ApiError::Other(format!("Bad exchange url, cause {err}")))?;
85
+        let contract_id = AccountId::from_str(&contract_id).map_err(ApiError::from)?;
70 86
 
71 87
         Ok(Self {
72 88
             contract_id,
73 89
             rpc_url,
74
-            key_exchange_url,
90
+            exchange_url,
75 91
         })
76 92
     }
77 93
 }
78 94
 
79 95
 #[wasm_bindgen]
80 96
 pub struct KeyProvisioner {
81
-    client: Arc<NearClient>,
82
-    participants: Arc<Mutex<Option<Vec<String>>>>,
83
-    signer: Arc<Mutex<Signer>>,
97
+    signer: Arc<Signer>,
84 98
     handler: Arc<Handler>,
85 99
 }
86 100
 
@@ -89,11 +103,7 @@ impl KeyProvisioner {
89 103
         Arc::clone(&self.handler)
90 104
     }
91 105
 
92
-    fn client(&self) -> Arc<NearClient> {
93
-        Arc::clone(&self.client)
94
-    }
95
-
96
-    fn signer(&self) -> Arc<Mutex<Signer>> {
106
+    fn signer(&self) -> Arc<Signer> {
97 107
         Arc::clone(&self.signer)
98 108
     }
99 109
 }
@@ -105,29 +115,25 @@ impl KeyProvisioner {
105 115
         keypair_str: String,
106 116
         nonce: Nonce,
107 117
         account_id: String,
108
-        config: ProvisionerConfig,
118
+        ProvisionerConfig {
119
+            contract_id,
120
+            rpc_url,
121
+            exchange_url,
122
+        }: ProvisionerConfig,
109 123
     ) -> Result<KeyProvisioner> {
110
-        let client = Arc::new(NearClient::new(config.rpc_url).map_err(ApiError::from)?);
111
-        let signer = Arc::new(Mutex::new(
112
-            Signer::from_secret_str(
113
-                &keypair_str,
114
-                AccountId::from_str(&account_id).map_err(Into::<ApiError>::into)?,
115
-                nonce,
116
-            )
117
-            .map_err(ApiError::from)?,
118
-        ));
124
+        let signer = Arc::new(Signer::from_secret_str(
125
+            &keypair_str,
126
+            AccountId::from_str(&account_id)?,
127
+            nonce,
128
+        )?);
119 129
         let handler = Arc::new(Handler {
120
-            contract_id: config.contract_id,
121
-            owner_sk: new_secret()?,
122
-            url: config.key_exchange_url,
130
+            contract_id,
131
+            secret: secret(),
132
+            exchange_url,
133
+            rpc_url,
123 134
         });
124 135
 
125
-        Ok(Self {
126
-            client,
127
-            signer,
128
-            handler,
129
-            participants: Arc::new(Mutex::new(None)),
130
-        })
136
+        Ok(Self { signer, handler })
131 137
     }
132 138
 
133 139
     /// Initializes meeting by calling the contract and providing there a set of participants' keys
@@ -136,66 +142,37 @@ impl KeyProvisioner {
136 142
     ///
137 143
     /// - participants_set - The [`js_sys::Set`] represents hash set of participants' keys
138 144
     /// - timeout_ms - The [`u32`] that represents milliseconds that were given not to be exceeded
139
-    pub fn init(&self, participants_set: js_sys::Set, timeout_ms: u32) -> Promise {
145
+    pub fn init_meeting(&self, participants_set: js_sys::Set, timeout_ms: u32) -> Promise {
140 146
         let handler = self.handler();
141
-        let client = self.client();
142 147
         let signer = self.signer();
143
-        let participants_lock = self.participants.clone();
144 148
 
145
-        let init = async move {
146
-            let exchange_uuid = uuid::Uuid::new_v4();
147
-            let (contract_id, owner_pk) = (
148
-                handler.contract_id.clone(),
149
-                PublicKey::from(&handler.owner_sk),
150
-            );
151
-
152
-            let res: Vec<Result<String>> = participants_set
153
-                .keys()
149
+        let init_meeting = async move {
150
+            let participants: HashSet<AccountId> = participants_set
151
+                .values()
154 152
                 .into_iter()
155
-                .map(|item| {
156
-                    item.map(|it| {
157
-                        it.as_string()
158
-                            .ok_or_else(|| {
159
-                                ApiError::new(
160
-                                    ErrorType::Other,
161
-                                    "participants_set is empty".to_owned(),
162
-                                )
163
-                            })
164
-                            .map_err(ApiError::from)
165
-                    })
153
+                .map_ok(|it| {
154
+                    let Some(acc_id) = it.as_string() else {
155
+                        return Err(ApiError::Other("Set item type isn't a string".to_owned()));
156
+                    };
157
+
158
+                    AccountId::from_str(&acc_id).map_err(ApiError::from)
166 159
                 })
160
+                .flatten_ok()
167 161
                 .try_collect()?;
168 162
 
169
-            let participants: Vec<String> = res.into_iter().try_collect()?;
170
-
171
-            client
172
-                .function_call(
173
-                    signer.lock().await.deref_mut(),
174
-                    &contract_id,
175
-                    "init_meeting",
176
-                )
177
-                .gas(near_units::parse_gas!("20 T") as u64)
178
-                .args(json!({
179
-                    "owner_public_key": base64::encode(owner_pk.to_bytes()),
180
-                    "id": exchange_uuid.to_string(),
181
-                    "participants": participants,
182
-                }))
183
-                .build()
184
-                .map_err(ApiError::from)?
185
-                .commit(Finality::Final)
186
-                .await
187
-                .map_err(ApiError::from)?;
188
-
189
-            *participants_lock.lock_owned().await = Some(participants);
190
-
191
-            Ok(JsValue::from_str(&exchange_uuid.to_string()))
163
+            let (meet_id, transaction_id) = init_meeting(&handler, &signer, participants).await?;
164
+
165
+            Ok(to_value(&Meeting {
166
+                meet_id,
167
+                transaction_id,
168
+            }))
192 169
         };
193 170
 
194 171
         wasm_bindgen_futures::future_to_promise(async move {
195 172
             select! {
196
-                uuid = init.fuse() => uuid,
173
+                meet = init_meeting.fuse() => meet,
197 174
                 _ = TimeoutFuture::new(timeout_ms).fuse() => {
198
-                    Err(JsValue::from(ApiError::new(ErrorType::Other, "The initialization has been timed out".to_string())))
175
+                    Err(ApiError::CallTimeout("The initialization has been timed out".to_owned()).into())
199 176
                 }
200 177
             }
201 178
         })
@@ -209,112 +186,67 @@ impl KeyProvisioner {
209 186
     /// - timeout_ms - The [`u32`] that represents milliseconds that were given not to be exceeded
210 187
     pub fn send_keys(&self, meeting_id: String, timeout_ms: u32) -> Promise {
211 188
         let handler = self.handler();
212
-        let client = self.client();
213
-        let participants_lock = self.participants.clone();
189
+        let signer = self.signer();
190
+
214 191
         let send_keys = async move {
215
-            let guard = participants_lock.lock().await;
216
-            let participants = guard
217
-                .as_ref()
218
-                .ok_or_else(|| ApiError::new(ErrorType::Other, "Empty participants".to_owned()))?;
219
-
220
-            let (contract_id, url) = (handler.contract_id.clone(), &handler.url);
221
-
222
-            let fetch_participants_keys = participants.iter().map(|participant| {
223
-                let client_cp = client.clone();
224
-                let contract_cp = contract_id.clone();
225
-                let meeting_id_cp = meeting_id.clone();
226
-                async move {
227
-                    loop {
228
-                        let resp = client_cp
229
-                            .view::<Option<String>>(
230
-                                &contract_cp,
231
-                                Finality::None,
232
-                                "public_key",
233
-                                Some(json!({
234
-                                    "id": meeting_id_cp,
235
-                                    "participant_id": participant.to_string()
236
-                                })),
237
-                            )
238
-                            .await
239
-                            .map_err(|err| ApiError::new(ErrorType::NearClient, err.to_string()))?
240
-                            .data();
241
-
242
-                        if let Some(key) = resp {
243
-                            return Ok::<(String, String), ApiError>((
244
-                                key,
245
-                                participant.to_string(),
246
-                            ));
192
+            let meet_id = Uuid::from_str(&meeting_id).map_err(ApiError::from)?;
193
+
194
+            let mut participants = view_meet_participants(&handler, meet_id)
195
+                .await?
196
+                .ok_or_else(|| {
197
+                    ApiError::InvalidSessionUuid(format!("Wrong Session ID: {meet_id}"))
198
+                })?;
199
+
200
+            info!("Get a meeting participants {participants:?}");
201
+
202
+            while !participants.is_empty() {
203
+                let infos =
204
+                    match public_keys(&handler, &signer, meet_id, participants.clone()).await {
205
+                        Ok(infos) => infos,
206
+                        Err(err) => {
207
+                            warn!("Failed to fetch a public keys, cause {err:?}");
208
+                            continue;
247 209
                         }
248
-                    }
249
-                }
250
-            });
210
+                    };
251 211
 
252
-            let participants_keys = futures::future::try_join_all(fetch_participants_keys).await?;
212
+                // remove infos that is already processed
213
+                for key in &infos {
214
+                    participants.remove(&key.account_id);
215
+                }
253 216
 
254
-            let keys: Vec<(PublicKey, String)> = participants_keys
255
-                .into_iter()
256
-                .map(|(pk, participant_id)| {
257
-                    base64::decode(pk)
258
-                        .map(|it| (it, participant_id))
259
-                        .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))
260
-                        .and_then(|(pk_bytes, participant_id)| {
261
-                            PublicKey::try_from_bytes(&pk_bytes)
262
-                                .map(|it| (it, participant_id))
263
-                                .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))
217
+                let messages = infos
218
+                    .into_iter()
219
+                    .map(|info| {
220
+                        let sk = signer.secret_key();
221
+                        let other_pk = info.public_key;
222
+                        let msg = encrypt(sk, other_pk, meet_id, &handler.secret)?;
223
+                        Ok::<ExchangeMessage, JsValue>(ExchangeMessage {
224
+                            account_id: info.account_id,
225
+                            message: Data {
226
+                                data: msg,
227
+                                moderator_pk: signer.public_key().to_owned(),
228
+                            },
264 229
                         })
265
-                })
266
-                .try_collect()?;
267
-
268
-            let aes_secret = base64::encode(new_secret()?.to_bytes());
230
+                    })
231
+                    .try_collect()?;
269 232
 
270
-            let encrypted_keys: Vec<(Vec<u8>, String)> = keys
271
-                .into_iter()
272
-                .map(|(pk, participant_id)| {
273
-                    let res_key = handler.owner_sk.exchange(&pk);
274
-                    encrypt(&res_key, aes_secret.as_bytes(), participant_id)
275
-                })
276
-                .try_collect()?;
233
+                exchange(&handler, &signer, meet_id, messages).await?;
234
+            }
277 235
 
278
-            let meeting_url = url
279
-                .join("meeting")
280
-                .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?;
281
-
282
-            let _: Vec<reqwest::Response> = futures::future::try_join_all(
283
-                encrypted_keys.into_iter().map(|(bytes, participant_id)| {
284
-                    let client = reqwest::ClientBuilder::new().build().unwrap();
285
-                    client
286
-                        .post(meeting_url.clone())
287
-                        .query(&[
288
-                            ("id", serde_json::to_value(meeting_id.clone()).unwrap()),
289
-                            ("participant", serde_json::to_value(participant_id).unwrap()),
290
-                        ])
291
-                        .json(&serde_json::Value::String(base64::encode(bytes)))
292
-                        .send()
293
-                }),
294
-            )
295
-            .await
296
-            .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?
297
-            .into_iter()
298
-            .map(|it| {
299
-                it.error_for_status()
300
-                    .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))
301
-            })
302
-            .try_collect()?;
303
-
304
-            Ok(JsValue::from_str(&aes_secret))
236
+            Ok(JsValue::from_str(&base64::encode(handler.secret)))
305 237
         };
306 238
 
307 239
         wasm_bindgen_futures::future_to_promise(async move {
308 240
             select! {
309 241
                 aes_secret = send_keys.fuse() => aes_secret,
310 242
                 _ = TimeoutFuture::new(timeout_ms).fuse() => {
311
-                    Err(JsValue::from(ApiError::new(ErrorType::Other, "The send keys operation has been timed out".to_string())))
243
+                    Err(ApiError::CallTimeout("The send keys operation has been timed out".to_owned()).into())
312 244
                 }
313 245
             }
314 246
         })
315 247
     }
316 248
 
317
-    /// Get participant's key from a blockchain
249
+    /// Get participant's key from a server
318 250
     ///
319 251
     /// Arguments
320 252
     ///
@@ -322,182 +254,22 @@ impl KeyProvisioner {
322 254
     /// - timeout_ms - The [`u32`] that represents milliseconds that were given not to be exceeded
323 255
     pub fn get_key(&self, meeting_id: String, timeout_ms: u32) -> Promise {
324 256
         let handler = self.handler();
325
-        let client = self.client();
326 257
         let signer = self.signer();
327 258
 
328 259
         let get_key = async move {
329
-            let (contract_id, sk, url) =
330
-                (handler.contract_id.clone(), &handler.owner_sk, &handler.url);
331
-
332
-            let msg = get_message(url, signer, &meeting_id).await?;
333
-            loop {
334
-                let owner_pub_key = client
335
-                    .view::<Option<String>>(
336
-                        &contract_id,
337
-                        Finality::None,
338
-                        "owner_key",
339
-                        Some(serde_json::json!({
340
-                            "id": meeting_id.to_string()
341
-                        })),
342
-                    )
343
-                    .await
344
-                    .map_err(|err| ApiError::new(ErrorType::NearClient, err.to_string()))?
345
-                    .data();
346
-
347
-                if let Some(owner_pk) = owner_pub_key {
348
-                    let bytes = base64::decode(owner_pk)
349
-                        .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?;
350
-                    let owner_pk = PublicKey::try_from_bytes(&bytes)
351
-                        .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?;
352
-                    let key = sk.exchange(&owner_pk);
353
-
354
-                    let msg_bytes = base64::decode(msg)
355
-                        .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?;
356
-                    let decrypted_msg = decrypt(&key, &msg_bytes)?;
357
-
358
-                    return Ok(JsValue::from_str(
359
-                        std::str::from_utf8(&decrypted_msg)
360
-                            .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?,
361
-                    ));
362
-                }
363
-            }
260
+            let meet_id = Uuid::from_str(&meeting_id).map_err(ApiError::from)?;
261
+            let data = receive(&handler, &signer, meet_id).await?;
262
+            let secret = decrypt(signer.secret_key(), data.moderator_pk, meet_id, data.data)?;
263
+            Ok(JsValue::from_str(&base64::encode(secret)))
364 264
         };
365 265
 
366 266
         wasm_bindgen_futures::future_to_promise(async move {
367 267
             select! {
368 268
                 key = get_key.fuse() => key,
369 269
                 _ = TimeoutFuture::new(timeout_ms).fuse() => {
370
-                    Err(JsValue::from(ApiError::new(ErrorType::Other, "The getting key operation has been timed out".to_string())))
371
-                }
372
-            }
373
-        })
374
-    }
375
-
376
-    /// Pushes end-user's key to the blockchain
377
-    ///
378
-    /// Arguments
379
-    ///
380
-    /// - meeting_id - The [`String`] that indicates ID of the meeting room
381
-    /// - timeout_ms - The [`u32`] that represents milliseconds that were given not to be exceeded
382
-    pub fn push_to_near(&self, meeting_id: String, timeout_ms: u32) -> Promise {
383
-        let handler = self.handler();
384
-        let client = self.client();
385
-        let signer = self.signer();
386
-
387
-        let push_key = async move {
388
-            let (contract_id, pk) = (
389
-                handler.contract_id.clone(),
390
-                PublicKey::from(&handler.owner_sk),
391
-            );
392
-            client
393
-                .function_call(signer.lock().await.deref_mut(), &contract_id, "set_key")
394
-                .gas(near_units::parse_gas!("300 T") as u64)
395
-                .args(json!({
396
-                    "id": meeting_id.to_string(),
397
-                    "pk": base64::encode(pk.to_bytes()),
398
-                }))
399
-                .build()
400
-                .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?
401
-                .commit(Finality::None)
402
-                .await
403
-                .map_err(|err| ApiError::new(ErrorType::NearClient, err.to_string()))?;
404
-
405
-            Ok(JsValue::undefined())
406
-        };
407
-
408
-        wasm_bindgen_futures::future_to_promise(async move {
409
-            select! {
410
-                exchange_uuid = push_key.fuse() => exchange_uuid,
411
-                _ = TimeoutFuture::new(timeout_ms).fuse() => {
412
-                    Err(JsValue::from(ApiError::new(ErrorType::Other, "The initialization has been timed out".to_string())))
270
+                    Err(ApiError::CallTimeout("The getting key operation has been timed out".to_owned()).into())
413 271
                 }
414 272
             }
415 273
         })
416 274
     }
417 275
 }
418
-
419
-/// Retrieves encrypted message from the server. That message should be dectypted to grant an access to the meeting.
420
-///
421
-/// Arguments
422
-///
423
-/// - url - The ['reqwest::Url'] that represents a handler's url
424
-/// - signer - The [`Arc<Mutex<Signer>>`] is a signer that is used to sign transactions
425
-/// - meeting_id - The [`&str`] represents ID of the meeting
426
-async fn get_message(
427
-    url: &reqwest::Url,
428
-    signer: Arc<Mutex<Signer>>,
429
-    meeting_id: &str,
430
-) -> Result<String> {
431
-    let signer = signer.lock().await;
432
-    let http_client = reqwest::ClientBuilder::new()
433
-        .build()
434
-        .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?;
435
-    let meeting_url = url
436
-        .join("meeting")
437
-        .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?;
438
-    let meeting_id_json = serde_json::to_value(meeting_id)
439
-        .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?;
440
-    let signer_json = serde_json::to_value(signer.account().clone())
441
-        .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?;
442
-    loop {
443
-        let fetch_msg = http_client
444
-            .get(meeting_url.clone())
445
-            .query(&[("id", &meeting_id_json), ("participant", &signer_json)])
446
-            .send()
447
-            .await
448
-            .and_then(|it| it.error_for_status())
449
-            .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))?
450
-            .text()
451
-            .await
452
-            .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()));
453
-
454
-        if fetch_msg.is_ok() {
455
-            return fetch_msg;
456
-        }
457
-    }
458
-}
459
-
460
-fn new_secret() -> Result<SecretKey> {
461
-    use rand_chacha::ChaChaRng;
462
-    let mut chacha = ChaChaRng::from_entropy();
463
-    let mut bytes = [0_u8; 32];
464
-    chacha.fill_bytes(&mut bytes);
465
-    SecretKey::try_from_bytes(&bytes)
466
-        .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))
467
-}
468
-
469
-fn encrypt(
470
-    exchange_key: &[u8],
471
-    secret: &[u8],
472
-    participant_id: String,
473
-) -> Result<(Vec<u8>, String)> {
474
-    aes(exchange_key)
475
-        .and_then(|it: Aes256Gcm| {
476
-            it.encrypt(AesNonce::from_slice(b"unique nonce"), secret)
477
-                .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))
478
-        })
479
-        .map(|encrypted_msg| (encrypted_msg, participant_id))
480
-}
481
-
482
-fn decrypt(exchange_key: &[u8], secret: &[u8]) -> Result<Vec<u8>> {
483
-    aes(exchange_key).and_then(|it: Aes256Gcm| {
484
-        it.decrypt(AesNonce::from_slice(b"unique nonce"), secret)
485
-            .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))
486
-    })
487
-}
488
-
489
-fn aes(exchange_key: &[u8]) -> Result<Aes256Gcm> {
490
-    Blake2bVar::new(32)
491
-        .map(|mut kdf| {
492
-            kdf.update(exchange_key);
493
-            kdf.finalize_boxed()
494
-        })
495
-        .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))
496
-        .and_then(|it| {
497
-            Aes256Gcm::new_from_slice(&it)
498
-                .map_err(|err| ApiError::new(ErrorType::Other, err.to_string()))
499
-        })
500
-}
501
-
502
-#[cfg(test)]
503
-mod tests {}

+ 50
- 72
web-client/tests/integration.rs Voir le fichier

@@ -8,11 +8,16 @@ use url::Url;
8 8
 use wasm_bindgen::JsValue;
9 9
 use wasm_bindgen_futures::JsFuture;
10 10
 use wasm_bindgen_test::*;
11
-
12
-use web_client::{KeyProvisioner, ProvisionerConfig};
11
+use web_client::{KeyProvisioner, Meeting, ProvisionerConfig};
13 12
 
14 13
 wasm_bindgen_test_configure!(run_in_browser);
15 14
 
15
+const ALICE_BYTES: [u8; 32] = [1; 32];
16
+const BOB_BYTES: [u8; 32] = [2; 32];
17
+const KARL_BYTES: [u8; 32] = [3; 32];
18
+const MIKE_BYTES: [u8; 32] = [4; 32];
19
+const CONTRACT_BYTES: [u8; 32] = [5; 32];
20
+
16 21
 #[derive(Serialize, Deserialize)]
17 22
 struct ValidatorKey {
18 23
     account_id: AccountId,
@@ -20,12 +25,6 @@ struct ValidatorKey {
20 25
     secret_key: String,
21 26
 }
22 27
 
23
-const ALICE_BYTES: [u8; 32] = [1; 32];
24
-const BOB_BYTES: [u8; 32] = [2; 32];
25
-const KARL_BYTES: [u8; 32] = [3; 32];
26
-const MIKE_BYTES: [u8; 32] = [4; 32];
27
-const CONTRACT_BYTES: [u8; 32] = [5; 32];
28
-
29 28
 async fn create_account(
30 29
     client: &NearClient,
31 30
     validator: &Signer,
@@ -50,7 +49,6 @@ async fn create_account(
50 49
             .await
51 50
             .unwrap(),
52 51
     )
53
-    .map_err(|err| anyhow::anyhow!(err))
54 52
     .unwrap();
55 53
 
56 54
     Signer::from_secret(sk, id, nonce)
@@ -80,19 +78,16 @@ async fn drop_created_account(
80 78
     }
81 79
 }
82 80
 
83
-async fn validator_key() -> anyhow::Result<ValidatorKey> {
84
-    Ok(
85
-        reqwest::get("http://sandbox-artifact:3032/validator_key.json")
86
-            .await?
87
-            .json::<ValidatorKey>()
88
-            .await?,
89
-    )
81
+async fn validator_key() -> ValidatorKey {
82
+    reqwest::get("http://sandbox-artifact:3032/validator_key.json")
83
+        .await
84
+        .unwrap()
85
+        .json::<ValidatorKey>()
86
+        .await
87
+        .unwrap()
90 88
 }
91 89
 
92
-async fn validator_account(
93
-    client: &NearClient,
94
-    validator_key: ValidatorKey,
95
-) -> anyhow::Result<Signer> {
90
+async fn validator_account(client: &NearClient, validator_key: ValidatorKey) -> Signer {
96 91
     let key_nonce = Result::from(
97 92
         client
98 93
             .view_access_key(
@@ -100,32 +95,27 @@ async fn validator_account(
100 95
                 &validator_key.public_key,
101 96
                 Finality::Final,
102 97
             )
103
-            .await?,
98
+            .await
99
+            .unwrap(),
104 100
     )
105
-    .map_err(|err| anyhow::anyhow!(err))?;
101
+    .unwrap();
106 102
 
107
-    let signer = Signer::from_secret_str(
103
+    Signer::from_secret_str(
108 104
         &validator_key.secret_key,
109 105
         validator_key.account_id.clone(),
110 106
         key_nonce,
111
-    )?;
112
-
113
-    Ok(signer)
114
-}
115
-
116
-async fn meeting_room_contract() -> anyhow::Result<Vec<u8>> {
117
-    Ok(
118
-        reqwest::get("http://contract-artifact:3033/meeting_room.wasm")
119
-            .await?
120
-            .bytes()
121
-            .await?
122
-            .to_vec(),
123 107
     )
108
+    .unwrap()
124 109
 }
125 110
 
126
-#[wasm_bindgen_test]
127
-async fn pass() {
128
-    console_log!("Test Passed");
111
+async fn meeting_room_contract() -> Vec<u8> {
112
+    reqwest::get("http://contract-artifact:3033/meet.wasm")
113
+        .await
114
+        .unwrap()
115
+        .bytes()
116
+        .await
117
+        .unwrap()
118
+        .to_vec()
129 119
 }
130 120
 
131 121
 #[wasm_bindgen_test]
@@ -133,10 +123,10 @@ async fn key_exchange() {
133 123
     let rpc_url = Url::parse("http://sandbox:3030").unwrap();
134 124
     let client = NearClient::new(rpc_url).unwrap();
135 125
 
136
-    let validator_key = validator_key().await.unwrap();
137
-    let validator = validator_account(&client, validator_key).await.unwrap();
126
+    let validator_key = validator_key().await;
127
+    let validator = validator_account(&client, validator_key).await;
138 128
 
139
-    // delete accounts if any to be sure there are no dublicates
129
+    // delete accounts if any to be sure there are no duplicates
140 130
     drop_created_account(
141 131
         &client,
142 132
         validator.account(),
@@ -165,7 +155,7 @@ async fn key_exchange() {
165 155
         create_account(&client, &validator, &CONTRACT_BYTES, "contract.test.near").await;
166 156
 
167 157
     // Contract byte code
168
-    let contract_wasm = meeting_room_contract().await.unwrap();
158
+    let contract_wasm = meeting_room_contract().await;
169 159
     let contract_id = contract_sg.account().clone();
170 160
 
171 161
     client
@@ -205,75 +195,66 @@ async fn key_exchange() {
205 195
 
206 196
     let set = js_sys::Set::new(&arr);
207 197
 
208
-    // Init meeting
209
-    let meeting_id = JsFuture::from(provisioner.init(set, 3000))
210
-        .await
211
-        .unwrap()
212
-        .as_string()
213
-        .unwrap();
198
+    let Meeting { meet_id, .. } = serde_wasm_bindgen::from_value::<Meeting>(
199
+        JsFuture::from(provisioner.init_meeting(set, 3000))
200
+            .await
201
+            .unwrap(),
202
+    )
203
+    .unwrap();
214 204
 
215 205
     // Then each user push own key to blockchain
216
-    let keypair_str = bob_sg.secret_key().string();
217 206
     let provisioner_bob = KeyProvisioner::new(
218
-        keypair_str,
207
+        bob_sg.secret_key().string(),
219 208
         bob_sg.nonce(),
220 209
         bob_sg.account().to_string(),
221 210
         config.clone(),
222 211
     )
223 212
     .unwrap();
224 213
 
225
-    let _ = JsFuture::from(provisioner_bob.push_to_near(meeting_id.clone(), 3000))
226
-        .await
227
-        .unwrap();
228
-
229
-    let keypair_str = karl_sg.secret_key().string();
230 214
     let provisioner_karl = KeyProvisioner::new(
231
-        keypair_str,
215
+        karl_sg.secret_key().string(),
232 216
         karl_sg.nonce(),
233 217
         karl_sg.account().to_string(),
234 218
         config.clone(),
235 219
     )
236 220
     .unwrap();
237 221
 
238
-    let _ = JsFuture::from(provisioner_karl.push_to_near(meeting_id.clone(), 3000))
239
-        .await
240
-        .unwrap();
241
-
242
-    let keypair_str = mike_sg.secret_key().string();
243 222
     let provisioner_mike = KeyProvisioner::new(
244
-        keypair_str,
223
+        mike_sg.secret_key().string(),
245 224
         mike_sg.nonce(),
246 225
         mike_sg.account().to_string(),
247 226
         config.clone(),
248 227
     )
249 228
     .unwrap();
250 229
 
251
-    let _ = JsFuture::from(provisioner_mike.push_to_near(meeting_id.clone(), 3000))
252
-        .await
253
-        .unwrap();
230
+    let _ = JsFuture::from(provisioner.send_keys(meet_id.to_string(), 300)).await;
231
+    gloo_timers::future::sleep(std::time::Duration::from_millis(400)).await;
232
+    let _ = JsFuture::from(provisioner_bob.get_key(meet_id.to_string(), 200)).await;
233
+    let _ = JsFuture::from(provisioner_karl.get_key(meet_id.to_string(), 200)).await;
234
+    let _ = JsFuture::from(provisioner_mike.get_key(meet_id.to_string(), 200)).await;
254 235
 
255 236
     // Send encrypted keys to participants
256 237
     // timeout in millis
257
-    let alice_key = JsFuture::from(provisioner.send_keys(meeting_id.clone(), 200))
238
+    let alice_key = JsFuture::from(provisioner.send_keys(meet_id.to_string(), 4000))
258 239
         .await
259 240
         .unwrap()
260 241
         .as_string()
261 242
         .unwrap();
262 243
 
263 244
     // Fetch encrypted keys
264
-    let bob_key = JsFuture::from(provisioner_bob.get_key(meeting_id.clone(), 200))
245
+    let bob_key = JsFuture::from(provisioner_bob.get_key(meet_id.to_string(), 1000))
265 246
         .await
266 247
         .unwrap()
267 248
         .as_string()
268 249
         .unwrap();
269 250
 
270
-    let karl_key = JsFuture::from(provisioner_karl.get_key(meeting_id.clone(), 200))
251
+    let karl_key = JsFuture::from(provisioner_karl.get_key(meet_id.to_string(), 1000))
271 252
         .await
272 253
         .unwrap()
273 254
         .as_string()
274 255
         .unwrap();
275 256
 
276
-    let mike_key = JsFuture::from(provisioner_mike.get_key(meeting_id.clone(), 200))
257
+    let mike_key = JsFuture::from(provisioner_mike.get_key(meet_id.to_string(), 1000))
277 258
         .await
278 259
         .unwrap()
279 260
         .as_string()
@@ -302,6 +283,3 @@ async fn key_exchange() {
302 283
     )
303 284
     .await;
304 285
 }
305
-
306
-#[wasm_bindgen_test]
307
-async fn sandbox() {}

Chargement…
Annuler
Enregistrer