wallet import/export API and QRcode scanning from Tauri plugin

pull/31/head^2
Niko PLP 5 months ago
parent 104c796f68
commit 65b91ffc3f
  1. 48
      Cargo.lock
  2. 48
      README.md
  3. 1
      nextgraph/.gitignore
  4. 3
      nextgraph/Cargo.toml
  5. 3
      nextgraph/src/lib.rs
  6. 261
      nextgraph/src/local_broker.rs
  7. 1
      nextgraph/src/local_broker_dev_env.rs
  8. 1
      ng-app/package.json
  9. 3
      ng-app/src-tauri/Cargo.toml
  10. 1
      ng-app/src-tauri/gen/android/app/src/main/AndroidManifest.xml
  11. 70
      ng-app/src-tauri/src/lib.rs
  12. 2
      ng-app/src/App.svelte
  13. 5
      ng-app/src/api.ts
  14. 3
      ng-app/src/lib/Login.svelte
  15. 9
      ng-app/src/locales/en.json
  16. 4
      ng-app/src/routes/AccountInfo.svelte
  17. 51
      ng-app/src/routes/ScanQR.svelte
  18. 2
      ng-app/src/routes/User.svelte
  19. 3
      ng-app/src/routes/UserRegistered.svelte
  20. 8
      ng-app/src/routes/WalletCreate.svelte
  21. 16
      ng-app/src/routes/WalletInfo.svelte
  22. 112
      ng-app/src/routes/WalletLogin.svelte
  23. 13
      ng-app/src/store.ts
  24. 101
      ng-broker/src/server_broker.rs
  25. 2
      ng-net/src/actors/client/mod.rs
  26. 89
      ng-net/src/actors/client/wallet_put_export.rs
  27. 2
      ng-net/src/actors/ext/mod.rs
  28. 109
      ng-net/src/actors/ext/wallet_get_export.rs
  29. 2
      ng-net/src/actors/mod.rs
  30. 72
      ng-net/src/actors/start.rs
  31. 28
      ng-net/src/broker.rs
  32. 103
      ng-net/src/connection.rs
  33. 13
      ng-net/src/server_broker.rs
  34. 121
      ng-net/src/types.rs
  35. 43
      ng-repo/src/errors.rs
  36. 75
      ng-sdk-js/src/lib.rs
  37. 20
      ng-verifier/src/verifier.rs
  38. 1
      ng-wallet/Cargo.toml
  39. 24
      ng-wallet/src/types.rs
  40. 8
      pnpm-lock.yaml

48
Cargo.lock generated

@ -2797,14 +2797,13 @@ checksum = "f850fafca79ebacd70eab9d80cb75a33aeda38bde8f3dd784c1837cdf0bde631"
[[package]] [[package]]
name = "json-patch" name = "json-patch"
version = "1.0.0" version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f54898088ccb91df1b492cc80029a6fdf1c48ca0db7c6822a8babad69c94658" checksum = "ec9ad60d674508f3ca8f380a928cfe7b096bc729c4e2dbfe3852bc45da3ab30b"
dependencies = [ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror",
"treediff",
] ]
[[package]] [[package]]
@ -2942,9 +2941,9 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.19" version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
dependencies = [ dependencies = [
"value-bag", "value-bag",
] ]
@ -3264,6 +3263,7 @@ dependencies = [
"async-trait", "async-trait",
"base64-url", "base64-url",
"futures", "futures",
"lazy_static",
"ng-client-ws", "ng-client-ws",
"ng-net", "ng-net",
"ng-repo", "ng-repo",
@ -3271,7 +3271,9 @@ dependencies = [
"ng-verifier", "ng-verifier",
"ng-wallet", "ng-wallet",
"once_cell", "once_cell",
"qrcode",
"serde_bare", "serde_bare",
"serde_bytes",
"serde_json", "serde_json",
"web-time", "web-time",
"zeroize", "zeroize",
@ -3293,7 +3295,9 @@ dependencies = [
"sys-locale", "sys-locale",
"tauri", "tauri",
"tauri-build", "tauri-build",
"tauri-plugin-barcode-scanner",
"tauri-plugin-window", "tauri-plugin-window",
"tauri-utils",
] ]
[[package]] [[package]]
@ -3547,6 +3551,7 @@ dependencies = [
"aes-gcm-siv", "aes-gcm-siv",
"argon2", "argon2",
"async-std", "async-std",
"base64-url",
"blake3", "blake3",
"chacha20poly1305", "chacha20poly1305",
"crypto_box", "crypto_box",
@ -4436,6 +4441,12 @@ dependencies = [
"bytemuck", "bytemuck",
] ]
[[package]]
name = "qrcode"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec"
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.28.2" version = "0.28.2"
@ -5575,6 +5586,20 @@ dependencies = [
"tauri-utils", "tauri-utils",
] ]
[[package]]
name = "tauri-plugin-barcode-scanner"
version = "2.0.0-alpha.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "058922dd9cafc89a865593c99507177c4cdbdad9d22a911ac41872ed7dbf0348"
dependencies = [
"log",
"serde",
"serde_json",
"tauri",
"tauri-build",
"thiserror",
]
[[package]] [[package]]
name = "tauri-plugin-window" name = "tauri-plugin-window"
version = "2.0.0-alpha.1" version = "2.0.0-alpha.1"
@ -6043,15 +6068,6 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "treediff"
version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303"
dependencies = [
"serde_json",
]
[[package]] [[package]]
name = "try-lock" name = "try-lock"
version = "0.2.4" version = "0.2.4"
@ -6198,9 +6214,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]] [[package]]
name = "value-bag" name = "value-bag"
version = "1.4.0" version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e" checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101"
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"

