merge feat/ng-app/sync-wallets with master

pull/33/head
Niko PLP 3 months ago
commit 26e67a0bc4
  1. 75
      Cargo.lock
  2. 48
      README.md
  3. 3
      nextgraph/.gitignore
  4. 4
      nextgraph/Cargo.toml
  5. 34
      nextgraph/src/lib.rs
  6. 258
      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. 76
      ng-app/src-tauri/src/lib.rs
  12. 2
      ng-app/src/App.svelte
  13. 6
      ng-app/src/api.ts
  14. 41
      ng-app/src/lib/Login.svelte
  15. 12
      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. 32
      ng-app/src/routes/WalletCreate.svelte
  21. 29
      ng-app/src/routes/WalletInfo.svelte
  22. 68
      ng-app/src/routes/WalletLogin.svelte
  23. 8
      ng-app/src/routes/WalletLoginQr.svelte
  24. 15
      ng-app/src/store.ts
  25. 101
      ng-broker/src/server_broker.rs
  26. 5
      ng-net/src/actor.rs
  27. 2
      ng-net/src/actors/client/mod.rs
  28. 89
      ng-net/src/actors/client/wallet_put_export.rs
  29. 2
      ng-net/src/actors/ext/mod.rs
  30. 109
      ng-net/src/actors/ext/wallet_get_export.rs
  31. 2
      ng-net/src/actors/mod.rs
  32. 72
      ng-net/src/actors/start.rs
  33. 28
      ng-net/src/broker.rs
  34. 103
      ng-net/src/connection.rs
  35. 13
      ng-net/src/server_broker.rs
  36. 121
      ng-net/src/types.rs
  37. 43
      ng-repo/src/errors.rs
  38. 80
      ng-sdk-js/src/lib.rs
  39. 20
      ng-verifier/src/verifier.rs
  40. 1
      ng-wallet/Cargo.toml
  41. 24
      ng-wallet/src/types.rs
  42. 3706
      pnpm-lock.yaml

75
Cargo.lock generated

