From a29db952e2f5f6fa178af3beb37f201ad855b654 Mon Sep 17 00:00:00 2001 From: Laurin Weger Date: Tue, 9 Jul 2024 13:59:19 +0200 Subject: [PATCH 1/4] feat/ng-app: ask for device name svelte part --- ng-app/src/lib/Login.svelte | 37 +++++++++++++++++++++++---- ng-app/src/locales/en.json | 4 +++ ng-app/src/routes/WalletCreate.svelte | 27 ++++++++++++++++++- ng-app/src/store.ts | 2 +- 4 files changed, 63 insertions(+), 7 deletions(-) diff --git a/ng-app/src/lib/Login.svelte b/ng-app/src/lib/Login.svelte index 765df6b..0f7f7fa 100644 --- a/ng-app/src/lib/Login.svelte +++ b/ng-app/src/lib/Login.svelte @@ -27,7 +27,6 @@ Backspace, ArrowPath, LockOpen, - Key, CheckCircle, ArrowLeft, } from "svelte-heros-v2"; @@ -122,6 +121,11 @@ let unlockWith: "pazzle" | "mnemonic" | undefined; + let device_name; + + // TODO: @niko Implement API + // ng.get_device_name().then((name) => (device_name = name)); + function order() { step = "order"; ordered = []; @@ -168,6 +172,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 +196,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 +217,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 +244,7 @@ wallet: msg.data.success, id: msg.data.success.V0.wallet_id, trusted, + device_name, }); } else { console.error(msg.data.error); @@ -387,13 +399,28 @@ {$t("pages.login.trust_device_yes")} - {/if} -
+
+ + {#if for_import} + + + {/if} + {#if !loaded} {$t("pages.login.loading_pazzle")}... diff --git a/ng-app/src/locales/en.json b/ng-app/src/locales/en.json index 5e60d09..630f122 100644 --- a/ng-app/src/locales/en.json +++ b/ng-app/src/locales/en.json @@ -84,6 +84,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 device as seen on your other devices", + "device_name_placeholder": "Display name of this device", "loading_pazzle": "Loading Pazzle", "open_with_pazzle": "Open With Pazzle", "login_cancel": "Cancel Login", @@ -191,6 +193,8 @@ "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.", + "device_name_placeholder": "Display name of this device", "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?", diff --git a/ng-app/src/routes/WalletCreate.svelte b/ng-app/src/routes/WalletCreate.svelte index f2628d5..47f392a 100644 --- a/ng-app/src/routes/WalletCreate.svelte +++ b/ng-app/src/routes/WalletCreate.svelte @@ -139,6 +139,11 @@ /** The emojis for the newly created pazzle. */ let pazzle_emojis = []; let confirm_modal_open = false; + let device_name; + + // TODO @niko add API + // ng.get_device_name().then((name) => (device_name = name)); + function scrollToTop() { top.scrollIntoView(); } @@ -254,6 +259,7 @@ core_bootstrap: invitation.V0.bootstrap, core_registration, additional_bootstrap, + device_name, }; //console.log("do wallet with params", params); try { @@ -1424,8 +1430,27 @@ "pages.wallet_create.save_wallet_options.trust_toggle" )} -

+ + {#if options.trusted} +
+

+ {@html $t( + "pages.wallet_create.save_wallet_options.device_name_description" + )} +

+ + {/if}

{@html $t( diff --git a/ng-app/src/store.ts b/ng-app/src/store.ts index fd477dc..67cc08e 100644 --- a/ng-app/src/store.ts +++ b/ng-app/src/store.ts @@ -454,7 +454,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) { From 65b91ffc3fbba8369d7675ef1ec6cdfbb66f6cc0 Mon Sep 17 00:00:00 2001 From: Niko PLP Date: Wed, 10 Jul 2024 18:14:34 +0300 Subject: [PATCH 2/4] wallet import/export API and QRcode scanning from Tauri plugin --- Cargo.lock | 48 ++-- README.md | 48 ++-- nextgraph/.gitignore | 3 +- nextgraph/Cargo.toml | 3 + nextgraph/src/lib.rs | 3 + nextgraph/src/local_broker.rs | 261 +++++++++++++++++- nextgraph/src/local_broker_dev_env.rs | 1 + ng-app/package.json | 1 + ng-app/src-tauri/Cargo.toml | 3 +- .../android/app/src/main/AndroidManifest.xml | 1 + ng-app/src-tauri/src/lib.rs | 70 ++++- ng-app/src/App.svelte | 2 + ng-app/src/api.ts | 5 + ng-app/src/lib/Login.svelte | 3 +- ng-app/src/locales/en.json | 9 +- ng-app/src/routes/AccountInfo.svelte | 4 +- ng-app/src/routes/ScanQR.svelte | 51 ++++ ng-app/src/routes/User.svelte | 2 +- ng-app/src/routes/UserRegistered.svelte | 3 +- ng-app/src/routes/WalletCreate.svelte | 8 +- ng-app/src/routes/WalletInfo.svelte | 16 +- ng-app/src/routes/WalletLogin.svelte | 112 +++++--- ng-app/src/store.ts | 13 +- ng-broker/src/server_broker.rs | 101 ++++++- ng-net/src/actors/client/mod.rs | 2 + ng-net/src/actors/client/wallet_put_export.rs | 89 ++++++ ng-net/src/actors/ext/mod.rs | 2 + ng-net/src/actors/ext/wallet_get_export.rs | 109 ++++++++ ng-net/src/actors/mod.rs | 2 + ng-net/src/actors/start.rs | 72 ++--- ng-net/src/broker.rs | 28 ++ ng-net/src/connection.rs | 103 +++++-- ng-net/src/server_broker.rs | 13 + ng-net/src/types.rs | 121 +++++++- ng-repo/src/errors.rs | 43 ++- ng-sdk-js/src/lib.rs | 75 +++++ ng-verifier/src/verifier.rs | 20 ++ ng-wallet/Cargo.toml | 1 + ng-wallet/src/types.rs | 24 ++ pnpm-lock.yaml | 8 + 40 files changed, 1311 insertions(+), 172 deletions(-) create mode 100644 nextgraph/src/local_broker_dev_env.rs create mode 100644 ng-app/src/routes/ScanQR.svelte create mode 100644 ng-net/src/actors/client/wallet_put_export.rs create mode 100644 ng-net/src/actors/ext/mod.rs create mode 100644 ng-net/src/actors/ext/wallet_get_export.rs diff --git a/Cargo.lock b/Cargo.lock index f550fea..ad2491e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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,7 +3271,9 @@ dependencies = [ "ng-verifier", "ng-wallet", "once_cell", + "qrcode", "serde_bare", + "serde_bytes", "serde_json", "web-time", "zeroize", @@ -3293,7 +3295,9 @@ dependencies = [ "sys-locale", "tauri", "tauri-build", + "tauri-plugin-barcode-scanner", "tauri-plugin-window", + "tauri-utils", ] [[package]] @@ -3547,6 +3551,7 @@ dependencies = [ "aes-gcm-siv", "argon2", "async-std", + "base64-url", "blake3", "chacha20poly1305", "crypto_box", @@ -4436,6 +4441,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" @@ -5575,6 +5586,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 +6068,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 +6214,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" diff --git a/README.md b/README.md index a1a8a3a..e8d9f17 100644 --- a/README.md +++ b/README.md @@ -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` diff --git a/nextgraph/.gitignore b/nextgraph/.gitignore index 3598c30..01f9583 100644 --- a/nextgraph/.gitignore +++ b/nextgraph/.gitignore @@ -1 +1,2 @@ -tests \ No newline at end of file +tests +local_broker_dev_env_peer_id.rs \ No newline at end of file diff --git a/nextgraph/Cargo.toml b/nextgraph/Cargo.toml index a160823..e9ee4b7 100644 --- a/nextgraph/Cargo.toml +++ b/nextgraph/Cargo.toml @@ -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,9 @@ 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" +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" } diff --git a/nextgraph/src/lib.rs b/nextgraph/src/lib.rs index 9dbf3a0..9eca8d7 100644 --- a/nextgraph/src/lib.rs +++ b/nextgraph/src/lib.rs @@ -98,3 +98,6 @@ pub mod verifier { pub mod wallet { pub use ng_wallet::*; } + +#[cfg(debug_assertions)] +mod local_broker_dev_env; diff --git a/nextgraph/src/local_broker.rs b/nextgraph/src/local_broker.rs index edf7e60..2a7584d 100644 --- a/nextgraph/src/local_broker.rs +++ b/nextgraph/src/local_broker.rs @@ -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,238 @@ 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 { + + 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 { + + 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::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 { + + 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::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 +2360,7 @@ pub async fn app_request(request: AppRequest) -> Result { 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 +2448,29 @@ async fn do_admin_call< .await } +async fn do_ext_call< + A: Into + Into + std::fmt::Debug + Sync + Send + 'static, + B: TryFrom + + std::fmt::Debug + + Sync + + Send + + 'static, +>( + broker_server: &BrokerServerV0, + cmd: A, +) -> Result { + 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 { diff --git a/nextgraph/src/local_broker_dev_env.rs b/nextgraph/src/local_broker_dev_env.rs new file mode 100644 index 0000000..1ddaafe --- /dev/null +++ b/nextgraph/src/local_broker_dev_env.rs @@ -0,0 +1 @@ +pub const PEER_ID: &str = "FtdzuDYGewfXWdoPuXIPb0wnd0SAg1WoA2B14S7jW3MA"; diff --git a/ng-app/package.json b/ng-app/package.json index af43b6d..87d425e 100644 --- a/ng-app/package.json +++ b/ng-app/package.json @@ -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", diff --git a/ng-app/src-tauri/Cargo.toml b/ng-app/src-tauri/Cargo.toml index 0c9ba1a..89b0715 100644 --- a/ng-app/src-tauri/Cargo.toml +++ b/ng-app/src-tauri/Cargo.toml @@ -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 = [] } diff --git a/ng-app/src-tauri/gen/android/app/src/main/AndroidManifest.xml b/ng-app/src-tauri/gen/android/app/src/main/AndroidManifest.xml index a057f34..493b196 100644 --- a/ng-app/src-tauri/gen/android/app/src/main/AndroidManifest.xml +++ b/ng-app/src-tauri/gen/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + Result { - 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 { + 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 { + 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 { + 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, @@ -517,7 +567,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 +583,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 +604,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, diff --git a/ng-app/src/App.svelte b/ng-app/src/App.svelte index 3efb223..ee84e85 100644 --- a/ng-app/src/App.svelte +++ b/ng-app/src/App.svelte @@ -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"; @@ -49,6 +50,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); diff --git a/ng-app/src/api.ts b/ng-app/src/api.ts index 91732c0..369c731 100644 --- a/ng-app/src/api.ts +++ b/ng-app/src/api.ts @@ -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"], diff --git a/ng-app/src/lib/Login.svelte b/ng-app/src/lib/Login.svelte index caadefa..a9aa80b 100644 --- a/ng-app/src/lib/Login.svelte +++ b/ng-app/src/lib/Login.svelte @@ -32,6 +32,7 @@ ArrowLeft, } from "svelte-heros-v2"; import PasswordInput from "./components/PasswordInput.svelte"; + import { display_error } from "../store"; //import Worker from "../worker.js?worker&inline"; export let wallet; export let for_import = false; @@ -685,7 +686,7 @@ /> - {$t("errors." + error)} + {display_error(error)}

diff --git a/ng-app/src/locales/en.json b/ng-app/src/locales/en.json index 17ca308..7cab474 100644 --- a/ng-app/src/locales/en.json +++ b/ng-app/src/locales/en.json @@ -295,18 +295,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", diff --git a/ng-app/src/routes/AccountInfo.svelte b/ng-app/src/routes/AccountInfo.svelte index 2423ab8..37061a0 100644 --- a/ng-app/src/routes/AccountInfo.svelte +++ b/ng-app/src/routes/AccountInfo.svelte @@ -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}

{@html $t("errors.error_occurred", { - values: { message: $t("errors." + error) }, + values: { message: display_error(error) }, })}

diff --git a/ng-app/src/routes/ScanQR.svelte b/ng-app/src/routes/ScanQR.svelte new file mode 100644 index 0000000..7d8f393 --- /dev/null +++ b/ng-app/src/routes/ScanQR.svelte @@ -0,0 +1,51 @@ + + + + + + +
+ + Scanning the QRcode +
\ No newline at end of file diff --git a/ng-app/src/routes/User.svelte b/ng-app/src/routes/User.svelte index f10e73e..678ff1c 100644 --- a/ng-app/src/routes/User.svelte +++ b/ng-app/src/routes/User.svelte @@ -365,7 +365,7 @@ {:else}

{@html $t("errors.error_occurred", { - values: { message: $t("errors." + error) }, + values: { message: display_error(error) }, })}

diff --git a/ng-app/src/routes/UserRegistered.svelte b/ng-app/src/routes/UserRegistered.svelte index 522a94b..235f6db 100644 --- a/ng-app/src/routes/UserRegistered.svelte +++ b/ng-app/src/routes/UserRegistered.svelte @@ -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}

{@html $t("errors.error_occurred", { - values: { message: $t("errors." + error) }, + values: { message: display_error(error) }, })}

diff --git a/ng-app/src/routes/WalletCreate.svelte b/ng-app/src/routes/WalletCreate.svelte index 464077e..c330225 100644 --- a/ng-app/src/routes/WalletCreate.svelte +++ b/ng-app/src/routes/WalletCreate.svelte @@ -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"; const param = new URLSearchParams($querystring); @@ -315,7 +315,7 @@ unsub_register_accepted = undefined; }; - onDestroy(() => { + onDestroy(async () => { unsub_register(); }); @@ -589,7 +589,7 @@ {:else}

{@html $t("errors.error_occurred", { - values: { message: $t("errors." + registration_error) }, + values: { message: display_error(registration_error) }, })}

@@ -1743,7 +1743,7 @@ /> - {$t("errors." + error)} + {display_error(error)}
{/each} + +
+ {#if qrcode} + {@html qrcode[0]} + {/if} +
+
+ {#if qrcode} + {qrcode[1]} + {/if} +
+
- {#if $has_wallets}

+ {#if $has_wallets} +

{$t("pages.wallet_login.with_another_wallet")}

- {:else}

{$t("pages.wallet_login.import_wallet")}

+ {:else} +

+ {$t("pages.wallet_login.import_wallet")} +

{/if} {$t("pages.wallet_login.import_file")} - - +
+ +