@ -43,9 +43,9 @@ Read our [getting started guide](https://docs.nextgraph.org/en/getting-started/)
## For contributors ## For contributors
- [Install Rust](https://www.rust-lang.org/tools/install) minimum required MSRV 1.74.0 - [Install Rust](https://www.rust-lang.org/tools/install) minimum required MSRV 1.74.0
- [Install Nodejs](https://nodejs.org/en/download/) - [Install Nodejs](https://nodejs.org/en/download/)
- [Install LLVM](https://rust-lang.github.io/rust-bindgen/requirements.html) - [Install LLVM](https://rust-lang.github.io/rust-bindgen/requirements.html)
On openbsd, for LLVM you need to choose llvm-17. On openbsd, for LLVM you need to choose llvm-17.
@ -57,6 +57,14 @@ cargo install wasm-pack --git https://github.com/rustwasm/wasm-pack.git --rev c2
then : then :
create a file called `nextgraph/src/local_broker_dev_env.rs` with the content :
```
pub const PEER_ID: &str = "FtdzuDYGewfXWdoPuXIPb0wnd0SAg1WoA2B14S7jW3MA";
```
once your ngd server will run in your dev env, replace the above string with the actual PEER ID of your ngd server.
``` ```
cargo install cargo-watch cargo install cargo-watch
// optionally, if you want a Rust REPL: cargo install evcxr_repl // optionally, if you want a Rust REPL: cargo install evcxr_repl
@ -70,20 +78,20 @@ cargo build
The crates are organized as follow : The crates are organized as follow :
- [nextgraph](nextgraph/README.md) : Client library. Use this crate to embed NextGraph client in your Rust application - [nextgraph](nextgraph/README.md) : Client library. Use this crate to embed NextGraph client in your Rust application
- [ngcli](ngcli/README.md) : CLI tool to manipulate the local documents and repos and administrate the server - [ngcli](ngcli/README.md) : CLI tool to manipulate the local documents and repos and administrate the server
- [ngd](ngd/README.md) : binary executable of the daemon (that can run a broker, verifier and/or Rust services) - [ngd](ngd/README.md) : binary executable of the daemon (that can run a broker, verifier and/or Rust services)
- [ng-app](ng-app/README.md) : all the native apps, based on Tauri, and the official web app. - [ng-app](ng-app/README.md) : all the native apps, based on Tauri, and the official web app.
- [ng-sdk-js](ng-sdk-js/DEV.md) : contains the JS SDK, with example for: web app, react app, or node service. - [ng-sdk-js](ng-sdk-js/DEV.md) : contains the JS SDK, with example for: web app, react app, or node service.
- ng-repo : Repositories common library - ng-repo : Repositories common library
- ng-net : Network common library - ng-net : Network common library
- ng-verifier : Verifier library, that exposes the document API to the app - ng-verifier : Verifier library, that exposes the document API to the app
- ng-wallet : keeps the secret keys of all identities of the user in a safe wallet - ng-wallet : keeps the secret keys of all identities of the user in a safe wallet
- ng-broker : Core and Server Broker library - ng-broker : Core and Server Broker library
- ng-client-ws : Websocket client library - ng-client-ws : Websocket client library
- ng-storage-rocksdb : RocksDB backed stores. see also dependency [repo here](https://git.nextgraph.org/NextGraph/rust-rocksdb) - ng-storage-rocksdb : RocksDB backed stores. see also dependency [repo here](https://git.nextgraph.org/NextGraph/rust-rocksdb)
- ngone : server for nextgraph.one. helps user bootstrap into the right app. Not useful to you. Published here for transparency - ngone : server for nextgraph.one. helps user bootstrap into the right app. Not useful to you. Published here for transparency
- ngaccount : server for nextgraph's Broker Service Provider account manager. Not useful to you. Published here for transparency - ngaccount : server for nextgraph's Broker Service Provider account manager. Not useful to you. Published here for transparency
### Run ### Run
@ -224,9 +232,9 @@ additional terms or conditions.
Licensed under either of Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0) - Apache License, Version 2.0 ([LICENSE-APACHE2](LICENSE-APACHE2) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option. at your option.
`SPDX-License-Identifier: Apache-2.0 OR MIT` `SPDX-License-Identifier: Apache-2.0 OR MIT`

@ -1 +1,2 @@
tests tests
local_broker_dev_env_peer_id.rs

@ -18,6 +18,7 @@ maintenance = { status = "actively-developed" }
[dependencies] [dependencies]
serde_bare = "0.5.0" serde_bare = "0.5.0"
serde_json = "1.0" serde_json = "1.0"
serde_bytes = "0.11.7"
base64-url = "2.0.0" base64-url = "2.0.0"
once_cell = "1.17.1" once_cell = "1.17.1"
zeroize = { version = "1.7.0", features = ["zeroize_derive"] } zeroize = { version = "1.7.0", features = ["zeroize_derive"] }
@ -25,7 +26,9 @@ futures = "0.3.24"
async-std = { version = "1.12.0", features = [ "attributes", "unstable" ] } async-std = { version = "1.12.0", features = [ "attributes", "unstable" ] }
async-trait = "0.1.64" async-trait = "0.1.64"
async-once-cell = "0.5.3" async-once-cell = "0.5.3"
lazy_static = "1.4.0"
web-time = "0.2.0" web-time = "0.2.0"
qrcode = { version = "0.14.1", default-features = false, features = ["svg"] }
ng-repo = { path = "../ng-repo", version = "0.1.0-preview.1" } ng-repo = { path = "../ng-repo", version = "0.1.0-preview.1" }
ng-net = { path = "../ng-net", version = "0.1.0-preview.1" } ng-net = { path = "../ng-net", version = "0.1.0-preview.1" }
ng-wallet = { path = "../ng-wallet", version = "0.1.0-preview.5" } ng-wallet = { path = "../ng-wallet", version = "0.1.0-preview.5" }

@ -98,3 +98,6 @@ pub mod verifier {
pub mod wallet { pub mod wallet {
pub use ng_wallet::*; pub use ng_wallet::*;
} }
#[cfg(debug_assertions)]
mod local_broker_dev_env;

@ -20,6 +20,8 @@ use once_cell::sync::Lazy;
use serde_bare::to_vec; use serde_bare::to_vec;
use serde_json::json; use serde_json::json;
use zeroize::Zeroize; use zeroize::Zeroize;
use qrcode::{render::svg, QrCode};
use lazy_static::lazy_static;
use ng_repo::block_storage::BlockStorage; use ng_repo::block_storage::BlockStorage;
use ng_repo::block_storage::HashMapBlockStorage; use ng_repo::block_storage::HashMapBlockStorage;
@ -27,7 +29,7 @@ use ng_repo::errors::{NgError, ProtocolError};
use ng_repo::log::*; use ng_repo::log::*;
use ng_repo::os_info::get_os_info; use ng_repo::os_info::get_os_info;
use ng_repo::types::*; use ng_repo::types::*;
use ng_repo::utils::{derive_key, generate_keypair}; use ng_repo::utils::{derive_key, generate_keypair, encrypt_in_place};
use ng_net::{actor::*,actors::admin::*}; use ng_net::{actor::*,actors::admin::*};
use ng_net::broker::*; use ng_net::broker::*;
@ -1492,6 +1494,238 @@ pub async fn wallet_add(lws: LocalWalletStorageV0) -> Result<(), NgError> {
} }
Ok(()) Ok(())
} }
#[cfg(debug_assertions)]
lazy_static! {
static ref NEXTGRAPH_EU: BrokerServerV0 = BrokerServerV0 {
server_type: BrokerServerTypeV0::Localhost(14400),
can_verify: false,
can_forward: false,
peer_id: ng_repo::utils::decode_key({use crate::local_broker_dev_env::PEER_ID; PEER_ID}).unwrap(),
};
}
#[cfg(not(debug_assertions))]
lazy_static! {
static ref NEXTGRAPH_EU: BrokerServerV0 = BrokerServerV0 {
server_type: BrokerServerTypeV0::Domain("nextgraph.eu".to_string()),
can_verify: false,
can_forward: false,
peer_id: ng_repo::utils::decode_key("FtdzuDYGewfXWdoPuXIPb0wnd0SAg1WoA2B14S7jW3MA").unwrap(),
};
}
/// Obtains a Wallet object from a QRCode or a TextCode.
///
/// The returned object can be used to import the wallet into a new Device
/// with the help of the function [wallet_open_with_pazzle_words]
/// followed by [wallet_import]
pub async fn wallet_import_from_code(code: String) -> Result<Wallet, NgError> {
let qr = NgQRCode::from_code(code)?;
match qr {
NgQRCode::V0(NgQRCodeV0{broker, rendezvous, secret_key, is_rendezvous}) => {
let wallet: ExportedWallet = do_ext_call(
&broker,
ExtWalletGetExportV0 {
id:rendezvous,
is_rendezvous
}).await?;
let mut buf = wallet.0.into_vec();
encrypt_in_place(&mut buf,*secret_key.slice(), [0;12]);
let wallet: Wallet = serde_bare::from_slice(&buf)?;
let broker = match LOCAL_BROKER.get() {
None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
Some(Ok(broker)) => broker.read().await,
};
// check that the wallet is not already present in local_broker
let wallet_name = wallet.name();
if broker.wallets.get(&wallet_name).is_none() {
Ok(wallet)
} else {
Err(NgError::WalletAlreadyAdded)
}
}
}
}
/// Starts a rendez-vous to obtain a wallet from other device.
///
/// A rendezvous is used when the device that is importing, doesn't have a camera.
/// The QRCode is displayed on that device, and another device (with camera, and with the wallet) will scan it.
///
/// Returns the QRcode in SVG format, and the code (a string) to be used with [wallet_import_from_code]
pub async fn wallet_import_rendezvous(size: u32) -> Result<(String,String), NgError> {
let code = NgQRCode::V0(NgQRCodeV0 {
broker: NEXTGRAPH_EU.clone(),
rendezvous: SymKey::random(),
secret_key: SymKey::random(),
is_rendezvous: true
});
let code_string = code.to_code();
let code_svg = match QrCode::with_error_correction_level(&code_string, qrcode::EcLevel::M) {
Ok(qr) => {
let svg = qr
.render()
.max_dimensions(size, size)
.dark_color(svg::Color("#000000"))
.light_color(svg::Color("#ffffff"))
.build();
svg
},
Err(_e) => {return Err(NgError::BrokerError)}
};
Ok((code_svg,code_string))
}
/// Gets the TextCode to display in order to export the wallet of the current session ID
///
/// The ExportedWallet is valid for 5 min.
///
/// Returns the TextCode
pub async fn wallet_export_get_textcode(session_id: u64) -> Result<String, NgError> {
let broker = match LOCAL_BROKER.get() {
None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
Some(Ok(broker)) => broker.read().await,
};
match &broker.config {
LocalBrokerConfig::Headless(_) => {
return Err(NgError::LocalBrokerIsHeadless)
},
_ => {
let (real_session_id, is_remote) = broker.get_real_session_id_for_mut(session_id)?;
if is_remote {
return Err(NgError::NotImplemented);
} else {
let session = broker.opened_sessions_list[real_session_id].as_ref()
.ok_or(NgError::SessionNotFound)?;
let wallet_name = session.config.wallet_name();
match broker.wallets.get(&wallet_name) {
None => Err(NgError::WalletNotFound),
Some(lws) => {
let broker = lws.bootstrap.servers().first().unwrap();
let wallet = &lws.wallet;
let secret_key = SymKey::random();
let rendezvous = SymKey::random();
let code = NgQRCode::V0(NgQRCodeV0 {
broker: broker.clone(),
rendezvous: rendezvous.clone(),
secret_key: secret_key.clone(),
is_rendezvous: false
});
let code_string = code.to_code();
let mut wallet_ser = serde_bare::to_vec(wallet)?;
encrypt_in_place(&mut wallet_ser,*secret_key.slice(), [0;12]);
let exported_wallet = ExportedWallet(serde_bytes::ByteBuf::from(wallet_ser));
match session.verifier.client_request::<WalletPutExport, ()>(WalletPutExport::V0(WalletPutExportV0{wallet:exported_wallet, rendezvous_id:rendezvous, is_rendezvous:false})).await {
Err(e) => Err(e),
Ok(SoS::Stream(_)) => Err(NgError::InvalidResponse),
Ok(SoS::Single(_)) => Ok(code_string)
}
}
}
}
}
}
}
/// Gets the QRcode to display in order to export a wallet of the current session ID
///
/// The ExportedWallet is valid for 5 min.
///
/// Returns the QRcode in SVG format
pub async fn wallet_export_get_qrcode(session_id: u64, size: u32) -> Result<String, NgError> {
let code_string = wallet_export_get_textcode(session_id).await?;
let code_svg = match QrCode::with_error_correction_level(&code_string, qrcode::EcLevel::M) {
Ok(qr) => {
let svg = qr
.render()
.max_dimensions(size, size)
.dark_color(svg::Color("#000000"))
.light_color(svg::Color("#ffffff"))
.build();
svg
},
Err(_e) => {return Err(NgError::BrokerError)}
};
Ok(code_svg)
}
/// Puts the Wallet to the rendezvous ID that was scanned
///
/// The rendezvous ID is valid for 5 min.
pub async fn wallet_export_rendezvous(session_id: u64, code: String) -> Result<(), NgError> {
let qr = NgQRCode::from_code(code)?;
match qr {
NgQRCode::V0(NgQRCodeV0{broker: _, rendezvous, secret_key, is_rendezvous}) => {
if !is_rendezvous {
return Err(NgError::NotARendezVous);
}
let broker = match LOCAL_BROKER.get() {
None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
Some(Ok(broker)) => broker.read().await,
};
match &broker.config {
LocalBrokerConfig::Headless(_) => {
return Err(NgError::LocalBrokerIsHeadless)
},
_ => {
let (real_session_id, is_remote) = broker.get_real_session_id_for_mut(session_id)?;
if is_remote {
return Err(NgError::NotImplemented);
} else {
let session = broker.opened_sessions_list[real_session_id].as_ref()
.ok_or(NgError::SessionNotFound)?;
let wallet_name = session.config.wallet_name();
match broker.wallets.get(&wallet_name) {
None => Err(NgError::WalletNotFound),
Some(lws) => {
//let broker = lws.bootstrap.servers().first().unwrap();
let wallet = &lws.wallet;
let mut wallet_ser = serde_bare::to_vec(wallet)?;
encrypt_in_place(&mut wallet_ser,*secret_key.slice(), [0;12]);
let exported_wallet = ExportedWallet(serde_bytes::ByteBuf::from(wallet_ser));
// TODO: send the WalletPutExport client request to the broker received from QRcode. for now it is cheer luck that all clients are connected to nextgraph.eu.
// if the user doesn't have an account with nextgraph.eu, their broker should relay the request (core protocol ?)
match session.verifier.client_request::<WalletPutExport, ()>(WalletPutExport::V0(WalletPutExportV0{wallet:exported_wallet, rendezvous_id:rendezvous, is_rendezvous:true})).await {
Err(e) => Err(e),
Ok(SoS::Stream(_)) => Err(NgError::InvalidResponse),
Ok(SoS::Single(_)) => Ok(())
}
}
}
}
}
}
}
}
}
/// Reads a binary Wallet File and decodes it to a Wallet object. /// Reads a binary Wallet File and decodes it to a Wallet object.
/// ///
@ -2126,7 +2360,7 @@ pub async fn app_request(request: AppRequest) -> Result<AppResponse, NgError> {
let session = broker.opened_sessions_list[real_session_id] let session = broker.opened_sessions_list[real_session_id]
.as_mut() .as_mut()
.ok_or(NgError::SessionNotFound)?; .ok_or(NgError::SessionNotFound)?;
session.verifier.app_request(request).await session.verifier.app_request(request).await
} }
} }
} }
@ -2214,6 +2448,29 @@ async fn do_admin_call<
.await .await
} }
async fn do_ext_call<
A: Into<ProtocolMessage> + Into<ExtRequestContentV0> + std::fmt::Debug + Sync + Send + 'static,
B: TryFrom<ProtocolMessage, Error = ProtocolError>
+ std::fmt::Debug
+ Sync
+ Send
+ 'static,
>(
broker_server: &BrokerServerV0,
cmd: A,
) -> Result<B, NgError> {
let (peer_privk, peer_pubk) = generate_keypair();
Broker::ext(
Box::new(ConnectionWebSocket {}),
peer_privk,
peer_pubk,
broker_server.peer_id,
broker_server.get_ws_url(&None).await.unwrap().0, // for now we are only connecting to NextGraph SaaS cloud (nextgraph.eu) so it is safe.
cmd,
)
.await
}
#[doc(hidden)] #[doc(hidden)]
pub async fn admin_create_user(server_peer_id: DirectPeerId, admin_user_key: PrivKey, server_addr: BindAddress) -> Result<UserId, ProtocolError> { pub async fn admin_create_user(server_peer_id: DirectPeerId, admin_user_key: PrivKey, server_addr: BindAddress) -> Result<UserId, ProtocolError> {

@ -0,0 +1 @@
pub const PEER_ID: &str = "FtdzuDYGewfXWdoPuXIPb0wnd0SAg1WoA2B14S7jW3MA";

@ -18,6 +18,7 @@
"dependencies": { "dependencies": {
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"@tauri-apps/api": "2.0.0-alpha.8", "@tauri-apps/api": "2.0.0-alpha.8",
"@tauri-apps/plugin-barcode-scanner": "2.0.0-alpha.0",
"@tauri-apps/plugin-window": "2.0.0-alpha.1", "@tauri-apps/plugin-window": "2.0.0-alpha.1",
"async-proxy": "^0.4.1", "async-proxy": "^0.4.1",
"classnames": "^2.3.2", "classnames": "^2.3.2",

@ -21,7 +21,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "2.0.0-alpha.8", features = [] } tauri-build = { version = "2.0.0-alpha.8", features = [] }
# tauri-macros = { version = "=2.0.0-alpha.6" } # tauri-macros = { version = "=2.0.0-alpha.6" }
# tauri-codegen = { version = "=2.0.0-alpha.6" } # tauri-codegen = { version = "=2.0.0-alpha.6" }
# tauri-utils = { version = "=2.0.0-alpha.6" } tauri-utils = { version = "=2.0.0-alpha.7" }
[dependencies] [dependencies]
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
@ -32,6 +32,7 @@ sys-locale = { version = "0.3.1" }
ng-async-tungstenite = { git = "https://git.nextgraph.org/NextGraph/async-tungstenite.git", branch = "nextgraph", features = ["async-std-runtime", "async-native-tls"] } ng-async-tungstenite = { git = "https://git.nextgraph.org/NextGraph/async-tungstenite.git", branch = "nextgraph", features = ["async-std-runtime", "async-native-tls"] }
tauri = { version = "2.0.0-alpha.14", features = [] } tauri = { version = "2.0.0-alpha.14", features = [] }
tauri-plugin-window = "2.0.0-alpha.1" tauri-plugin-window = "2.0.0-alpha.1"
tauri-plugin-barcode-scanner = "=2.0.0-alpha.0"
# tauri-plugin-window = { git = "https://git.nextgraph.org/NextGraph/plugins-workspace.git", branch="window-alpha.1-nextgraph" } # tauri-plugin-window = { git = "https://git.nextgraph.org/NextGraph/plugins-workspace.git", branch="window-alpha.1-nextgraph" }
# tauri = { git = "https://git.nextgraph.org/NextGraph/tauri.git", branch="alpha.11-nextgraph", features = ["no-ipc-custom-protocol"] } # tauri = { git = "https://git.nextgraph.org/NextGraph/tauri.git", branch="alpha.11-nextgraph", features = ["no-ipc-custom-protocol"] }
# tauri = { git = "https://github.com/simonhyll/tauri.git", branch="fix/ipc-mixup", features = [] } # tauri = { git = "https://github.com/simonhyll/tauri.git", branch="fix/ipc-mixup", features = [] }

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<application <application
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"

@ -127,8 +127,9 @@ async fn wallet_open_with_mnemonic_words(
pin: [u8; 4], pin: [u8; 4],
_app: tauri::AppHandle, _app: tauri::AppHandle,
) -> Result<SensitiveWallet, String> { ) -> Result<SensitiveWallet, String> {
let wallet = nextgraph::local_broker::wallet_open_with_mnemonic_words(&wallet, &mnemonic_words, pin) let wallet =
.map_err(|e| e.to_string())?; nextgraph::local_broker::wallet_open_with_mnemonic_words(&wallet, &mnemonic_words, pin)
.map_err(|e| e.to_string())?;
Ok(wallet) Ok(wallet)
} }
@ -205,6 +206,55 @@ async fn wallet_import(
.map_err(|e: NgError| e.to_string()) .map_err(|e: NgError| e.to_string())
} }
#[tauri::command(rename_all = "snake_case")]
async fn wallet_export_rendezvous(
session_id: u64,
code: String,
_app: tauri::AppHandle,
) -> Result<(), String> {
nextgraph::local_broker::wallet_export_rendezvous(session_id, code)
.await
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_export_get_qrcode(
session_id: u64,
size: u32,
_app: tauri::AppHandle,
) -> Result<String, String> {
nextgraph::local_broker::wallet_export_get_qrcode(session_id, size)
.await
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_export_get_textcode(
session_id: u64,
_app: tauri::AppHandle,
) -> Result<String, String> {
nextgraph::local_broker::wallet_export_get_textcode(session_id)
.await
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_import_rendezvous(
size: u32,
_app: tauri::AppHandle,
) -> Result<(String, String), String> {
nextgraph::local_broker::wallet_import_rendezvous(size)
.await
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")]
async fn wallet_import_from_code(code: String, _app: tauri::AppHandle) -> Result<Wallet, String> {
nextgraph::local_broker::wallet_import_from_code(code)
.await
.map_err(|e: NgError| e.to_string())
}
#[tauri::command(rename_all = "snake_case")] #[tauri::command(rename_all = "snake_case")]
async fn get_wallets( async fn get_wallets(
app: tauri::AppHandle, app: tauri::AppHandle,
@ -517,7 +567,7 @@ impl AppBuilder {
pub fn run(self) { pub fn run(self) {
let setup = self.setup; let setup = self.setup;
tauri::Builder::default() let builder = tauri::Builder::default()
.setup(move |app| { .setup(move |app| {
if let Some(setup) = setup { if let Some(setup) = setup {
(setup)(app)?; (setup)(app)?;
@ -533,7 +583,14 @@ impl AppBuilder {
} }
Ok(()) Ok(())
}) })
.plugin(tauri_plugin_window::init()) .plugin(tauri_plugin_window::init());
#[cfg(mobile)]
{
let builder = builder.plugin(tauri_plugin_barcode_scanner::init());
}
builder
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
test, test,
locales, locales,
@ -547,6 +604,11 @@ impl AppBuilder {
wallet_read_file, wallet_read_file,
wallet_get_file, wallet_get_file,
wallet_import, wallet_import,
wallet_export_rendezvous,
wallet_export_get_qrcode,
wallet_export_get_textcode,
wallet_import_rendezvous,
wallet_import_from_code,
wallet_close, wallet_close,
encode_create_account, encode_create_account,
session_start, session_start,

@ -35,6 +35,7 @@
import User from "./routes/User.svelte"; import User from "./routes/User.svelte";
import UserRegistered from "./routes/UserRegistered.svelte"; import UserRegistered from "./routes/UserRegistered.svelte";
import Install from "./routes/Install.svelte"; import Install from "./routes/Install.svelte";
import ScanQR from "./routes/ScanQR.svelte";
import ng from "./api"; import ng from "./api";
import AccountInfo from "./routes/AccountInfo.svelte"; import AccountInfo from "./routes/AccountInfo.svelte";
@ -49,6 +50,7 @@
routes.set("/user/registered", UserRegistered); routes.set("/user/registered", UserRegistered);
routes.set("/wallet", WalletInfo); routes.set("/wallet", WalletInfo);
routes.set("/user/accounts", AccountInfo); routes.set("/user/accounts", AccountInfo);
routes.set("/wallet/scanqr", ScanQR);
if (import.meta.env.NG_APP_WEB) routes.set("/install", Install); if (import.meta.env.NG_APP_WEB) routes.set("/install", Install);
routes.set(/^\/did:ng(.*)/i, NURI); routes.set(/^\/did:ng(.*)/i, NURI);
routes.set("*", NotFound); routes.set("*", NotFound);

@ -22,6 +22,11 @@ const mapping = {
"wallet_read_file": ["file"], "wallet_read_file": ["file"],
"wallet_get_file": ["wallet_name"], "wallet_get_file": ["wallet_name"],
"wallet_import": ["encrypted_wallet","opened_wallet","in_memory"], "wallet_import": ["encrypted_wallet","opened_wallet","in_memory"],
"wallet_export_rendezvous": ["session_id", "code"],
"wallet_export_get_qrcode": ["session_id", "size"],
"wallet_export_get_textcode": ["session_id"],
"wallet_import_rendezvous": ["size"],
"wallet_import_from_code": ["code"],
"wallet_close": ["wallet_name"], "wallet_close": ["wallet_name"],
"encode_create_account": ["payload"], "encode_create_account": ["payload"],
"session_start": ["wallet_name","user"], "session_start": ["wallet_name","user"],

@ -32,6 +32,7 @@
ArrowLeft, ArrowLeft,
} from "svelte-heros-v2"; } from "svelte-heros-v2";
import PasswordInput from "./components/PasswordInput.svelte"; import PasswordInput from "./components/PasswordInput.svelte";
import { display_error } from "../store";
//import Worker from "../worker.js?worker&inline"; //import Worker from "../worker.js?worker&inline";
export let wallet; export let wallet;
export let for_import = false; export let for_import = false;
@ -685,7 +686,7 @@
/> />
</svg> </svg>
<Alert color="red" class="mt-5"> <Alert color="red" class="mt-5">
{$t("errors." + error)} {display_error(error)}
</Alert> </Alert>
</div> </div>
<div class="flex justify-between mt-auto gap-4"> <div class="flex justify-between mt-auto gap-4">

@ -295,18 +295,19 @@
"VerifierError": "Error during verification.", "VerifierError": "Error during verification.",
"SiteNotFoundOnBroker": "The site cannot be found on the broker", "SiteNotFoundOnBroker": "The site cannot be found on the broker",
"BrokerConfigErrorStr": "{error}", "BrokerConfigErrorStr": "{error}",
"BrokerConfigError": "Error in the broker configuration", "BrokerConfigError": "Error in the broker configuration.",
"MalformedEvent": "The event has an invalid format.", "MalformedEvent": "The event has an invalid format.",
"InvalidPayload": "The payload is invalid.", "InvalidPayload": "The payload is invalid.",
"WrongUploadId": "The upload ID is incorrect.", "WrongUploadId": "The upload ID is incorrect.",
"FileError": "Error with file.", "FileError": "Error with file.",
"InternalError": "Internal Error", "InternalError": "Internal Error",
"OxiGraphError": "Error in OxiGraph database.", "OxiGraphError": "Error in OxiGraph database.",
"ConfigError": "Error in configuration", "ConfigError": "Error in configuration.",
"LocalBrokerIsHeadless": "The local broker is headless.", "LocalBrokerIsHeadless": "The local broker is headless.",
"LocalBrokerIsNotHeadless": "The local broker is not headless.", "LocalBrokerIsNotHeadless": "The local broker is not headless.",
"InvalidNuri": "Invalid NextGraph URI", "InvalidNuri": "Invalid NextGraph URI.",
"InvalidTarget": "Cannot resolve target" "InvalidTarget": "Cannot resolve target.",
"ExportWalletTimeOut": "Export of wallet has expired."
}, },
"connectivity": { "connectivity": {
"stopped": "Stopped", "stopped": "Stopped",

@ -22,7 +22,7 @@
import { onMount, tick } from "svelte"; import { onMount, tick } from "svelte";
import { Sidebar, SidebarGroup, SidebarWrapper } from "flowbite-svelte"; import { Sidebar, SidebarGroup, SidebarWrapper } from "flowbite-svelte";
import { t } from "svelte-i18n"; import { t } from "svelte-i18n";
import { active_session, active_wallet, connections } from "../store"; import { active_session, active_wallet, connections, display_error } from "../store";
import { default as ng } from "../api"; import { default as ng } from "../api";
import DeviceIcon from "../lib/components/DeviceIcon.svelte"; import DeviceIcon from "../lib/components/DeviceIcon.svelte";
@ -343,7 +343,7 @@
{:else} {:else}
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5"> <p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
{@html $t("errors.error_occurred", { {@html $t("errors.error_occurred", {
values: { message: $t("errors." + error) }, values: { message: display_error(error) },
})} })}
</p> </p>
<a use:link href="/"> <a use:link href="/">

@ -0,0 +1,51 @@
<!--
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<!--
Home page to display for logged in users.
Redirects to no-wallet or login page, if not logged in.
-->
<script>
import { onMount, onDestroy } from "svelte";
import {
wallet_import_qrcode
} from "../store";
let tauri_platform = import.meta.env.TAURI_PLATFORM;
let mobile = tauri_platform == "android" || tauri_platform == "ios";
onMount(async () => {
//TODO: here we should also take into account the case of a webapp with camera feature, and sue the lib https://www.npmjs.com/package/html5-qrcode
if (mobile) {
const scanner = await import("@tauri-apps/plugin-barcode-scanner");
let perms = await scanner.requestPermissions();
console.log(perms);
wallet_import_qrcode.set("");
let result = await scanner.scan({ windowed: false, cameraDirection: "back", formats: [scanner.Format.QRCode] })
console.log(result)
wallet_import_qrcode.set(result.content);
window.history.go(-1);
}
});
onDestroy(async () => {
if (mobile) {
const scanner = await import("@tauri-apps/plugin-barcode-scanner");
await scanner.cancel();
}
});
</script>
<div class="text-center">
<!-- please translate this too. i didnt want to do it to avoid a merge conflict-->
Scanning the QRcode
</div>

@ -365,7 +365,7 @@
{:else} {:else}
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5"> <p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
{@html $t("errors.error_occurred", { {@html $t("errors.error_occurred", {
values: { message: $t("errors." + error) }, values: { message: display_error(error) },
})} })}
</p> </p>
<a use:link href="/"> <a use:link href="/">

@ -19,6 +19,7 @@
import { onMount, tick } from "svelte"; import { onMount, tick } from "svelte";
import { default as ng } from "../api"; import { default as ng } from "../api";
import { display_error } from "../store";
const param = new URLSearchParams($querystring); const param = new URLSearchParams($querystring);
@ -79,7 +80,7 @@
{:else} {:else}
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5"> <p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
{@html $t("errors.error_occurred", { {@html $t("errors.error_occurred", {
values: { message: $t("errors." + error) }, values: { message: display_error(error) },
})} })}
</p> </p>
<a use:link href="/"> <a use:link href="/">

@ -44,7 +44,7 @@
} from "../wallet_emojis"; } from "../wallet_emojis";
import { onMount, onDestroy, tick } from "svelte"; import { onMount, onDestroy, tick } from "svelte";
import { wallets, set_active_session, has_wallets } from "../store"; import { wallets, set_active_session, has_wallets, display_error } from "../store";
const param = new URLSearchParams($querystring); const param = new URLSearchParams($querystring);
@ -315,7 +315,7 @@
unsub_register_accepted = undefined; unsub_register_accepted = undefined;
}; };
onDestroy(() => { onDestroy(async () => {
unsub_register(); unsub_register();
}); });
@ -589,7 +589,7 @@
{:else} {:else}
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5"> <p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
{@html $t("errors.error_occurred", { {@html $t("errors.error_occurred", {
values: { message: $t("errors." + registration_error) }, values: { message: display_error(registration_error) },
})} })}
</p> </p>
<a use:link href="/"> <a use:link href="/">
@ -1743,7 +1743,7 @@
/> />
</svg> </svg>
<Alert color="red" class="mt-5"> <Alert color="red" class="mt-5">
{$t("errors." + error)} {display_error(error)}
</Alert> </Alert>
<button <button
class="mt-10 select-none" class="mt-10 select-none"

@ -31,7 +31,7 @@
import { onMount, tick } from "svelte"; import { onMount, tick } from "svelte";
import { Sidebar, SidebarGroup, SidebarWrapper } from "flowbite-svelte"; import { Sidebar, SidebarGroup, SidebarWrapper } from "flowbite-svelte";
import { t } from "svelte-i18n"; import { t } from "svelte-i18n";
import { close_active_wallet, active_session, active_wallet } from "../store"; import { close_active_wallet, active_session, active_wallet, display_error } from "../store";
import { default as ng } from "../api"; import { default as ng } from "../api";
@ -52,6 +52,7 @@
} else { } else {
await scrollToTop(); await scrollToTop();
} }
text_code = await ng.wallet_export_get_textcode($active_session.session_id);
}); });
let downloading = false; let downloading = false;
@ -78,9 +79,12 @@
} }
} }
let text_code;
let wallet_remove_modal_open = false; let wallet_remove_modal_open = false;
function remove_wallet_clicked() { async function remove_wallet_clicked() {
wallet_remove_modal_open = true; //wallet_remove_modal_open = true;
await ng.wallet_export_rendezvous($active_session.session_id, "AABAOAAAAHNb4y7hdWADqFWDgER3J0xvD3K5D9pZ1wd7Bja4c9cWAGLFmUlRYG3D2ULZKhHltZY9IhE2wzBbOqRL-PLw7ZiKAJPyRr_TGnHd-9Uh2Zsv9ahfOWD6tB3q8tVPUS54qdrdAQ");
} }
const close_modal = () => { const close_modal = () => {
@ -209,6 +213,10 @@
</li> </li>
{/if} {/if}
<li class="break-all">
{text_code}
</li>
<!-- Remove Wallet --> <!-- Remove Wallet -->
<li <li
tabindex="0" tabindex="0"
@ -337,7 +345,7 @@
{:else} {:else}
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5"> <p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
{@html $t("errors.error_occurred", { {@html $t("errors.error_occurred", {
values: { message: $t("errors." + error) }, values: { message: display_error(error) },
})} })}
</p> </p>
<a use:link href="/"> <a use:link href="/">

@ -32,6 +32,8 @@
active_session, active_session,
set_active_session, set_active_session,
has_wallets, has_wallets,
wallet_import_qrcode,
display_error,
} from "../store"; } from "../store";
let tauri_platform = import.meta.env.TAURI_PLATFORM; let tauri_platform = import.meta.env.TAURI_PLATFORM;
@ -55,7 +57,10 @@
return imageUrl; return imageUrl;
} }
let qrcode;
onMount(async () => { onMount(async () => {
step = "open"; step = "open";
wallets_unsub = wallets.subscribe((value) => { wallets_unsub = wallets.subscribe((value) => {
wallet = selected && $wallets[selected]?.wallet; wallet = selected && $wallets[selected]?.wallet;
@ -92,6 +97,25 @@
} }
} }
}); });
if ($wallet_import_qrcode) {
let code = $wallet_import_qrcode;
wallet_import_qrcode.set("");
try {
wallet = await ng.wallet_import_from_code(code);
importing = true;
} catch(e) {
error = e;
}
}
// example of rendezvous for desktop and web without cam (please remove it)
qrcode = await ng.wallet_import_rendezvous(300);
try {
wallet = await ng.wallet_import_from_code(qrcode[1]);
importing = true;
} catch (e) {
error = e;
}
}); });
function loggedin() { function loggedin() {
step = "loggedin"; step = "loggedin";
@ -214,7 +238,7 @@
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5"> <p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
{@html $t("errors.error_occurred", { {@html $t("errors.error_occurred", {
values: { message: $t("errors." + error) }, values: { message: display_error(error) },
})} })}
</p> </p>
<button <button
@ -267,11 +291,35 @@
/> />
</div> </div>
{/each} {/each}
<!-- remove all this-->
<div
class="wallet-box"
role="button"
tabindex="0"
>
{#if qrcode}
{@html qrcode[0]}
{/if}
</div>
<div
class="wallet-box break-all"
role="button"
tabindex="0"
>
{#if qrcode}
{qrcode[1]}
{/if}
</div>
<!-- remove until here -->
<div class="wallet-box"> <div class="wallet-box">
{#if $has_wallets}<p class="mt-1"> {#if $has_wallets}
<p class="mt-1">
{$t("pages.wallet_login.with_another_wallet")} {$t("pages.wallet_login.with_another_wallet")}
</p> </p>
{:else}<p class="mt-1">{$t("pages.wallet_login.import_wallet")}</p> {:else}
<p class="mt-1">
{$t("pages.wallet_login.import_wallet")}
</p>
{/if} {/if}
<Fileupload <Fileupload
style="display:none;" style="display:none;"
@ -302,36 +350,36 @@
</svg> </svg>
{$t("pages.wallet_login.import_file")} {$t("pages.wallet_login.import_file")}
</button> </button>
<Button <a href="/wallet/scanqr" use:link>
style="min-width: 250px;justify-content: left;" <button
disabled style="min-width: 250px;justify-content: left;"
class="disabled mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center justify-center dark:focus:ring-primary-100/55 mb-2" class="disabled mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center justify-center dark:focus:ring-primary-100/55 mb-2"
>
<svg
class="w-8 h-8 mr-2 -ml-1"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
> >
<path <svg
stroke-linecap="round" class="w-8 h-8 mr-2 -ml-1"
stroke-linejoin="round" fill="none"
d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 013.75 9.375v-4.5zM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 01-1.125-1.125v-4.5zM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0113.5 9.375v-4.5z" stroke="currentColor"
/> stroke-width="1.5"
<path viewBox="0 0 24 24"
stroke-linecap="round" xmlns="http://www.w3.org/2000/svg"
stroke-linejoin="round" aria-hidden="true"
d="M6.75 6.75h.75v.75h-.75v-.75zM6.75 16.5h.75v.75h-.75v-.75zM16.5 6.75h.75v.75h-.75v-.75zM13.5 13.5h.75v.75h-.75v-.75zM13.5 19.5h.75v.75h-.75v-.75zM19.5 13.5h.75v.75h-.75v-.75zM19.5 19.5h.75v.75h-.75v-.75zM16.5 16.5h.75v.75h-.75v-.75z" >
/> <path
</svg> stroke-linecap="round"
{$t("pages.wallet_login.import_qr")} stroke-linejoin="round"
</Button> d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 013.75 9.375v-4.5zM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 01-1.125-1.125v-4.5zM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0113.5 9.375v-4.5z"
<Button />
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6.75 6.75h.75v.75h-.75v-.75zM6.75 16.5h.75v.75h-.75v-.75zM16.5 6.75h.75v.75h-.75v-.75zM13.5 13.5h.75v.75h-.75v-.75zM13.5 19.5h.75v.75h-.75v-.75zM19.5 13.5h.75v.75h-.75v-.75zM19.5 19.5h.75v.75h-.75v-.75zM16.5 16.5h.75v.75h-.75v-.75z"
/>
</svg>
{$t("pages.wallet_login.import_qr")}
</button>
</a>
<button
style="min-width: 250px;justify-content: left;" style="min-width: 250px;justify-content: left;"
disabled
class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2" class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
> >
<svg <svg
@ -351,7 +399,7 @@
</svg> </svg>
{$t("pages.wallet_login.import_link")} {$t("pages.wallet_login.import_link")}
</Button> </button>
<a href="/wallet/create" use:link> <a href="/wallet/create" use:link>
<button <button
tabindex="-1" tabindex="-1"

@ -15,7 +15,7 @@ import {
get, get,
type Writable, type Writable,
} from "svelte/store"; } from "svelte/store";
import { register, init, locale } from "svelte-i18n"; import { register, init, locale, format } from "svelte-i18n";
import ng from "./api"; import ng from "./api";
import { official_classes } from "./classes"; import { official_classes } from "./classes";
import { official_apps, official_services } from "./zeras"; import { official_apps, official_services } from "./zeras";
@ -43,6 +43,15 @@ init({
initialLocale: "en", initialLocale: "en",
}); });
export const display_error = (error:string) => {
const parts = error.split(":");
let res = get(format)("errors."+parts[0]);
if (parts[1]) {
res += " "+get(format)("errors."+parts[1]);
}
return res;
}
export const select_default_lang = async () => { export const select_default_lang = async () => {
let locales = await ng.locales(); let locales = await ng.locales();
for (let lo of locales) { for (let lo of locales) {
@ -148,6 +157,8 @@ export const cur_tab = writable({
}); });
export const wallet_import_qrcode = writable("");
export const opened_wallets = writable({}); export const opened_wallets = writable({});
/// { wallet:, id: } /// { wallet:, id: }

@ -12,14 +12,15 @@
//! Implementation of the Server Broker //! Implementation of the Server Broker
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{BTreeMap, HashMap, HashSet},
path::PathBuf, path::PathBuf,
sync::Arc, sync::Arc,
time::{Duration, SystemTime},
}; };
use async_std::sync::{Mutex, RwLock}; use async_std::sync::{Mutex, RwLock};
use either::Either; use either::Either;
use futures::StreamExt; use futures::{channel::mpsc, SinkExt, StreamExt};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ng_repo::{ use ng_repo::{
@ -35,6 +36,7 @@ use ng_net::{
connection::NoiseFSM, connection::NoiseFSM,
server_broker::IServerBroker, server_broker::IServerBroker,
types::*, types::*,
utils::{spawn_and_log_error, Receiver, ResultSend, Sender},
}; };
use ng_verifier::{ use ng_verifier::{
@ -147,6 +149,10 @@ pub struct ServerBrokerState {
verifiers: HashMap<UserId, Arc<RwLock<DetachableVerifier>>>, verifiers: HashMap<UserId, Arc<RwLock<DetachableVerifier>>>,
remote_apps: HashMap<(DirectPeerId, u64), UserId>, remote_apps: HashMap<(DirectPeerId, u64), UserId>,
wallet_rendezvous: HashMap<SymKey, Sender<ExportedWallet>>,
wallet_exports: HashMap<SymKey, ExportedWallet>,
wallet_exports_timestamp: BTreeMap<SystemTime, SymKey>,
} }
pub struct ServerBroker { pub struct ServerBroker {
@ -167,6 +173,9 @@ impl ServerBroker {
local_subscriptions: HashMap::new(), local_subscriptions: HashMap::new(),
verifiers: HashMap::new(), verifiers: HashMap::new(),
remote_apps: HashMap::new(), remote_apps: HashMap::new(),
wallet_rendezvous: HashMap::new(),
wallet_exports: HashMap::new(),
wallet_exports_timestamp: BTreeMap::new(),
}), }),
path_users, path_users,
@ -273,10 +282,98 @@ impl ServerBroker {
} }
} }
use async_std::future::timeout;
async fn wait_for_wallet(
mut internal_receiver: Receiver<ExportedWallet>,
mut sender: Sender<Result<ExportedWallet, ServerError>>,
rendezvous: SymKey,
) -> ResultSend<()> {
let wallet_future = internal_receiver.next();
let _ = sender
.send(
match timeout(Duration::from_millis(5 * 60_000), wallet_future).await {
Err(_) => Err(ServerError::ExportWalletTimeOut),
Ok(Some(w)) => Ok(w),
Ok(None) => Err(ServerError::BrokerError),
},
)
.await;
BROKER
.read()
.await
.get_server_broker()?
.read()
.await
.remove_rendezvous(&rendezvous)
.await;
Ok(())
}
//TODO: the purpose of this trait is to have a level of indirection so we can keep some data in memory (cache) and avoid hitting the storage backend (rocksdb) at every call. //TODO: the purpose of this trait is to have a level of indirection so we can keep some data in memory (cache) and avoid hitting the storage backend (rocksdb) at every call.
//for now this cache is not implemented, but the structs are ready (see above), and it would just require to change slightly the implementation of the trait functions here below. //for now this cache is not implemented, but the structs are ready (see above), and it would just require to change slightly the implementation of the trait functions here below.
#[async_trait::async_trait] #[async_trait::async_trait]
impl IServerBroker for ServerBroker { impl IServerBroker for ServerBroker {
async fn remove_rendezvous(&self, rendezvous: &SymKey) {
let mut lock = self.state.write().await;
let _ = lock.wallet_rendezvous.remove(&rendezvous);
}
async fn wait_for_wallet_at_rendezvous(
&self,
rendezvous: SymKey,
) -> Receiver<Result<ExportedWallet, ServerError>> {
let (internal_sender, internal_receiver) = mpsc::unbounded();
let (mut sender, receiver) = mpsc::unbounded();
{
let mut state = self.state.write().await;
if state.wallet_rendezvous.contains_key(&rendezvous) {
let _ = sender.send(Err(ServerError::BrokerError));
sender.close_channel();
return receiver;
} else {
let _ = state
.wallet_rendezvous
.insert(rendezvous.clone(), internal_sender);
}
}
spawn_and_log_error(wait_for_wallet(internal_receiver, sender, rendezvous));
receiver
}
async fn get_wallet_export(&self, rendezvous: SymKey) -> Result<ExportedWallet, ServerError> {
let mut state = self.state.write().await;
match state.wallet_exports.remove(&rendezvous) {
Some(wallet) => Ok(wallet),
None => Err(ServerError::NotFound),
}
}
async fn put_wallet_export(&self, rendezvous: SymKey, export: ExportedWallet) {
let mut state = self.state.write().await;
let _ = state.wallet_exports.insert(rendezvous.clone(), export);
let _ = state
.wallet_exports_timestamp
.insert(SystemTime::now(), rendezvous);
}
// TODO: periodically (every 5 min) remove entries in wallet_exports_timestamp and wallet_exports
async fn put_wallet_at_rendezvous(
&self,
rendezvous: SymKey,
export: ExportedWallet,
) -> Result<(), ServerError> {
let mut state = self.state.write().await;
match state.wallet_rendezvous.remove(&rendezvous) {
None => Err(ServerError::NotFound),
Some(mut sender) => {
let _ = sender.send(export).await;
Ok(())
}
}
}
fn get_block_storage( fn get_block_storage(
&self, &self,
) -> std::sync::Arc<std::sync::RwLock<dyn BlockStorage + Send + Sync>> { ) -> std::sync::Arc<std::sync::RwLock<dyn BlockStorage + Send + Sync>> {

@ -15,3 +15,5 @@ pub mod blocks_put;
pub mod blocks_exist; pub mod blocks_exist;
pub mod blocks_get; pub mod blocks_get;
pub mod wallet_put_export;

@ -0,0 +1,89 @@
/*
* Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
* All rights reserved.
* Licensed under the Apache License, Version 2.0
* <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
* or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
* at your option. All files in the project carrying such
* notice may not be copied, modified, or distributed except
* according to those terms.
*/
use std::sync::Arc;
use async_std::sync::Mutex;
use ng_repo::errors::*;
use ng_repo::log::*;
use ng_repo::types::OverlayId;
use crate::broker::BROKER;
use crate::connection::NoiseFSM;
use crate::types::*;
use crate::{actor::*, types::ProtocolMessage};
impl WalletPutExport {
pub fn get_actor(&self, id: i64) -> Box<dyn EActor> {
Actor::<WalletPutExport, ()>::new_responder(id)
}
}
impl TryFrom<ProtocolMessage> for WalletPutExport {
type Error = ProtocolError;
fn try_from(msg: ProtocolMessage) -> Result<Self, Self::Error> {
let req: ClientRequestContentV0 = msg.try_into()?;
if let ClientRequestContentV0::WalletPutExport(a) = req {
Ok(a)
} else {
log_debug!("INVALID {:?}", req);
Err(ProtocolError::InvalidValue)
}
}
}
impl From<WalletPutExport> for ProtocolMessage {
fn from(msg: WalletPutExport) -> ProtocolMessage {
ProtocolMessage::from_client_request_v0(
ClientRequestContentV0::WalletPutExport(msg),
OverlayId::nil(),
)
}
}
impl Actor<'_, WalletPutExport, ()> {}
#[async_trait::async_trait]
impl EActor for Actor<'_, WalletPutExport, ()> {
async fn respond(
&mut self,
msg: ProtocolMessage,
fsm: Arc<Mutex<NoiseFSM>>,
) -> Result<(), ProtocolError> {
let req = WalletPutExport::try_from(msg)?;
let sb = { BROKER.read().await.get_server_broker()? };
let mut res: Result<(), ServerError> = Ok(());
match req {
WalletPutExport::V0(v0) => {
if v0.is_rendezvous {
res = sb
.read()
.await
.put_wallet_at_rendezvous(v0.rendezvous_id, v0.wallet)
.await;
} else {
sb.read()
.await
.put_wallet_export(v0.rendezvous_id, v0.wallet)
.await;
}
}
}
fsm.lock()
.await
.send_in_reply_to(res.into(), self.id())
.await?;
Ok(())
}
}

@ -0,0 +1,2 @@
pub mod wallet_get_export;
pub use wallet_get_export::*;

@ -0,0 +1,109 @@
/*
* Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
* All rights reserved.
* Licensed under the Apache License, Version 2.0
* <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
* or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
* at your option. All files in the project carrying such
* notice may not be copied, modified, or distributed except
* according to those terms.
*/
use std::sync::Arc;
use async_std::stream::StreamExt;
use async_std::sync::Mutex;
use ng_repo::errors::*;
use ng_repo::log::*;
use super::super::StartProtocol;
use crate::broker::BROKER;
use crate::connection::NoiseFSM;
use crate::types::*;
use crate::{actor::*, types::ProtocolMessage};
impl ExtWalletGetExportV0 {
pub fn get_actor(&self) -> Box<dyn EActor> {
Actor::<ExtWalletGetExportV0, ExportedWallet>::new_responder(0)
}
}
impl TryFrom<ProtocolMessage> for ExtWalletGetExportV0 {
type Error = ProtocolError;
fn try_from(msg: ProtocolMessage) -> Result<Self, Self::Error> {
if let ProtocolMessage::Start(StartProtocol::Ext(ExtRequest::V0(ExtRequestV0 {
content: ExtRequestContentV0::WalletGetExport(a),
..
}))) = msg
{
Ok(a)
} else {
log_debug!("INVALID {:?}", msg);
Err(ProtocolError::InvalidValue)
}
}
}
impl From<ExtWalletGetExportV0> for ProtocolMessage {
fn from(_msg: ExtWalletGetExportV0) -> ProtocolMessage {
unimplemented!();
}
}
impl From<ExtWalletGetExportV0> for ExtRequestContentV0 {
fn from(msg: ExtWalletGetExportV0) -> ExtRequestContentV0 {
ExtRequestContentV0::WalletGetExport(msg)
}
}
impl TryFrom<ProtocolMessage> for ExportedWallet {
type Error = ProtocolError;
fn try_from(msg: ProtocolMessage) -> Result<ExportedWallet, Self::Error> {
let content: ExtResponseContentV0 = msg.try_into()?;
if let ExtResponseContentV0::Wallet(res) = content {
Ok(res)
} else {
Err(ProtocolError::InvalidValue)
}
}
}
impl Actor<'_, ExtWalletGetExportV0, ExportedWallet> {}
#[async_trait::async_trait]
impl EActor for Actor<'_, ExtWalletGetExportV0, ExportedWallet> {
async fn respond(
&mut self,
msg: ProtocolMessage,
fsm: Arc<Mutex<NoiseFSM>>,
) -> Result<(), ProtocolError> {
let req = ExtWalletGetExportV0::try_from(msg)?;
let result = if req.is_rendezvous {
let mut receiver = {
let broker = BROKER.read().await;
let sb = broker.get_server_broker()?;
let lock = sb.read().await;
lock.wait_for_wallet_at_rendezvous(req.id).await
};
match receiver.next().await {
None => Err(ServerError::BrokerError),
Some(Err(e)) => Err(e),
Some(Ok(w)) => Ok(ExtResponseContentV0::Wallet(w)),
}
} else {
{
let broker = BROKER.read().await;
let sb = broker.get_server_broker()?;
let lock = sb.read().await;
lock.get_wallet_export(req.id).await
}
.map(|wallet| ExtResponseContentV0::Wallet(wallet))
};
let response: ExtResponseV0 = result.into();
fsm.lock().await.send(response.into()).await?;
Ok(())
}
}

@ -21,3 +21,5 @@ pub mod client;
pub mod admin; pub mod admin;
pub mod app; pub mod app;
pub mod ext;

@ -23,7 +23,7 @@ use crate::actors::noise::Noise;
use crate::connection::NoiseFSM; use crate::connection::NoiseFSM;
use crate::types::{ use crate::types::{
AdminRequest, ClientInfo, CoreBrokerConnect, CoreBrokerConnectResponse, CoreMessage, AdminRequest, ClientInfo, CoreBrokerConnect, CoreBrokerConnectResponse, CoreMessage,
CoreMessageV0, CoreResponse, CoreResponseContentV0, CoreResponseV0, ExtResponse, CoreMessageV0, CoreResponse, CoreResponseContentV0, CoreResponseV0, ExtRequest,
}; };
use crate::{actor::*, types::ProtocolMessage}; use crate::{actor::*, types::ProtocolMessage};
@ -34,7 +34,7 @@ use crate::{actor::*, types::ProtocolMessage};
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum StartProtocol { pub enum StartProtocol {
Client(ClientHello), Client(ClientHello),
Ext(ExtHello), Ext(ExtRequest),
Core(CoreHello), Core(CoreHello),
Admin(AdminRequest), Admin(AdminRequest),
App(AppHello), App(AppHello),
@ -136,28 +136,28 @@ impl EActor for Actor<'_, CoreBrokerConnect, CoreBrokerConnectResponse> {
} }
} }
/// External Hello (finalizes the Noise handshake and sends first ExtRequest) // /// External Hello (finalizes the Noise handshake and sends first ExtRequest)
#[derive(Clone, Debug, Serialize, Deserialize)] // #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExtHello { // pub struct ExtHello {
// contains the 3rd Noise handshake message "s,se" // // contains the 3rd Noise handshake message "s,se"
pub noise: Noise, // pub noise: Noise,
/// Noise encrypted payload (an ExtRequest) // /// Noise encrypted payload (an ExtRequest)
#[serde(with = "serde_bytes")] // #[serde(with = "serde_bytes")]
pub payload: Vec<u8>, // pub payload: Vec<u8>,
} // }
impl ExtHello { // impl ExtHello {
pub fn get_actor(&self) -> Box<dyn EActor> { // pub fn get_actor(&self) -> Box<dyn EActor> {
Actor::<ExtHello, ExtResponse>::new_responder(0) // Actor::<ExtHello, ExtResponse>::new_responder(0)
} // }
} // }
impl From<ExtHello> for ProtocolMessage { // impl From<ExtHello> for ProtocolMessage {
fn from(msg: ExtHello) -> ProtocolMessage { // fn from(msg: ExtHello) -> ProtocolMessage {
ProtocolMessage::Start(StartProtocol::Ext(msg)) // ProtocolMessage::Start(StartProtocol::Ext(msg))
} // }
} // }
/// Client Hello /// Client Hello
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
@ -251,18 +251,18 @@ impl EActor for Actor<'_, ClientHello, ServerHello> {
} }
} }
impl Actor<'_, ExtHello, ExtResponse> {} // impl Actor<'_, ExtHello, ExtResponse> {}
#[async_trait::async_trait] // #[async_trait::async_trait]
impl EActor for Actor<'_, ExtHello, ExtResponse> { // impl EActor for Actor<'_, ExtHello, ExtResponse> {
async fn respond( // async fn respond(
&mut self, // &mut self,
_msg: ProtocolMessage, // _msg: ProtocolMessage,
_fsm: Arc<Mutex<NoiseFSM>>, // _fsm: Arc<Mutex<NoiseFSM>>,
) -> Result<(), ProtocolError> { // ) -> Result<(), ProtocolError> {
Ok(()) // Ok(())
} // }
} // }
// ///////////// APP HELLO /////////////// // ///////////// APP HELLO ///////////////