@ -2797,14 +2797,13 @@ checksum = "f850fafca79ebacd70eab9d80cb75a33aeda38bde8f3dd784c1837cdf0bde631"
[[package]]
name = "json-patch"
version = "1.0.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f54898088ccb91df1b492cc80029a6fdf1c48ca0db7c6822a8babad69c94658"
checksum = "ec9ad60d674508f3ca8f380a928cfe7b096bc729c4e2dbfe3852bc45da3ab30b"
dependencies = [
"serde",
"serde_json",
"thiserror",
"treediff",
]
[[package]]
@ -2942,9 +2941,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.19"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
dependencies = [
"value-bag",
]
@ -3264,6 +3263,7 @@ dependencies = [
"async-trait",
"base64-url",
"futures",
"lazy_static",
"ng-client-ws",
"ng-net",
"ng-repo",
@ -3271,9 +3271,12 @@ dependencies = [
"ng-verifier",
"ng-wallet",
"once_cell",
"qrcode",
"serde_bare",
"serde_bytes",
"serde_json",
"web-time",
"whoami",
"zeroize",
]
@ -3293,7 +3296,9 @@ dependencies = [
"sys-locale",
"tauri",
"tauri-build",
"tauri-plugin-barcode-scanner",
"tauri-plugin-window",
"tauri-utils",
]
[[package]]
@ -3547,6 +3552,7 @@ dependencies = [
"aes-gcm-siv",
"argon2",
"async-std",
"base64-url",
"blake3",
"chacha20poly1305",
"crypto_box",
@ -4436,6 +4442,12 @@ dependencies = [
"bytemuck",
]
[[package]]
name = "qrcode"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec"
[[package]]
name = "quick-xml"
version = "0.28.2"
@ -4608,6 +4620,15 @@ dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_users"
version = "0.4.3"
@ -5575,6 +5596,20 @@ dependencies = [
"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]]
name = "tauri-plugin-window"
version = "2.0.0-alpha.1"
@ -6043,15 +6078,6 @@ dependencies = [
"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]]
name = "try-lock"
version = "0.2.4"
@ -6198,9 +6224,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "value-bag"
version = "1.4.0"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e"
checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101"
[[package]]
name = "vcpkg"
@ -6325,6 +6351,12 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]]
name = "wasm-bindgen"
version = "0.2.87"
@ -6540,6 +6572,17 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
[[package]]
name = "whoami"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9"
dependencies = [
"redox_syscall 0.4.1",
"wasite",
"web-sys",
]
[[package]]
name = "winapi"
version = "0.3.9"

@ -43,9 +43,9 @@ Read our [getting started guide](https://docs.nextgraph.org/en/getting-started/)
## For contributors
- [Install Rust](https://www.rust-lang.org/tools/install) minimum required MSRV 1.74.0
- [Install Nodejs](https://nodejs.org/en/download/)
- [Install LLVM](https://rust-lang.github.io/rust-bindgen/requirements.html)
- [Install Rust](https://www.rust-lang.org/tools/install) minimum required MSRV 1.74.0
- [Install Nodejs](https://nodejs.org/en/download/)
- [Install LLVM](https://rust-lang.github.io/rust-bindgen/requirements.html)
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 :
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
// optionally, if you want a Rust REPL: cargo install evcxr_repl
@ -70,20 +78,20 @@ cargo build
The crates are organized as follow :
- [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
- [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-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-net : Network common library
- 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-broker : Core and Server Broker 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)
- 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
- [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
- [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-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-net : Network common library
- 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-broker : Core and Server Broker 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)
- 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
### Run
@ -224,9 +232,9 @@ additional terms or conditions.
Licensed under either of
- 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)
at your option.
- 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)
at your option.
`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]
serde_bare = "0.5.0"
serde_json = "1.0"
serde_bytes = "0.11.7"
base64-url = "2.0.0"
once_cell = "1.17.1"
zeroize = { version = "1.7.0", features = ["zeroize_derive"] }
@ -25,7 +26,10 @@ futures = "0.3.24"
async-std = { version = "1.12.0", features = [ "attributes", "unstable" ] }
async-trait = "0.1.64"
async-once-cell = "0.5.3"
lazy_static = "1.4.0"
web-time = "0.2.0"
whoami = "1.5.1"
qrcode = { version = "0.14.1", default-features = false, features = ["svg"] }
ng-repo = { path = "../ng-repo", 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" }

@ -98,3 +98,37 @@ pub mod verifier {
pub mod wallet {
pub use ng_wallet::*;
}
pub fn get_device_name() -> String {
let mut list: Vec<String> = Vec::with_capacity(3);
#[cfg(not(target_arch = "wasm32"))]
if let Ok(realname) = whoami::fallible::realname() {
list.push(realname);
} else {
#[cfg(not(target_arch = "wasm32"))]
if let Ok(username) = whoami::fallible::username() {
list.push(username);
}
}
if let Ok(devicename) = whoami::fallible::devicename() {
list.push(devicename);
} else {
#[cfg(not(target_arch = "wasm32"))]
if let Ok(hostname) = whoami::fallible::hostname() {
list.push(hostname);
} else {
if let Ok(distro) = whoami::fallible::distro() {
list.push(distro);
}
}
}
#[cfg(target_arch = "wasm32")]
if let Ok(distro) = whoami::fallible::distro() {
list.push(distro);
}
list.join(" ")
}
#[cfg(debug_assertions)]
mod local_broker_dev_env;

@ -20,6 +20,8 @@ use once_cell::sync::Lazy;
use serde_bare::to_vec;
use serde_json::json;
use zeroize::Zeroize;
use qrcode::{render::svg, QrCode};
use lazy_static::lazy_static;
use ng_repo::block_storage::BlockStorage;
use ng_repo::block_storage::HashMapBlockStorage;
@ -27,7 +29,7 @@ use ng_repo::errors::{NgError, ProtocolError};
use ng_repo::log::*;
use ng_repo::os_info::get_os_info;
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::broker::*;
@ -1492,6 +1494,235 @@ pub async fn wallet_add(lws: LocalWalletStorageV0) -> Result<(), NgError> {
}
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: NEXTGRAPH_EU.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.
///
@ -2126,7 +2357,7 @@ pub async fn app_request(request: AppRequest) -> Result<AppResponse, NgError> {
let session = broker.opened_sessions_list[real_session_id]
.as_mut()
.ok_or(NgError::SessionNotFound)?;
session.verifier.app_request(request).await
session.verifier.app_request(request).await
}
}
}
@ -2214,6 +2445,29 @@ async fn do_admin_call<
.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)]
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": {
"@popperjs/core": "^2.11.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",
"async-proxy": "^0.4.1",
"classnames": "^2.3.2",

@ -21,7 +21,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "2.0.0-alpha.8", features = [] }
# tauri-macros = { 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]
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"] }
tauri = { version = "2.0.0-alpha.14", features = [] }
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 = { 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 = [] }

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

@ -127,8 +127,9 @@ async fn wallet_open_with_mnemonic_words(
pin: [u8; 4],
_app: tauri::AppHandle,
) -> Result<SensitiveWallet, String> {
let wallet = nextgraph::local_broker::wallet_open_with_mnemonic_words(&wallet, &mnemonic_words, pin)
.map_err(|e| e.to_string())?;
let wallet =
nextgraph::local_broker::wallet_open_with_mnemonic_words(&wallet, &mnemonic_words, pin)
.map_err(|e| e.to_string())?;
Ok(wallet)
}
@ -205,6 +206,55 @@ async fn wallet_import(
.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")]
async fn get_wallets(
app: tauri::AppHandle,
@ -491,6 +541,11 @@ fn client_info_rust() -> Result<Value, String> {
Ok(ng_repo::os_info::get_os_info())
}
#[tauri::command(rename_all = "snake_case")]
fn get_device_name() -> Result<String, String> {
Ok(nextgraph::get_device_name())
}
#[derive(Default)]
pub struct AppBuilder {
setup: Option<SetupHook>,
@ -517,7 +572,7 @@ impl AppBuilder {
pub fn run(self) {
let setup = self.setup;
tauri::Builder::default()
let builder = tauri::Builder::default()
.setup(move |app| {
if let Some(setup) = setup {
(setup)(app)?;
@ -533,7 +588,14 @@ impl AppBuilder {
}
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![
test,
locales,
@ -547,6 +609,11 @@ impl AppBuilder {
wallet_read_file,
wallet_get_file,
wallet_import,
wallet_export_rendezvous,
wallet_export_get_qrcode,
wallet_export_get_textcode,
wallet_import_rendezvous,
wallet_import_from_code,
wallet_close,
encode_create_account,
session_start,
@ -565,6 +632,7 @@ impl AppBuilder {
app_request_stream,
app_request,
upload_chunk,
get_device_name,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");

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

@ -22,6 +22,11 @@ const mapping = {
"wallet_read_file": ["file"],
"wallet_get_file": ["wallet_name"],
"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"],
"encode_create_account": ["payload"],
"session_start": ["wallet_name","user"],
@ -34,6 +39,7 @@ const mapping = {
"user_disconnect": ["user_id"],
"app_request": ["request"],
"test": [ ],
"get_device_name": [],
"doc_fetch_private_subscribe": [],
"doc_fetch_repo_subscribe": ["repo_id"],
}

@ -27,12 +27,12 @@
Backspace,
ArrowPath,
LockOpen,
Key,
CheckCircle,
ArrowLeft,
} from "svelte-heros-v2";
import PasswordInput from "./components/PasswordInput.svelte";
import Spinner from "./components/Spinner.svelte";
import { display_error } from "../store";
//import Worker from "../worker.js?worker&inline";
export let wallet;
export let for_import = false;
@ -48,9 +48,13 @@
onMount(async () => {
loaded = false;
if (for_import) {
device_name = await ng.get_device_name();
}
load_svg();
//console.log(wallet);
await init();
});
async function init() {
@ -122,6 +126,8 @@
let unlockWith: "pazzle" | "mnemonic" | undefined;
let device_name;
function order() {
step = "order";
ordered = [];
@ -168,6 +174,7 @@
// open the wallet
try {
if (tauri_platform) {
// TODO @niko: Add device_name as param to open_with_* APIs
let opened_wallet =
unlockWith === "pazzle"
? await ng.wallet_open_with_pazzle(wallet, pazzle, pin_code)
@ -191,6 +198,7 @@
wallet: opened_wallet,
id: opened_wallet.V0.wallet_id,
trusted,
device_name,
});
} else {
let worker_import = await import("../worker.js?worker&inline");
@ -211,9 +219,14 @@
//console.log("Message received from worker", msg.data);
if (msg.data.loaded) {
if (unlockWith === "pazzle") {
myWorker.postMessage({ wallet, pazzle, pin_code });
myWorker.postMessage({ wallet, pazzle, pin_code, device_name });
} else {
myWorker.postMessage({ wallet, mnemonic_words, pin_code });
myWorker.postMessage({
wallet,
mnemonic_words,
pin_code,
device_name,
});
}
//console.log("postMessage");
} else if (msg.data.success) {
@ -233,6 +246,7 @@
wallet: msg.data.success,
id: msg.data.success.V0.wallet_id,
trusted,
device_name,
});
} else {
console.error(msg.data.error);
@ -387,13 +401,26 @@
<Toggle class="" bind:checked={trusted}
>{$t("pages.login.trust_device_yes")}</Toggle
>
<!-- Ask for Device Name -->
</div>
</div>
{/if}
<div class=" max-w-xl lg:px-8 mx-auto px-4 text-primary-700">
<div class="max-w-xl lg:px-8 mx-auto px-4 text-primary-700">
<div class="flex flex-col justify-centerspace-x-12 mt-4 mb-4">
<!-- Device Name, if trusted-->
{#if for_import}
<label for="device-name-input" class="text-sm text-black">
{$t("pages.login.device_name_label")}
</label>
<input
id="device-name-input"
bind:value={device_name}
placeholder={$t("pages.login.device_name_placeholder")}
type="text"
class="w-full mb-10 lg:px-8 mx-auto px-4 bg-gray-50 border border-gray-300 text-xs rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
/>
{/if}
{#if !loaded}
{$t("pages.login.loading_pazzle")}...
<Spinner className="my-4 h-14 w-14 mx-auto" />
@ -422,7 +449,7 @@
on:keypress={start_with_mnemonic}
role="link"
tabindex="0"
class="mt-1 text-lg px-5 py-2.5 text-center inline-flex items-center mb-2 underline cursor-pointer"
class="mt-1 text-lg px-5 py-2.5 text-center inline-flex items-center mb-10 underline cursor-pointer"
>
{$t("pages.login.open_with_mnemonic")}
</span>
@ -649,7 +676,7 @@
/>
</svg>
<Alert color="red" class="mt-5">
{$t("errors." + error)}
{display_error(error)}
</Alert>
</div>
<div class="flex justify-between mt-auto gap-4">

@ -100,6 +100,8 @@
},
"trust_device_allow_cookies": "By selecting this option, you agree to saving some cookies on your browser.",
"trust_device_yes": "Yes, save my wallet on this device",
"device_name_label": "Name of this device. You can edit it now",
"device_name_placeholder": "Enter name of this device",
"loading_pazzle": "Loading Pazzle",
"open_with_pazzle": "Open With Pazzle",
"login_cancel": "Cancel Login",
@ -207,6 +209,7 @@
"trust": "Do you trust this device?",
"trust_description": "If you do, if this device is yours, or it is used by a few trusted persons of your family or workplace, and you would like to login again from this device in the future, then you can save your wallet on this device. To the contrary, if this device is public and shared by strangers, do not save your wallet here.",
"trust_toggle": "Save my wallet on this device?",
"device_name_description": "To see which devices you are connected with, every device should have a name (e.g. Bob's laptop). Please enter it here.",
"cloud": "Keep a copy in the cloud?",
"cloud_description": "Are you afraid that you will loose the file containing your wallet? If this would happen, your wallet would be lost forever, together with all your documents. We can keep an encrypted copy of your wallet in our cloud. Only you will be able to download it with a special link. You would have to keep this link safely though. By selecting this option, you agree to the",
"cloud_toggle": "Save my wallet in the cloud?",
@ -331,18 +334,19 @@
"VerifierError": "Error during verification.",
"SiteNotFoundOnBroker": "The site cannot be found on the broker",
"BrokerConfigErrorStr": "{error}",
"BrokerConfigError": "Error in the broker configuration",
"BrokerConfigError": "Error in the broker configuration.",
"MalformedEvent": "The event has an invalid format.",
"InvalidPayload": "The payload is invalid.",
"WrongUploadId": "The upload ID is incorrect.",
"FileError": "Error with file.",
"InternalError": "Internal Error",
"OxiGraphError": "Error in OxiGraph database.",
"ConfigError": "Error in configuration",
"ConfigError": "Error in configuration.",
"LocalBrokerIsHeadless": "The local broker is headless.",
"LocalBrokerIsNotHeadless": "The local broker is not headless.",
"InvalidNuri": "Invalid NextGraph URI",
"InvalidTarget": "Cannot resolve target"
"InvalidNuri": "Invalid NextGraph URI.",
"InvalidTarget": "Cannot resolve target.",
"ExportWalletTimeOut": "Export of wallet has expired."
},
"connectivity": {
"stopped": "Stopped",

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

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

@ -44,7 +44,7 @@
} from "../wallet_emojis";
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";
import Spinner from "../lib/components/Spinner.svelte";
const param = new URLSearchParams($querystring);
@ -139,6 +139,8 @@
/** The emojis for the newly created pazzle. */
let pazzle_emojis = [];
let confirm_modal_open = false;
let device_name;
function scrollToTop() {
top.scrollIntoView();
}
@ -221,6 +223,8 @@
}
async function save_security() {
device_name = await ng.get_device_name();
options = {
trusted: true,
cloud: false,
@ -254,6 +258,7 @@
core_bootstrap: invitation.V0.bootstrap,
core_registration,
additional_bootstrap,
//TODO: device_name,
};
//console.log("do wallet with params", params);
try {
@ -316,7 +321,7 @@
unsub_register_accepted = undefined;
};
onDestroy(() => {
onDestroy(async () => {
unsub_register();
});
@ -570,7 +575,7 @@
{:else}
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
{@html $t("errors.error_occurred", {
values: { message: $t("errors." + registration_error) },
values: { message: display_error(registration_error) },
})}
</p>
<a use:link href="/">
@ -1424,8 +1429,25 @@
"pages.wallet_create.save_wallet_options.trust_toggle"
)}</Toggle
>
<!-- Device Name-->
</p>
<!-- Device Name -->
{#if options.trusted}
<br />
<p class="max-w-xl md:mx-auto lg:max-w-2xl text-left">
{@html $t(
"pages.wallet_create.save_wallet_options.device_name_description"
)}
</p>
<input
id="device-name-input"
class="mt-2 bg-gray-50 border border-gray-300 text-xs rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
bind:value={device_name}
placeholder={$t(
"pages.login.device_name_placeholder"
)}
type="text"
/>
{/if}
<p class="max-w-xl md:mx-auto mt-10 lg:max-w-2xl text-left">
<span class="text-xl"
>{@html $t(
@ -1707,7 +1729,7 @@
/>
</svg>
<Alert color="red" class="mt-5">
{$t("errors." + error)}
{display_error(error)}
</Alert>
<button
class="mt-10 select-none"

@ -42,7 +42,8 @@
close_active_wallet,
active_session,
active_wallet,
online,
display_error,
online
} from "../store";
import { default as ng } from "../api";
@ -63,7 +64,8 @@
let generated_qr: string | undefined = undefined;
let generated_text_code: string | null = null;
// TODO: do that only when needed // generated_text_code = await ng.wallet_export_get_textcode($active_session.session_id);
let scanner_open = false;
let scanned_qr = null;
let scan_successful: null | true = null;
@ -107,12 +109,20 @@
setTimeout(() => {
generation_state = "generated";
generated_qr = "dummy";
// TODO: generated_qr = await ng.wallet_export_get_qrcode($active_session.session_id, 250);
}, 3000);
}
function on_qr_scanned(text: string) {
async function on_qr_scanned(text: string) {
scanned_qr = text;
// TODO: API calls for synchronization @niko
//
// example :
// try {
// await ng.wallet_export_rendezvous($active_session.session_id, text);
// } catch (e) {
// console.error(e);
// }
// ToRemove:
setTimeout(() => {
scan_successful = true;
@ -184,7 +194,7 @@
}
let wallet_remove_modal_open = false;
function remove_wallet_clicked() {
async function remove_wallet_clicked() {
wallet_remove_modal_open = true;
}
@ -256,7 +266,7 @@
<div>
<QrCode
tabindex="-1"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
class="w-7 h-7 text-black transition duration-75 dark:text-white group-hover:text-gray-900 dark:group-hover:text-white"
/>
</div>
<span class="ml-3">{$t("pages.wallet_info.generate_qr")}</span>
@ -559,12 +569,15 @@
<QrCode class="w-full h-full" />
</div>
{:else}
<img
{@html generated_qr}
<!--img
src={generated_qr}
title={$t("pages.wallet_info.gen_qr.img_title")}
alt="pages.wallet_info.gen_qr_alt"
class="w-full h-full"
/>
/-->
{/if}
</div>
{/if}
@ -607,7 +620,7 @@
{:else}
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
{@html $t("errors.error_occurred", {
values: { message: $t("errors." + error) },
values: { message: display_error(error) },
})}
</p>
<a use:link href="/">

@ -32,6 +32,8 @@
active_session,
set_active_session,
has_wallets,
wallet_import_qrcode,
display_error,
} from "../store";
import { QrCode } from "svelte-heros-v2";
@ -57,6 +59,7 @@
}
onMount(async () => {
step = "open";
wallets_unsub = wallets.subscribe((value) => {
wallet = selected && $wallets[selected]?.wallet;
@ -93,6 +96,49 @@
}
}
});
if ($wallet_import_qrcode) {
let code = $wallet_import_qrcode;
wallet_import_qrcode.set("");
try {
let temp_wallet = await ng.wallet_import_from_code(code);
// TODO: in 2 steps. first display: wallet was retrieved successfully.
// then when user clicks on "continue to login", do:
wallet = temp_wallet;
importing = true;
} catch(e) {
error = e;
}
}
// example of getting wallet from TextCode
// async () => {
// try {
// let temp_wallet = await ng.wallet_import_from_code("AABAOAAAAHNb4y7hdWADqFWDgER3J0xvD3K5D9pZ1wd7Bja4c9cWAOFNpmUIZOFRro0UIpZWr5Ah8U7PlRFe1GFZSKuIextFAA8A45zZUJmUPhfdBrcho1vYPfgda0BAgIT1qjzgEkBQAA");
// // TODO: in 2 steps. first display: wallet was retrieved successfully.
// // then when user clicks on "continue to login", do:
// wallet = temp_wallet;
// 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 {
// let temp_wallet = await ng.wallet_import_from_code(qrcode[1]);
// // TODO: in 2 steps. first display: wallet was retrieved successfully.
// // then when user clicks on "continue to login", do:
// wallet = temp_wallet;
// importing = true;
// } catch (e) {
// error = e;
// }
// TODO: display with QRcode with :
// {#if qrcode}
// {@html qrcode[0]}
// {/if}
});
function loggedin() {
step = "loggedin";
@ -215,7 +261,7 @@
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
{@html $t("errors.error_occurred", {
values: { message: $t("errors." + error) },
values: { message: display_error(error) },
})}
</p>
<button
@ -269,10 +315,14 @@
</div>
{/each}
<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")}
</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}
<Fileupload
style="display:none;"
@ -304,20 +354,19 @@
{$t("pages.wallet_login.import_file")}
</button>
<a href="/wallet/login-qr" use:link>
<Button
<button
style="min-width: 250px;justify-content: left;"
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 justify-center dark:focus:ring-primary-100/55 mb-2"
>
<QrCode class="w-8 h-8 mr-2 -ml-1" />
{$t("pages.wallet_login.import_qr")}
</Button>
</button>
</a>
<a href="/wallet/login-text-code" use:link>
<Button
<button
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"
>
>
<svg
class="w-8 h-8 mr-2 -ml-1"
fill="none"
@ -333,9 +382,8 @@
d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"
/>
</svg>
{$t("pages.wallet_login.import_link")}
</Button>
</button>
</a>
<a href="/wallet/create" use:link>
<button

@ -29,10 +29,14 @@
// Load in browser only
if (!tauri_platform && !WebQRScannerClassPromise) {
WebQRScannerClassPromise = new Promise((resolve) => {
import("html5-qrcode").then((lib) => resolve(lib.Html5QrcodeScanner));
import("html5-qrcode").then((lib) => resolve(lib.Html5QrcodeScanner)); // comment: why you don't use await ?
});
}
// TODO: Load alternative for native apps?
// <a href="/wallet/scanqr" use:link>
}
let top;
@ -159,7 +163,7 @@
<div><Spinner /></div>
{:else if false}
<!-- Warning, if offline -->
<!-- TODO: get connection status to nextgraph.one -->
<!-- TODO: just use $online from store to know if it is online -->
<div class="text-left">
<Alert color="red">
{@html $t("pages.wallet_login_qr.offline_warning")}

@ -15,7 +15,7 @@ import {
get,
type Writable,
} from "svelte/store";
import { register, init, locale } from "svelte-i18n";
import { register, init, locale, format } from "svelte-i18n";
import ng from "./api";
import { official_classes } from "./classes";
import { official_apps, official_services } from "./zeras";
@ -43,6 +43,15 @@ init({
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 () => {
let locales = await ng.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({});
/// { wallet:, id: }
@ -454,7 +465,7 @@ export const branch_subs = function(nuri) {
};
let blob_cache = {};
export async function get_blob(ref: { nuri: string | number; reference: { key: any; id: any; }; }) {
export async function get_blob(ref: { nuri: string; reference: { key: any; id: any; }; }) {
if (!ref) return false;
const cached = blob_cache[ref.nuri];
if (cached) {

@ -12,14 +12,15 @@
//! Implementation of the Server Broker
use std::{
collections::{HashMap, HashSet},
collections::{BTreeMap, HashMap, HashSet},
path::PathBuf,
sync::Arc,
time::{Duration, SystemTime},
};
use async_std::sync::{Mutex, RwLock};
use either::Either;
use futures::StreamExt;
use futures::{channel::mpsc, SinkExt, StreamExt};
use serde::{Deserialize, Serialize};
use ng_repo::{
@ -35,6 +36,7 @@ use ng_net::{
connection::NoiseFSM,
server_broker::IServerBroker,
types::*,
utils::{spawn_and_log_error, Receiver, ResultSend, Sender},
};
use ng_verifier::{
@ -147,6 +149,10 @@ pub struct ServerBrokerState {
verifiers: HashMap<UserId, Arc<RwLock<DetachableVerifier>>>,
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 {
@ -167,6 +173,9 @@ impl ServerBroker {
local_subscriptions: HashMap::new(),
verifiers: HashMap::new(),
remote_apps: HashMap::new(),
wallet_rendezvous: HashMap::new(),
wallet_exports: HashMap::new(),
wallet_exports_timestamp: BTreeMap::new(),
}),
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.
//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]
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(
&self,
) -> std::sync::Arc<std::sync::RwLock<dyn BlockStorage + Send + Sync>> {

@ -58,6 +58,7 @@ pub(crate) struct Actor<
//initiator: bool,
}
#[derive(Debug)]
pub enum SoS<B> {
Single(B),
Stream(Receiver<B>),
@ -185,6 +186,10 @@ impl<
}
fsm.lock().await.remove_actor(self.id).await;
let server_error: Result<ServerError, NgError> = (&msg).try_into();
//log_debug!("server_error {:?}", server_error);
if server_error.is_ok() {
return Err(NgError::ServerError(server_error.unwrap()));
}
let response: B = match msg.try_into() {
Ok(b) => b,
Err(ProtocolError::ServerError) => {

@ -15,3 +15,5 @@ pub mod blocks_put;
pub mod blocks_exist;
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 app;
pub mod ext;

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

@ -894,6 +894,34 @@ impl Broker {
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)]
pub fn connect_local(&mut self, peer_pubk: PubKey, user: UserId) -> Result<(), ProtocolError> {
if self.closing {

@ -112,7 +112,7 @@ pub enum FSMstate {
Noise3,
AdminRequest,
ExtRequest,
ExtResponse,
//ExtResponse,
ClientHello,
ServerHello,
ClientAuth,
@ -177,7 +177,10 @@ pub struct ClientConfig {
}
#[derive(Debug, Clone)]
pub struct ExtConfig {}
pub struct ExtConfig {
pub url: String,
pub request: ExtRequestContentV0,
}
#[derive(Debug, Clone)]
pub struct CoreConfig {
@ -215,10 +218,14 @@ pub enum StartConfig {
impl StartConfig {
pub fn get_url(&self) -> String {
match self {
Self::Client(config) => config.url.clone(),
Self::Admin(config) => format!("ws://{}:{}", config.addr.ip, config.addr.port),
Self::Core(config) => format!("ws://{}:{}", config.addr.ip, config.addr.port),
Self::App(config) => format!("ws://{}:{}", config.addr.ip, config.addr.port),
Self::Client(ClientConfig { url, .. }) | Self::Ext(ExtConfig { url, .. }) => {
url.clone()
}
Self::Admin(AdminConfig { addr, .. })
| Self::Core(CoreConfig { addr, .. })
| Self::App(AppConfig { addr, .. }) => {
format!("ws://{}:{}", addr.ip, addr.port)
}
_ => unimplemented!(),
}
}
@ -235,9 +242,9 @@ impl StartConfig {
_ => false,
}
}
pub fn is_admin(&self) -> bool {
pub fn is_oneshot(&self) -> bool {
match self {
StartConfig::Admin(_) => true,
StartConfig::Admin(_) | StartConfig::Ext(_) => true,
_ => false,
}
}
@ -635,7 +642,10 @@ impl NoiseFSM {
self.state = FSMstate::ClientHello;
}
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) => {
todo!();
@ -774,8 +784,18 @@ impl NoiseFSM {
StartConfig::Client(_) => {
return Err(ProtocolError::InvalidState);
}
StartConfig::Ext(_ext_config) => {
todo!();
StartConfig::Ext(ext_config) => {
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) => {
todo!();
@ -807,8 +827,9 @@ impl NoiseFSM {
StartProtocol::Client(_) => {
return Err(ProtocolError::InvalidState);
}
StartProtocol::Ext(_ext_config) => {
todo!();
StartProtocol::Ext(_ext_req) => {
self.state = FSMstate::Closing;
return Ok(StepReply::Responder(msg_opt.unwrap()));
}
// StartProtocol::Core(core_config) => {
// todo!();
@ -852,8 +873,15 @@ impl NoiseFSM {
return Ok(StepReply::Response(msg));
}
}
FSMstate::ExtRequest => {}
FSMstate::ExtResponse => {}
FSMstate::ExtRequest => {
// 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 => {
if let Some(msg) = msg_opt.as_ref() {
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> {
if !self.dir.is_server() {
let config = StartConfig::Probe;
@ -1431,7 +1500,7 @@ impl ConnectionBase {
pub async fn start(&mut self, config: StartConfig) -> Result<(), ProtocolError> {
// BOOTSTRAP the protocol from client-side
if !self.dir.is_server() {
let is_admin = config.is_admin();
let is_oneshot = config.is_oneshot();
let res;
{
let mut fsm = self.fsm.as_ref().unwrap().lock().await;
@ -1442,7 +1511,7 @@ impl ConnectionBase {
self.send(ConnectionCommand::ProtocolError(err.clone()))
.await;
Err(err)
} else if !is_admin {
} else if !is_oneshot {
let mut actor = Box::new(Actor::<Connecting, ()>::new(0, true));
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::connection::NoiseFSM;
use crate::types::*;
use crate::utils::Receiver;
#[async_trait::async_trait]
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_block_storage(&self) -> Arc<std::sync::RwLock<dyn BlockStorage + Send + Sync>>;
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
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BlocksPutV0 {
@ -3414,6 +3428,8 @@ pub enum ClientRequestContentV0 {
// For InnerOverlay's only :
BlocksPut(BlocksPut),
PublishEvent(PublishEvent),
WalletPutExport(WalletPutExport),
}
impl ClientRequestContentV0 {
@ -3428,6 +3444,7 @@ impl ClientRequestContentV0 {
ClientRequestContentV0::BlocksPut(a) => a.set_overlay(overlay),
ClientRequestContentV0::BlocksExist(a) => a.set_overlay(overlay),
ClientRequestContentV0::BlocksGet(a) => a.set_overlay(overlay),
ClientRequestContentV0::WalletPutExport(_a) => {}
_ => unimplemented!(),
}
}
@ -3479,6 +3496,7 @@ impl ClientRequest {
ClientRequestContentV0::BlocksPut(r) => r.get_actor(self.id()),
ClientRequestContentV0::BlocksExist(r) => r.get_actor(self.id()),
ClientRequestContentV0::BlocksGet(r) => r.get_actor(self.id()),
ClientRequestContentV0::WalletPutExport(r) => r.get_actor(self.id()),
_ => unimplemented!(),
},
}
@ -3946,23 +3964,41 @@ pub enum ExtObjectGet {
V0(ExtObjectGetV0),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExtWalletGetExportV0 {
pub id: SymKey,
pub is_rendezvous: bool,
}
/// Topic synchronization request
pub type ExtTopicSyncReq = TopicSyncReq;
/// Content of ExtRequestV0
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ExtRequestContentV0 {
WalletGetExport(ExtWalletGetExportV0),
ExtObjectGet(ExtObjectGet),
ExtTopicSyncReq(ExtTopicSyncReq),
// TODO inbox requests
// 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
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExtRequestV0 {
/// outer overlayId
pub overlay: Digest,
pub overlay: Option<Digest>,
/// Request ID
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
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ExtResponseContentV0 {
EmptyResponse,
Block(Block),
Wallet(ExportedWallet),
// TODO inbox related replies
// 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
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExtResponseV0 {
@ -4013,7 +4081,7 @@ pub struct ExtResponseV0 {
pub result: u16,
/// Response content
pub content: Option<ExtResponseContentV0>,
pub content: ExtResponseContentV0,
}
/// 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 {
@ -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
//
@ -4149,6 +4250,12 @@ impl TryFrom<&ProtocolMessage> for ServerError {
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 {
let res = bm.result();
if res != 0 {
@ -4205,12 +4312,6 @@ impl ProtocolMessage {
match self {
ProtocolMessage::ClientMessage(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,
}
}
@ -4224,8 +4325,8 @@ impl ProtocolMessage {
// 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::ExtRequest(a) => a.get_actor(),
//ProtocolMessage::ExtResponse(a) => a.get_actor(),
// ProtocolMessage::BrokerMessage(a) => a.get_actor(),
_ => unimplemented!(),
}

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

@ -60,6 +60,11 @@ pub async fn locales() -> Result<JsValue, JsValue> {
Ok(serde_wasm_bindgen::to_value(&get_locales().collect::<Vec<_>>()).unwrap())
}
#[wasm_bindgen]
pub async fn get_device_name() -> Result<JsValue, JsValue> {
Ok(serde_wasm_bindgen::to_value(&nextgraph::get_device_name()).unwrap())
}
#[wasm_bindgen]
pub async fn get_local_bootstrap(location: String, invite: JsValue) -> JsValue {
let res = retrieve_local_bootstrap(location, invite.as_string(), false).await;
@ -535,6 +540,81 @@ pub async fn wallet_read_file(file: JsValue) -> Result<JsValue, String> {
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]
pub async fn wallet_was_opened(
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>(
&'a mut self,
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"] }
zeroize = { version = "1.7.0", features = ["zeroize_derive"] }
crypto_box = { version = "0.8.2", features = ["seal"] }
base64-url = "2.0.0"
blake3 = "1.3.1"
argon2 = "0.5.0"
chacha20poly1305 = "0.10.1"

@ -1426,3 +1426,27 @@ pub struct ShuffledPazzle {
pub category_indices: 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)
}
}

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save