@ -894,6 +894,34 @@ impl Broker {
connection.admin::<A>().await connection.admin::<A>().await
} }
pub async fn ext<
A: Into<ProtocolMessage> + Into<ExtRequestContentV0> + std::fmt::Debug + Sync + Send + 'static,
B: TryFrom<ProtocolMessage, Error = ProtocolError> + std::fmt::Debug + Sync + Send + 'static,
>(
cnx: Box<dyn IConnect>,
peer_privk: PrivKey,
peer_pubk: PubKey,
remote_peer_id: DirectPeerId,
url: String,
request: A,
) -> Result<B, NgError> {
let config = StartConfig::Ext(ExtConfig {
url,
request: request.into(),
});
let remote_peer_id_dh = remote_peer_id.to_dh_from_ed();
let mut connection = cnx
.open(
config.get_url(),
peer_privk.clone(),
peer_pubk,
remote_peer_id_dh,
config.clone(),
)
.await?;
connection.ext::<A, B>().await
}
#[doc(hidden)] #[doc(hidden)]
pub fn connect_local(&mut self, peer_pubk: PubKey, user: UserId) -> Result<(), ProtocolError> { pub fn connect_local(&mut self, peer_pubk: PubKey, user: UserId) -> Result<(), ProtocolError> {
if self.closing { if self.closing {

@ -112,7 +112,7 @@ pub enum FSMstate {
Noise3, Noise3,
AdminRequest, AdminRequest,
ExtRequest, ExtRequest,
ExtResponse, //ExtResponse,
ClientHello, ClientHello,
ServerHello, ServerHello,
ClientAuth, ClientAuth,
@ -177,7 +177,10 @@ pub struct ClientConfig {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ExtConfig {} pub struct ExtConfig {
pub url: String,
pub request: ExtRequestContentV0,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CoreConfig { pub struct CoreConfig {
@ -215,10 +218,14 @@ pub enum StartConfig {
impl StartConfig { impl StartConfig {
pub fn get_url(&self) -> String { pub fn get_url(&self) -> String {
match self { match self {
Self::Client(config) => config.url.clone(), Self::Client(ClientConfig { url, .. }) | Self::Ext(ExtConfig { url, .. }) => {
Self::Admin(config) => format!("ws://{}:{}", config.addr.ip, config.addr.port), url.clone()
Self::Core(config) => format!("ws://{}:{}", config.addr.ip, config.addr.port), }
Self::App(config) => format!("ws://{}:{}", config.addr.ip, config.addr.port), Self::Admin(AdminConfig { addr, .. })
| Self::Core(CoreConfig { addr, .. })
| Self::App(AppConfig { addr, .. }) => {
format!("ws://{}:{}", addr.ip, addr.port)
}
_ => unimplemented!(), _ => unimplemented!(),
} }
} }
@ -235,9 +242,9 @@ impl StartConfig {
_ => false, _ => false,
} }
} }
pub fn is_admin(&self) -> bool { pub fn is_oneshot(&self) -> bool {
match self { match self {
StartConfig::Admin(_) => true, StartConfig::Admin(_) | StartConfig::Ext(_) => true,
_ => false, _ => false,
} }
} }
@ -635,7 +642,10 @@ impl NoiseFSM {
self.state = FSMstate::ClientHello; self.state = FSMstate::ClientHello;
} }
StartConfig::Ext(_ext_config) => { StartConfig::Ext(_ext_config) => {
todo!(); let noise = Noise::V0(NoiseV0 { data: payload });
self.send(noise.into()).await?;
self.state = FSMstate::Noise3;
next_step = StepReply::ReEnter;
} }
StartConfig::Core(_core_config) => { StartConfig::Core(_core_config) => {
todo!(); todo!();
@ -774,8 +784,18 @@ impl NoiseFSM {
StartConfig::Client(_) => { StartConfig::Client(_) => {
return Err(ProtocolError::InvalidState); return Err(ProtocolError::InvalidState);
} }
StartConfig::Ext(_ext_config) => { StartConfig::Ext(ext_config) => {
todo!(); let ext_req = ExtRequestV0 {
content: ext_config.request.clone(),
id: 0,
overlay: None,
};
let protocol_start = StartProtocol::Ext(ExtRequest::V0(ext_req));
self.send(protocol_start.into()).await?;
self.state = FSMstate::ExtRequest;
return Ok(StepReply::NONE);
} }
StartConfig::Core(_core_config) => { StartConfig::Core(_core_config) => {
todo!(); todo!();
@ -807,8 +827,9 @@ impl NoiseFSM {
StartProtocol::Client(_) => { StartProtocol::Client(_) => {
return Err(ProtocolError::InvalidState); return Err(ProtocolError::InvalidState);
} }
StartProtocol::Ext(_ext_config) => { StartProtocol::Ext(_ext_req) => {
todo!(); self.state = FSMstate::Closing;
return Ok(StepReply::Responder(msg_opt.unwrap()));
} }
// StartProtocol::Core(core_config) => { // StartProtocol::Core(core_config) => {
// todo!(); // todo!();
@ -852,8 +873,15 @@ impl NoiseFSM {
return Ok(StepReply::Response(msg)); return Ok(StepReply::Response(msg));
} }
} }
FSMstate::ExtRequest => {} FSMstate::ExtRequest => {
FSMstate::ExtResponse => {} // CLIENT side receiving ExtResponse
if let Some(msg) = msg_opt {
if self.dir.is_server() || msg.type_id() != TypeId::of::<ExtResponse>() {
return Err(ProtocolError::InvalidState);
}
return Ok(StepReply::Response(msg));
}
}
FSMstate::ClientHello => { FSMstate::ClientHello => {
if let Some(msg) = msg_opt.as_ref() { if let Some(msg) = msg_opt.as_ref() {
if !self.dir.is_server() { if !self.dir.is_server() {
@ -1371,6 +1399,47 @@ impl ConnectionBase {
} }
} }
pub async fn ext<
A: Into<ProtocolMessage> + Into<ExtRequestContentV0> + std::fmt::Debug + Sync + Send + 'static,
B: TryFrom<ProtocolMessage, Error = ProtocolError> + std::fmt::Debug + Sync + Send + 'static,
>(
&mut self,
) -> Result<B, NgError> {
if !self.dir.is_server() {
let mut actor = Box::new(Actor::<A, B>::new(0, true));
self.actors.lock().await.insert(0, actor.get_receiver_tx());
let mut receiver = actor.detach_receiver();
match receiver.next().await {
Some(ConnectionCommand::Msg(msg)) => {
self.fsm
.as_ref()
.unwrap()
.lock()
.await
.remove_actor(0)
.await;
let server_error: Result<ServerError, NgError> = (&msg).try_into();
let response: B = match msg.try_into() {
Ok(b) => b,
Err(ProtocolError::ServerError) => {
return Err(NgError::ServerError(server_error?));
}
Err(e) => return Err(NgError::ProtocolError(e)),
};
self.close().await;
Ok(response)
}
Some(ConnectionCommand::ProtocolError(e)) => Err(e.into()),
Some(ConnectionCommand::Error(e)) => Err(ProtocolError::from(e).into()),
Some(ConnectionCommand::Close) => Err(ProtocolError::Closing.into()),
_ => Err(ProtocolError::ActorError.into()),
}
} else {
panic!("cannot call ext on a server-side connection");
}
}
pub async fn probe(&mut self) -> Result<Option<PubKey>, ProtocolError> { pub async fn probe(&mut self) -> Result<Option<PubKey>, ProtocolError> {
if !self.dir.is_server() { if !self.dir.is_server() {
let config = StartConfig::Probe; let config = StartConfig::Probe;
@ -1431,7 +1500,7 @@ impl ConnectionBase {
pub async fn start(&mut self, config: StartConfig) -> Result<(), ProtocolError> { pub async fn start(&mut self, config: StartConfig) -> Result<(), ProtocolError> {
// BOOTSTRAP the protocol from client-side // BOOTSTRAP the protocol from client-side
if !self.dir.is_server() { if !self.dir.is_server() {
let is_admin = config.is_admin(); let is_oneshot = config.is_oneshot();
let res; let res;
{ {
let mut fsm = self.fsm.as_ref().unwrap().lock().await; let mut fsm = self.fsm.as_ref().unwrap().lock().await;
@ -1442,7 +1511,7 @@ impl ConnectionBase {
self.send(ConnectionCommand::ProtocolError(err.clone())) self.send(ConnectionCommand::ProtocolError(err.clone()))
.await; .await;
Err(err) Err(err)
} else if !is_admin { } else if !is_oneshot {
let mut actor = Box::new(Actor::<Connecting, ()>::new(0, true)); let mut actor = Box::new(Actor::<Connecting, ()>::new(0, true));
self.actors.lock().await.insert(0, actor.get_receiver_tx()); self.actors.lock().await.insert(0, actor.get_receiver_tx());

@ -24,9 +24,22 @@ use crate::app_protocol::{AppRequest, AppSessionStart, AppSessionStartResponse,
use crate::broker::ClientPeerId; use crate::broker::ClientPeerId;
use crate::connection::NoiseFSM; use crate::connection::NoiseFSM;
use crate::types::*; use crate::types::*;
use crate::utils::Receiver;
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait IServerBroker: Send + Sync { pub trait IServerBroker: Send + Sync {
async fn remove_rendezvous(&self, rendezvous: &SymKey);
async fn put_wallet_export(&self, rendezvous: SymKey, export: ExportedWallet);
async fn get_wallet_export(&self, rendezvous: SymKey) -> Result<ExportedWallet, ServerError>;
async fn put_wallet_at_rendezvous(
&self,
rendezvous: SymKey,
export: ExportedWallet,
) -> Result<(), ServerError>;
async fn wait_for_wallet_at_rendezvous(
&self,
rendezvous: SymKey,
) -> Receiver<Result<ExportedWallet, ServerError>>;
fn get_path_users(&self) -> PathBuf; fn get_path_users(&self) -> PathBuf;
fn get_block_storage(&self) -> Arc<std::sync::RwLock<dyn BlockStorage + Send + Sync>>; fn get_block_storage(&self) -> Arc<std::sync::RwLock<dyn BlockStorage + Send + Sync>>;
fn put_block(&self, overlay_id: &OverlayId, block: Block) -> Result<(), ServerError>; fn put_block(&self, overlay_id: &OverlayId, block: Block) -> Result<(), ServerError>;

@ -3249,6 +3249,20 @@ impl CommitGet {
} }
} }
/// Request to store one or more blocks
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct WalletPutExportV0 {
pub wallet: ExportedWallet,
pub rendezvous_id: SymKey,
pub is_rendezvous: bool,
}
/// Request to store one or more blocks
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum WalletPutExport {
V0(WalletPutExportV0),
}
/// Request to store one or more blocks /// Request to store one or more blocks
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BlocksPutV0 { pub struct BlocksPutV0 {
@ -3414,6 +3428,8 @@ pub enum ClientRequestContentV0 {
// For InnerOverlay's only : // For InnerOverlay's only :
BlocksPut(BlocksPut), BlocksPut(BlocksPut),
PublishEvent(PublishEvent), PublishEvent(PublishEvent),
WalletPutExport(WalletPutExport),
} }
impl ClientRequestContentV0 { impl ClientRequestContentV0 {
@ -3428,6 +3444,7 @@ impl ClientRequestContentV0 {
ClientRequestContentV0::BlocksPut(a) => a.set_overlay(overlay), ClientRequestContentV0::BlocksPut(a) => a.set_overlay(overlay),
ClientRequestContentV0::BlocksExist(a) => a.set_overlay(overlay), ClientRequestContentV0::BlocksExist(a) => a.set_overlay(overlay),
ClientRequestContentV0::BlocksGet(a) => a.set_overlay(overlay), ClientRequestContentV0::BlocksGet(a) => a.set_overlay(overlay),
ClientRequestContentV0::WalletPutExport(_a) => {}
_ => unimplemented!(), _ => unimplemented!(),
} }
} }
@ -3479,6 +3496,7 @@ impl ClientRequest {
ClientRequestContentV0::BlocksPut(r) => r.get_actor(self.id()), ClientRequestContentV0::BlocksPut(r) => r.get_actor(self.id()),
ClientRequestContentV0::BlocksExist(r) => r.get_actor(self.id()), ClientRequestContentV0::BlocksExist(r) => r.get_actor(self.id()),
ClientRequestContentV0::BlocksGet(r) => r.get_actor(self.id()), ClientRequestContentV0::BlocksGet(r) => r.get_actor(self.id()),
ClientRequestContentV0::WalletPutExport(r) => r.get_actor(self.id()),
_ => unimplemented!(), _ => unimplemented!(),
}, },
} }
@ -3946,23 +3964,41 @@ pub enum ExtObjectGet {
V0(ExtObjectGetV0), V0(ExtObjectGetV0),
} }
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExtWalletGetExportV0 {
pub id: SymKey,
pub is_rendezvous: bool,
}
/// Topic synchronization request /// Topic synchronization request
pub type ExtTopicSyncReq = TopicSyncReq; pub type ExtTopicSyncReq = TopicSyncReq;
/// Content of ExtRequestV0 /// Content of ExtRequestV0
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ExtRequestContentV0 { pub enum ExtRequestContentV0 {
WalletGetExport(ExtWalletGetExportV0),
ExtObjectGet(ExtObjectGet), ExtObjectGet(ExtObjectGet),
ExtTopicSyncReq(ExtTopicSyncReq), ExtTopicSyncReq(ExtTopicSyncReq),
// TODO inbox requests // TODO inbox requests
// TODO subreq ? // TODO subreq ?
} }
impl ExtRequestContentV0 {
pub fn get_actor(&self) -> Box<dyn EActor> {
match self {
Self::WalletGetExport(a) => a.get_actor(),
_ => unimplemented!()
// Self::ExtObjectGet(a) => a.get_actor(),
// Self::ExtTopicSyncReq(a) => a.get_actor(),
}
}
}
/// External request with its request ID /// External request with its request ID
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExtRequestV0 { pub struct ExtRequestV0 {
/// outer overlayId /// outer overlayId
pub overlay: Digest, pub overlay: Option<Digest>,
/// Request ID /// Request ID
pub id: i64, pub id: i64,
@ -3993,16 +4029,48 @@ impl ExtRequest {
} }
} }
} }
pub fn get_actor(&self) -> Box<dyn EActor> {
match self {
Self::V0(a) => a.content.get_actor(),
}
}
} }
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExportedWallet(pub serde_bytes::ByteBuf);
/// Content of ExtResponseV0 /// Content of ExtResponseV0
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ExtResponseContentV0 { pub enum ExtResponseContentV0 {
EmptyResponse,
Block(Block), Block(Block),
Wallet(ExportedWallet),
// TODO inbox related replies // TODO inbox related replies
// TODO event ? // TODO event ?
} }
impl TryFrom<ProtocolMessage> for ExtResponseContentV0 {
type Error = ProtocolError;
fn try_from(msg: ProtocolMessage) -> Result<Self, Self::Error> {
if let ProtocolMessage::ExtResponse(ExtResponse::V0(ExtResponseV0 {
content,
result,
..
})) = msg
{
let err = ServerError::try_from(result).unwrap();
if !err.is_err() {
Ok(content)
} else {
Err(ProtocolError::ServerError)
}
} else {
log_debug!("INVALID {:?}", msg);
Err(ProtocolError::InvalidValue)
}
}
}
/// Response to an ExtRequest /// Response to an ExtRequest
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExtResponseV0 { pub struct ExtResponseV0 {
@ -4013,7 +4081,7 @@ pub struct ExtResponseV0 {
pub result: u16, pub result: u16,
/// Response content /// Response content
pub content: Option<ExtResponseContentV0>, pub content: ExtResponseContentV0,
} }
/// Response to an ExtRequest /// Response to an ExtRequest
@ -4035,6 +4103,16 @@ impl ExtResponse {
} }
} }
} }
pub fn result(&self) -> u16 {
match self {
Self::V0(o) => o.result,
}
}
pub fn content_v0(&self) -> ExtResponseContentV0 {
match self {
Self::V0(o) => o.content.clone(),
}
}
} }
impl TryFrom<ProtocolMessage> for ExtResponse { impl TryFrom<ProtocolMessage> for ExtResponse {
@ -4048,6 +4126,29 @@ impl TryFrom<ProtocolMessage> for ExtResponse {
} }
} }
impl From<Result<ExtResponseContentV0, ServerError>> for ExtResponseV0 {
fn from(res: Result<ExtResponseContentV0, ServerError>) -> ExtResponseV0 {
match res {
Err(e) => ExtResponseV0 {
id: 0,
result: e.into(),
content: ExtResponseContentV0::EmptyResponse,
},
Ok(content) => ExtResponseV0 {
id: 0,
result: 0,
content,
},
}
}
}
impl From<ExtResponseV0> for ProtocolMessage {
fn from(msg: ExtResponseV0) -> ProtocolMessage {
ProtocolMessage::ExtResponse(ExtResponse::V0(msg))
}
}
// //
// PROTOCOL MESSAGES // PROTOCOL MESSAGES
// //
@ -4149,6 +4250,12 @@ impl TryFrom<&ProtocolMessage> for ServerError {
return Ok(ServerError::try_from(res).unwrap()); return Ok(ServerError::try_from(res).unwrap());
} }
} }
if let ProtocolMessage::ExtResponse(ref bm) = msg {
let res = bm.result();
if res != 0 {
return Ok(ServerError::try_from(res).unwrap());
}
}
if let ProtocolMessage::AppMessage(ref bm) = msg { if let ProtocolMessage::AppMessage(ref bm) = msg {
let res = bm.result(); let res = bm.result();
if res != 0 { if res != 0 {
@ -4205,12 +4312,6 @@ impl ProtocolMessage {
match self { match self {
ProtocolMessage::ClientMessage(s) => Some(s as &dyn IStreamable), ProtocolMessage::ClientMessage(s) => Some(s as &dyn IStreamable),
ProtocolMessage::AppMessage(s) => Some(s as &dyn IStreamable), ProtocolMessage::AppMessage(s) => Some(s as &dyn IStreamable),
// ProtocolMessage::ServerHello(a) => a.get_actor(),
// ProtocolMessage::ClientAuth(a) => a.get_actor(),
// ProtocolMessage::AuthResult(a) => a.get_actor(),
// ProtocolMessage::ExtRequest(a) => a.get_actor(),
// ProtocolMessage::ExtResponse(a) => a.get_actor(),
// ProtocolMessage::BrokerMessage(a) => a.get_actor(),
_ => None, _ => None,
} }
} }
@ -4224,8 +4325,8 @@ impl ProtocolMessage {
// ProtocolMessage::ServerHello(a) => a.get_actor(), // ProtocolMessage::ServerHello(a) => a.get_actor(),
// ProtocolMessage::ClientAuth(a) => a.get_actor(), // ProtocolMessage::ClientAuth(a) => a.get_actor(),
// ProtocolMessage::AuthResult(a) => a.get_actor(), // ProtocolMessage::AuthResult(a) => a.get_actor(),
// ProtocolMessage::ExtRequest(a) => a.get_actor(), //ProtocolMessage::ExtRequest(a) => a.get_actor(),
// ProtocolMessage::ExtResponse(a) => a.get_actor(), //ProtocolMessage::ExtResponse(a) => a.get_actor(),
// ProtocolMessage::BrokerMessage(a) => a.get_actor(), // ProtocolMessage::BrokerMessage(a) => a.get_actor(),
_ => unimplemented!(), _ => unimplemented!(),
} }

@ -17,6 +17,7 @@ use num_enum::TryFromPrimitive;
pub use crate::commit::{CommitLoadError, CommitVerifyError}; pub use crate::commit::{CommitLoadError, CommitVerifyError};
use crate::file::FileError; use crate::file::FileError;
use crate::log::*;
use crate::object::Object; use crate::object::Object;
use crate::types::BlockId; use crate::types::BlockId;
@ -83,6 +84,9 @@ pub enum NgError {
LocalBrokerIsNotHeadless, LocalBrokerIsNotHeadless,
InvalidNuri, InvalidNuri,
InvalidTarget, InvalidTarget,
InvalidQrCode,
NotImplemented,
NotARendezVous,
} }
impl Error for NgError {} impl Error for NgError {}
@ -90,24 +94,24 @@ impl Error for NgError {}
impl fmt::Display for NgError { impl fmt::Display for NgError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::WalletError(string) => write!(f, "WalletError: {}", string), Self::WalletError(string) => write!(f, "WalletError:{}", string),
Self::JsStorageWriteError(string) => write!(f, "JsStorageWriteError: {}", string), Self::JsStorageWriteError(string) => write!(f, "JsStorageWriteError:{}", string),
Self::CommitVerifyError(commit_verify_error) => { Self::CommitVerifyError(commit_verify_error) => {
write!(f, "CommitVerifyError: {:?}", commit_verify_error) write!(f, "CommitVerifyError:{:?}", commit_verify_error)
} }
Self::ProtocolError(error) => write!(f, "ProtocolError: {:?}", error), Self::ProtocolError(error) => write!(f, "ProtocolError:{:?}", error),
Self::ServerError(error) => write!(f, "ServerError: {:?}", error), Self::ServerError(error) => write!(f, "ServerError:{:?}", error),
Self::VerifierError(error) => write!(f, "VerifierError: {:?}", error), Self::VerifierError(error) => write!(f, "VerifierError:{:?}", error),
Self::CommitLoadError(commit_load_error) => { Self::CommitLoadError(commit_load_error) => {
write!(f, "CommitLoadError: {:?}", commit_load_error) write!(f, "CommitLoadError:{:?}", commit_load_error)
} }
Self::BootstrapError(error) => { Self::BootstrapError(error) => {
write!(f, "BootstrapError: {:?}", error) write!(f, "BootstrapError:{:?}", error)
} }
Self::ObjectParseError(error) => write!(f, "ObjectParseError: {:?}", error), Self::ObjectParseError(error) => write!(f, "ObjectParseError:{:?}", error),
Self::StorageError(storage_error) => write!(f, "StorageError: {:?}", storage_error), Self::StorageError(storage_error) => write!(f, "StorageError:{:?}", storage_error),
Self::BrokerConfigErrorStr(s) => write!(f, "BrokerConfigError: {s}"), Self::BrokerConfigErrorStr(s) => write!(f, "BrokerConfigError:{s}"),
Self::BrokerConfigError(s) => write!(f, "BrokerConfigError: {s}"), Self::BrokerConfigError(s) => write!(f, "BrokerConfigError:{s}"),
_ => write!(f, "{:?}", self), _ => write!(f, "{:?}", self),
} }
} }
@ -264,6 +268,8 @@ pub enum ServerError {
OxiGraphError, OxiGraphError,
InvalidNuri, InvalidNuri,
InvalidTarget, InvalidTarget,
ExportWalletTimeOut,
NetError,
} }
impl From<StorageError> for ServerError { impl From<StorageError> for ServerError {
@ -275,12 +281,23 @@ impl From<StorageError> for ServerError {
} }
} }
impl From<NetError> for ServerError {
fn from(e: NetError) -> Self {
match e {
_ => ServerError::NetError,
}
}
}
impl From<ProtocolError> for ServerError { impl From<ProtocolError> for ServerError {
fn from(e: ProtocolError) -> Self { fn from(e: ProtocolError) -> Self {
match e { match e {
ProtocolError::NotFound => ServerError::NotFound, ProtocolError::NotFound => ServerError::NotFound,
ProtocolError::BrokerError => ServerError::BrokerError, ProtocolError::BrokerError => ServerError::BrokerError,
_ => ServerError::ProtocolError, _ => {
log_err!("{:?}", e);
ServerError::ProtocolError
}
} }
} }
} }

@ -535,6 +535,81 @@ pub async fn wallet_read_file(file: JsValue) -> Result<JsValue, String> {
Ok(serde_wasm_bindgen::to_value(&wallet).unwrap()) Ok(serde_wasm_bindgen::to_value(&wallet).unwrap())
} }
#[wasm_bindgen]
pub async fn wallet_import_from_code(code: JsValue) -> Result<JsValue, String> {
init_local_broker_with_lazy(&INIT_LOCAL_BROKER).await;
let code = serde_wasm_bindgen::from_value::<String>(code)
.map_err(|_| "Deserialization error of code".to_string())?;
let wallet = nextgraph::local_broker::wallet_import_from_code(code)
.await
.map_err(|e: NgError| e.to_string())?;
Ok(serde_wasm_bindgen::to_value(&wallet).unwrap())
}
#[wasm_bindgen]
pub async fn wallet_import_rendezvous(size: JsValue) -> Result<JsValue, String> {
init_local_broker_with_lazy(&INIT_LOCAL_BROKER).await;
let size: u32 = serde_wasm_bindgen::from_value::<u32>(size)
.map_err(|_| "Deserialization error of size".to_string())?;
let res = nextgraph::local_broker::wallet_import_rendezvous(size)
.await
.map_err(|e: NgError| e.to_string())?;
Ok(serde_wasm_bindgen::to_value(&res).unwrap())
}
#[wasm_bindgen]
pub async fn wallet_export_get_qrcode(
session_id: JsValue,
size: JsValue,
) -> Result<JsValue, String> {
let session_id: u64 = serde_wasm_bindgen::from_value::<u64>(session_id)
.map_err(|_| "Deserialization error of session_id".to_string())?;
let size: u32 = serde_wasm_bindgen::from_value::<u32>(size)
.map_err(|_| "Deserialization error of size".to_string())?;
init_local_broker_with_lazy(&INIT_LOCAL_BROKER).await;
let res = nextgraph::local_broker::wallet_export_get_qrcode(session_id, size)
.await
.map_err(|e: NgError| e.to_string())?;
Ok(serde_wasm_bindgen::to_value(&res).unwrap())
}
#[wasm_bindgen]
pub async fn wallet_export_get_textcode(session_id: JsValue) -> Result<JsValue, String> {
let session_id: u64 = serde_wasm_bindgen::from_value::<u64>(session_id)
.map_err(|_| "Deserialization error of session_id".to_string())?;
init_local_broker_with_lazy(&INIT_LOCAL_BROKER).await;
let res = nextgraph::local_broker::wallet_export_get_textcode(session_id)
.await
.map_err(|e: NgError| e.to_string())?;
Ok(serde_wasm_bindgen::to_value(&res).unwrap())
}
#[wasm_bindgen]
pub async fn wallet_export_rendezvous(session_id: JsValue, code: JsValue) -> Result<(), String> {
let session_id: u64 = serde_wasm_bindgen::from_value::<u64>(session_id)
.map_err(|_| "Deserialization error of session_id".to_string())?;
let code = serde_wasm_bindgen::from_value::<String>(code)
.map_err(|_| "Deserialization error of code".to_string())?;
init_local_broker_with_lazy(&INIT_LOCAL_BROKER).await;
nextgraph::local_broker::wallet_export_rendezvous(session_id, code)
.await
.map_err(|e: NgError| e.to_string())?;
Ok(())
}
#[wasm_bindgen] #[wasm_bindgen]
pub async fn wallet_was_opened( pub async fn wallet_was_opened(
opened_wallet: JsValue, //SensitiveWallet opened_wallet: JsValue, //SensitiveWallet

@ -813,6 +813,26 @@ impl Verifier {
} }
} }
pub async fn client_request<
A: Into<ProtocolMessage> + std::fmt::Debug + Sync + Send + 'static,
B: TryFrom<ProtocolMessage, Error = ProtocolError> + std::fmt::Debug + Sync + Send + 'static,
>(
&self,
msg: A,
) -> Result<SoS<B>, NgError> {
if self.connected_broker.is_some() {
let connected_broker = self.connected_broker.clone();
let broker = BROKER.read().await;
let user = self.user_id().clone();
broker
.request::<A, B>(&Some(user), &connected_broker.into(), msg)
.await
} else {
Err(NgError::NotConnected)
}
}
async fn send_or_save_event_to_outbox<'a>( async fn send_or_save_event_to_outbox<'a>(
&'a mut self, &'a mut self,
commit_ref: ObjectRef, commit_ref: ObjectRef,

@ -25,6 +25,7 @@ rand = { version = "0.7", features = ["getrandom"] }
aes-gcm-siv = {version = "0.11.1", features = ["aes","heapless","getrandom","std"] } aes-gcm-siv = {version = "0.11.1", features = ["aes","heapless","getrandom","std"] }
zeroize = { version = "1.7.0", features = ["zeroize_derive"] } zeroize = { version = "1.7.0", features = ["zeroize_derive"] }
crypto_box = { version = "0.8.2", features = ["seal"] } crypto_box = { version = "0.8.2", features = ["seal"] }
base64-url = "2.0.0"
blake3 = "1.3.1" blake3 = "1.3.1"
argon2 = "0.5.0" argon2 = "0.5.0"
chacha20poly1305 = "0.10.1" chacha20poly1305 = "0.10.1"

@ -1426,3 +1426,27 @@ pub struct ShuffledPazzle {
pub category_indices: Vec<u8>, pub category_indices: Vec<u8>,
pub emoji_indices: Vec<Vec<u8>>, pub emoji_indices: Vec<Vec<u8>>,
} }
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NgQRCodeV0 {
pub broker: BrokerServerV0,
pub rendezvous: SymKey, // Rendez-vous ID
pub secret_key: SymKey,
pub is_rendezvous: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum NgQRCode {
V0(NgQRCodeV0),
}
impl NgQRCode {
pub fn from_code(code: String) -> Result<Self, NgError> {
let decoded = base64_url::decode(&code).map_err(|_| NgError::SerializationError)?;
Ok(serde_bare::from_slice(&decoded)?)
}
pub fn to_code(&self) -> String {
let ser = serde_bare::to_vec(self).unwrap();
base64_url::encode(&ser)
}
}

@ -16,6 +16,7 @@ importers:
'@sveltejs/vite-plugin-svelte': ^2.0.0 '@sveltejs/vite-plugin-svelte': ^2.0.0
'@tauri-apps/api': 2.0.0-alpha.8 '@tauri-apps/api': 2.0.0-alpha.8
'@tauri-apps/cli': 2.0.0-alpha.14 '@tauri-apps/cli': 2.0.0-alpha.14
'@tauri-apps/plugin-barcode-scanner': 2.0.0-alpha.0
'@tauri-apps/plugin-window': 2.0.0-alpha.1 '@tauri-apps/plugin-window': 2.0.0-alpha.1
'@tsconfig/svelte': ^3.0.0 '@tsconfig/svelte': ^3.0.0
'@types/node': ^18.7.10 '@types/node': ^18.7.10
@ -50,6 +51,7 @@ importers:
dependencies: dependencies:
'@popperjs/core': 2.11.8 '@popperjs/core': 2.11.8
'@tauri-apps/api': 2.0.0-alpha.8 '@tauri-apps/api': 2.0.0-alpha.8
'@tauri-apps/plugin-barcode-scanner': 2.0.0-alpha.0
'@tauri-apps/plugin-window': 2.0.0-alpha.1 '@tauri-apps/plugin-window': 2.0.0-alpha.1
async-proxy: 0.4.1 async-proxy: 0.4.1
classnames: 2.3.2 classnames: 2.3.2
@ -892,6 +894,12 @@ packages:
'@tauri-apps/cli-win32-x64-msvc': 2.0.0-alpha.14 '@tauri-apps/cli-win32-x64-msvc': 2.0.0-alpha.14
dev: true dev: true
/@tauri-apps/plugin-barcode-scanner/2.0.0-alpha.0:
resolution: {integrity: sha512-qXc/HfGdWI2x2vUEfgf65kb4Bw3PEDMLz6tNizLdzQ5Q4wJLMkaPZxFsqS6ZbuRjILAqM0lfp/otgR93OGVOgA==}
dependencies:
'@tauri-apps/api': 2.0.0-alpha.8
dev: false
/@tauri-apps/plugin-window/2.0.0-alpha.1: /@tauri-apps/plugin-window/2.0.0-alpha.1:
resolution: {integrity: sha512-dFOAgal/3Txz3SQ+LNQq0AK1EPC+acdaFlwPVB/6KXUZYmaFleIlzgxDVoJCQ+/xOhxvYrdQaFLefh0I/Kldbg==} resolution: {integrity: sha512-dFOAgal/3Txz3SQ+LNQq0AK1EPC+acdaFlwPVB/6KXUZYmaFleIlzgxDVoJCQ+/xOhxvYrdQaFLefh0I/Kldbg==}
dependencies: dependencies:

Loading…
Cancel
Save