wallet creation and login working on native app tauri

pull/19/head
Niko PLP 1 year ago
parent bba95defd9
commit 5289f747e7
  1. 2
      Cargo.lock
  2. 181
      ng-app/src-tauri/src/lib.rs
  3. 6
      ng-app/src-tauri/tauri.conf.json
  4. 37
      ng-app/src/App.svelte
  5. 54
      ng-app/src/api.ts
  6. 12
      ng-app/src/lib/Home.svelte
  7. 65
      ng-app/src/lib/Login.svelte
  8. 8
      ng-app/src/lib/Test.svelte
  9. 2
      ng-app/src/routes/Home.svelte
  10. 4
      ng-app/src/routes/Test.svelte
  11. 2
      ng-app/src/routes/UserRegistered.svelte
  12. 123
      ng-app/src/routes/WalletCreate.svelte
  13. 6
      ng-app/src/routes/WalletLogin.svelte
  14. 10
      ng-app/src/store.ts
  15. 1
      ng-sdk-js/Cargo.toml
  16. 5
      ng-sdk-js/index.html
  17. 18
      ng-sdk-js/js/browser.js
  18. 127
      ng-sdk-js/src/lib.rs
  19. 1
      ng-wallet/Cargo.toml
  20. 37
      ng-wallet/src/lib.rs
  21. 92
      ng-wallet/src/types.rs
  22. 3
      ngaccount/README.md
  23. 71
      ngaccount/src/main.rs
  24. 15
      ngaccount/web/package.json
  25. 8
      ngaccount/web/pnpm-lock.yaml
  26. 53
      ngaccount/web/src/routes/Create.svelte
  27. 3
      ngaccount/web/src/routes/Delete.svelte
  28. 2
      p2p-broker/src/lib.rs
  29. 12
      p2p-broker/src/server_storage.rs
  30. 8
      p2p-broker/src/server_ws.rs
  31. 2
      p2p-net/src/actors/add_invitation.rs
  32. 2
      p2p-net/src/actors/add_user.rs
  33. 2
      p2p-net/src/actors/del_user.rs
  34. 2
      p2p-net/src/actors/list_invitations.rs
  35. 6
      p2p-net/src/actors/list_users.rs
  36. 44
      p2p-net/src/broker.rs
  37. 2
      p2p-net/src/lib.rs
  38. 2
      p2p-net/src/server_storage.rs
  39. 2
      p2p-repo/src/types.rs

2
Cargo.lock generated

@ -2785,7 +2785,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"async-std", "async-std",
"base64-url", "base64-url",
"crypto_box",
"futures", "futures",
"getrandom 0.1.16", "getrandom 0.1.16",
"gloo-timers", "gloo-timers",
@ -2816,6 +2815,7 @@ dependencies = [
"async-std", "async-std",
"base64-url", "base64-url",
"chacha20poly1305", "chacha20poly1305",
"crypto_box",
"getrandom 0.1.16", "getrandom 0.1.16",
"image", "image",
"lazy_static", "lazy_static",

@ -1,3 +1,6 @@
use std::collections::HashMap;
use std::fs::{read, write};
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers // Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved. // All rights reserved.
// Licensed under the Apache License, Version 2.0 // Licensed under the Apache License, Version 2.0
@ -10,11 +13,13 @@ use async_std::stream::StreamExt;
use ng_wallet::types::*; use ng_wallet::types::*;
use ng_wallet::*; use ng_wallet::*;
use p2p_net::broker::*; use p2p_net::broker::*;
use p2p_net::types::CreateAccountBSP; use p2p_net::types::{CreateAccountBSP, Invitation};
use p2p_net::utils::{spawn_and_log_error, Receiver, ResultSend}; use p2p_net::utils::{decode_invitation_string, spawn_and_log_error, Receiver, ResultSend};
use p2p_repo::log::*; use p2p_repo::log::*;
use p2p_repo::types::*; use p2p_repo::types::*;
use tauri::{App, Manager, Window}; use tauri::ipc::RemoteDomainAccessScope;
use tauri::utils::config::WindowConfig;
use tauri::{path::BaseDirectory, App, Manager, Window};
#[cfg(mobile)] #[cfg(mobile)]
mod mobile; mod mobile;
@ -61,19 +66,132 @@ async fn wallet_open_wallet_with_pazzle(
} }
#[tauri::command(rename_all = "snake_case")] #[tauri::command(rename_all = "snake_case")]
async fn wallet_create_wallet(mut params: CreateWalletV0) -> Result<CreateWalletResultV0, String> { async fn wallet_create_wallet(
mut params: CreateWalletV0,
app: tauri::AppHandle,
) -> Result<(CreateWalletResultV0, Option<SessionWalletStorageV0>), String> {
//log_debug!("wallet_create_wallet from rust {:?}", params); //log_debug!("wallet_create_wallet from rust {:?}", params);
params.result_with_wallet_file = false; params.result_with_wallet_file = !params.local_save;
let local_save = params.local_save; let local_save = params.local_save;
let res = create_wallet_v0(params).await.map_err(|e| e.to_string()); let res = create_wallet_v0(params).await.map_err(|e| e.to_string());
if res.is_ok() {
let mut cwr = res.unwrap();
if local_save { if local_save {
// TODO save in user store // save in local store
let session = save_wallet_locally(&cwr, app).await;
if session.is_err() {
return Err("Cannot save wallet locally".to_string());
}
return Ok((cwr, Some(session.unwrap())));
} else { } else {
// TODO save wallet file to Downloads folder // save wallet file to Downloads folder
let path = app
.path()
.resolve(
format!("wallet-{}.ngw", cwr.wallet_name),
BaseDirectory::Download,
)
.unwrap();
let _r = write(path, &cwr.wallet_file);
cwr.wallet_file = vec![];
return Ok((cwr, None));
}
}
Err(res.unwrap_err())
}
async fn save_wallet_locally(
res: &CreateWalletResultV0,
app: tauri::AppHandle,
) -> Result<SessionWalletStorageV0, ()> {
let path = app
.path()
.resolve("wallets", BaseDirectory::AppLocalData)
.map_err(|_| ())?;
let sws = save_new_session(&res.wallet_name, res.wallet.id(), res.user, app.clone())?;
let mut wallets: HashMap<String, LocalWalletStorageV0> = get_wallets_from_localstorage(app)
.await
.unwrap_or(Some(HashMap::new()))
.unwrap_or(HashMap::new());
// TODO: check that the wallet is not already present in localStorage
let lws: LocalWalletStorageV0 = res.into();
wallets.insert(res.wallet_name.clone(), lws);
let lws_ser = LocalWalletStorage::v0_to_vec(wallets);
let r = write(path.clone(), &lws_ser);
if r.is_err() {
log_debug!("write {:?} {}", path, r.unwrap_err());
return Err(());
}
Ok(sws)
}
#[tauri::command(rename_all = "snake_case")]
async fn get_wallets_from_localstorage(
app: tauri::AppHandle,
) -> Result<Option<HashMap<String, LocalWalletStorageV0>>, ()> {
let path = app
.path()
.resolve("wallets", BaseDirectory::AppLocalData)
.map_err(|_| ())?;
let map_ser = read(path);
if map_ser.is_ok() {
let wallets = LocalWalletStorage::v0_from_vec(&map_ser.unwrap());
let LocalWalletStorage::V0(v0) = wallets;
return Ok(Some(v0));
}
Ok(None)
}
fn save_new_session(
wallet_name: &String,
wallet_id: PubKey,
user: PubKey,
app: tauri::AppHandle,
) -> Result<SessionWalletStorageV0, ()> {
let mut path = app
.path()
.resolve("sessions", BaseDirectory::AppLocalData)
.map_err(|_| ())?;
let session_v0 = create_new_session(wallet_id, user);
if session_v0.is_err() {
log_debug!("create_new_session {}", session_v0.unwrap_err());
return Err(());
}
let sws = session_v0.unwrap();
std::fs::create_dir_all(path.clone()).unwrap();
path.push(wallet_name);
let res = write(path.clone(), &sws.1);
if res.is_err() {
log_debug!("write {:?} {}", path, res.unwrap_err());
return Err(());
}
Ok(sws.0)
}
#[tauri::command(rename_all = "snake_case")]
async fn get_local_session(
id: String,
key: PrivKey,
user: PubKey,
app: tauri::AppHandle,
) -> Result<SessionWalletStorageV0, ()> {
let path = app
.path()
.resolve(format!("sessions/{id}"), BaseDirectory::AppLocalData)
.map_err(|_| ())?;
let res = read(path.clone());
if res.is_ok() {
log_debug!("RESUMING SESSION");
let v0 = dec_session(key, &res.unwrap());
if v0.is_ok() {
return Ok(v0.unwrap());
}
} }
res // create a new session
let wallet_id: PubKey = id.as_str().try_into().unwrap();
save_new_session(&id, wallet_id, user, app)
} }
#[tauri::command(rename_all = "snake_case")] #[tauri::command(rename_all = "snake_case")]
@ -82,6 +200,34 @@ async fn encode_create_account(payload: CreateAccountBSP) -> Result<String, ()>
payload.encode().ok_or(()) payload.encode().ok_or(())
} }
#[tauri::command(rename_all = "snake_case")]
async fn open_window(
url: String,
label: String,
title: String,
app: tauri::AppHandle,
) -> Result<(), ()> {
log_debug!("open window url {:?}", url);
let already_exists = app.get_window(&label);
if (already_exists.is_some()) {
let _ = already_exists.unwrap().close();
std::thread::sleep(std::time::Duration::from_secs(1));
}
let mut config = WindowConfig::default();
config.label = label;
config.url = tauri::WindowUrl::External(url.parse().unwrap());
config.title = title;
let _register_window = tauri::WindowBuilder::from_config(&app, config)
.build()
.unwrap();
Ok(())
}
#[tauri::command(rename_all = "snake_case")]
async fn decode_invitation(invite: String) -> Option<Invitation> {
decode_invitation_string(invite)
}
#[tauri::command(rename_all = "snake_case")] #[tauri::command(rename_all = "snake_case")]
async fn doc_sync_branch(nuri: &str, stream_id: &str, app: tauri::AppHandle) -> Result<(), ()> { async fn doc_sync_branch(nuri: &str, stream_id: &str, app: tauri::AppHandle) -> Result<(), ()> {
log_debug!("doc_sync_branch {} {}", nuri, stream_id); log_debug!("doc_sync_branch {} {}", nuri, stream_id);
@ -157,6 +303,11 @@ pub struct AppBuilder {
setup: Option<SetupHook>, setup: Option<SetupHook>,
} }
#[cfg(debug_assertions)]
const ALLOWED_BSP_DOMAINS: [&str; 2] = ["account-dev.nextgraph.eu", "account-dev.nextgraph.net"];
#[cfg(not(debug_assertions))]
const ALLOWED_BSP_DOMAINS: [&str; 2] = ["account.nextgraph.eu", "account.nextgraph.net"];
impl AppBuilder { impl AppBuilder {
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
@ -178,7 +329,13 @@ impl AppBuilder {
if let Some(setup) = setup { if let Some(setup) = setup {
(setup)(app)?; (setup)(app)?;
} }
for domain in ALLOWED_BSP_DOMAINS {
app.ipc_scope().configure_remote_access(
RemoteDomainAccessScope::new(domain)
.add_window("registration")
.add_plugins(["window", "event"]),
);
}
Ok(()) Ok(())
}) })
.plugin(tauri_plugin_window::init()) .plugin(tauri_plugin_window::init())
@ -192,6 +349,10 @@ impl AppBuilder {
wallet_open_wallet_with_pazzle, wallet_open_wallet_with_pazzle,
wallet_create_wallet, wallet_create_wallet,
encode_create_account, encode_create_account,
get_local_session,
get_wallets_from_localstorage,
open_window,
decode_invitation,
]) ])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");

@ -11,7 +11,6 @@
"version": "0.1.0" "version": "0.1.0"
}, },
"tauri": { "tauri": {
"bundle": { "bundle": {
"active": true, "active": true,
"icon": [ "icon": [
@ -21,7 +20,7 @@
"icons/icon.icns", "icons/icon.icns",
"icons/icon.ico" "icons/icon.ico"
], ],
"identifier": "org.nextgraph.dev", "identifier": "org.nextgraph.app",
"targets": "all", "targets": "all",
"iOS": { "iOS": {
"developmentTeam": "test" "developmentTeam": "test"
@ -42,7 +41,8 @@
"resizable": true, "resizable": true,
"title": "NextGraph", "title": "NextGraph",
"width": 800, "width": 800,
"height": 980 "height": 1040,
"contentProtected": true
} }
] ]
} }

@ -46,30 +46,44 @@
let unsubscribe = () => {}; let unsubscribe = () => {};
let wallet_channel; let wallet_channel;
let unsub_main_close;
onMount(async () => { onMount(async () => {
let tauri_platform = import.meta.env.TAURI_PLATFORM; let tauri_platform = import.meta.env.TAURI_PLATFORM;
if (!tauri_platform) { if (tauri_platform) {
let walls = await ng.get_wallets_from_localstorage();
wallets.set(walls);
let window_api = await import("@tauri-apps/plugin-window");
let event_api = await import("@tauri-apps/api/event");
let main = window_api.WebviewWindow.getByLabel("main");
unsub_main_close = await main.onCloseRequested(async (event) => {
console.log("onCloseRequested main");
await event_api.emit("close_all", {});
let registration = window_api.WebviewWindow.getByLabel("registration");
if (registration) {
await registration.close();
}
let viewer = window_api.WebviewWindow.getByLabel("viewer");
if (viewer) {
await viewer.close();
}
});
} else {
window.addEventListener("storage", async (event) => { window.addEventListener("storage", async (event) => {
if (event.storageArea != localStorage) return; if (event.storageArea != localStorage) return;
if (event.key === "ng_wallets") { if (event.key === "ng_wallets") {
wallets.set( wallets.set(await ng.get_wallets_from_localstorage());
Object.fromEntries(await ng.get_wallets_from_localstorage())
);
} }
}); });
wallets.set( wallets.set(await ng.get_wallets_from_localstorage());
Object.fromEntries((await ng.get_wallets_from_localstorage()) || [])
);
wallet_channel = new BroadcastChannel("ng_wallet"); wallet_channel = new BroadcastChannel("ng_wallet");
wallet_channel.postMessage({ cmd: "is_opened" }, location.href); wallet_channel.postMessage({ cmd: "is_opened" }, location.href);
wallet_channel.onmessage = (event) => { wallet_channel.onmessage = (event) => {
console.log(event); console.log(event);
if (!location.href.startsWith(event.origin)) return; if (!location.href.startsWith(event.origin)) return;
console.log("ng_wallet", event.data);
switch (event.data.cmd) { switch (event.data.cmd) {
case "is_opened": case "is_opened":
console.log($active_wallet);
if ($active_wallet && $active_wallet.wallet) { if ($active_wallet && $active_wallet.wallet) {
wallet_channel.postMessage( wallet_channel.postMessage(
{ cmd: "opened", wallet: $active_wallet }, { cmd: "opened", wallet: $active_wallet },
@ -128,7 +142,10 @@
} }
}); });
onDestroy(unsubscribe); onDestroy(() => {
unsubscribe();
if (unsub_main_close) unsub_main_close();
});
</script> </script>
<main class=""> <main class="">

@ -19,6 +19,10 @@ const mapping = {
"wallet_open_wallet_with_pazzle": ["wallet","pazzle","pin"], "wallet_open_wallet_with_pazzle": ["wallet","pazzle","pin"],
"wallet_create_wallet": ["params"], "wallet_create_wallet": ["params"],
"encode_create_account": ["payload"], "encode_create_account": ["payload"],
"get_local_session": ["id","key","user"],
"get_wallets_from_localstorage": [],
"open_window": ["url","label","title"],
"decode_invitation": ["invite"],
"test": [ ] "test": [ ]
} }
@ -34,6 +38,23 @@ const handler = {
client_info.version=version; client_info.version=version;
//console.log(client_info); //console.log(client_info);
return client_info; return client_info;
} else if (path[0] === "get_wallets_from_localstorage") {
let wallets = await Reflect.apply(sdk[path], caller, args);
return Object.fromEntries(wallets || []);
} else if (path[0] === "get_local_session") {
let res = await Reflect.apply(sdk[path], caller, args);
let v = res.users.values().next().value;
v.branches_last_seq = Object.fromEntries(v.branches_last_seq);
res.users = Object.fromEntries(res.users);
return res;
} else if (path[0] === "wallet_create_wallet") {
let res = await Reflect.apply(sdk[path], caller, args);
if (res[1]) {
let v = res[1].users.values().next().value;
v.branches_last_seq = Object.fromEntries(v.branches_last_seq);
res[1].users = Object.fromEntries(res[1].users);
}
return res;
} else { } else {
return Reflect.apply(sdk[path], caller, args) return Reflect.apply(sdk[path], caller, args)
} }
@ -88,16 +109,28 @@ const handler = {
let res = await tauri.invoke(path[0],arg); let res = await tauri.invoke(path[0],arg);
res['File'].V0.content = Uint8Array.from(res['File'].V0.content); res['File'].V0.content = Uint8Array.from(res['File'].V0.content);
res['File'].V0.metadata = Uint8Array.from(res['File'].V0.metadata); res['File'].V0.metadata = Uint8Array.from(res['File'].V0.metadata);
return res return res;
} else if (path[0] === "get_wallets_from_localstorage") {
let res = await tauri.invoke(path[0],{});
for (let e of Object.entries(res)) {
e[1].wallet.V0.content.security_img = Uint8Array.from(e[1].wallet.V0.content.security_img);
}
return res;
} else if (path[0] === "wallet_create_wallet") { } else if (path[0] === "wallet_create_wallet") {
let params = args[0]; let params = args[0];
params.result_with_wallet_file = false; params.result_with_wallet_file = false;
params.security_img = Array.from(new Uint8Array(params.security_img)); params.security_img = Array.from(new Uint8Array(params.security_img));
return await tauri.invoke(path[0],{params}) return await tauri.invoke(path[0],{params})
} else if (path[0].starts_with("get_local_bootstrap")) { } else if (path[0] && path[0].startsWith("get_local_bootstrap")) {
return false; return false;
} else if (path[0].starts_with("get_local_url")) { } else if (path[0] === "get_local_url") {
return false; return false;
} else if (path[0] === "wallet_open_wallet_with_pazzle") {
let arg = {};
args.map((el,ix) => arg[mapping[path[0]][ix]]=el)
arg.wallet.V0.content.security_img = Array.from(new Uint8Array(arg.wallet.V0.content.security_img));
return tauri.invoke(path[0],arg)
} }
else { else {
let arg = {}; let arg = {};
@ -111,15 +144,20 @@ const handler = {
const api = createAsyncProxy({}, handler); const api = createAsyncProxy({}, handler);
export const NG_EU_BSP = "https://nextgraph.eu"; export const NG_EU_BSP = "https://nextgraph.eu";
export const NG_EU_BSP_REGISTER = "https://account.nextgraph.eu/#/create"; export const NG_EU_BSP_REGISTER = import.meta.env.PROD
export const NG_EU_BSP_REGISTERED = "https://nextgraph.eu/#/user/registered"; ? "https://account.nextgraph.eu/#/create"
: "http://account-dev.nextgraph.eu:5173/#/create";
export const NG_NET_BSP = "https://nextgraph.net";
export const NG_NET_BSP_REGISTER = import.meta.env.PROD
? "https://account.nextgraph.net/#/create"
: "http://account-dev.nextgraph.net:5173/#/create";
export const APP_ACCOUNT_REGISTERED_SUFFIX = "/#/user/registered"; export const APP_ACCOUNT_REGISTERED_SUFFIX = "/#/user/registered";
export const APP_WALLET_CREATE_SUFFIX = "/#/wallet/create"; export const APP_WALLET_CREATE_SUFFIX = "/#/wallet/create";
export const NG_NET_BSP = "https://nextgraph.net"; export const LINK_NG_BOX = "https://nextgraph.org/ng-box/";
export const NG_NET_BSP_REGISTER = "https://account.nextgraph.net/#/create"; export const LINK_SELF_HOST = "https://nextgraph.org/self-host/";
export const NG_NET_BSP_REGISTERED = "https://nextgraph.net/#/user/registered";
export default api; export default api;

@ -14,13 +14,14 @@
import { link } from "svelte-spa-router"; import { link } from "svelte-spa-router";
// @ts-ignore // @ts-ignore
import Logo from "../assets/nextgraph.svg?component"; import Logo from "../assets/nextgraph.svg?component";
import { has_wallets } from "../store"; import Test from "./Test.svelte";
import { has_wallets, active_wallet } from "../store";
import { onMount } from "svelte"; import { onMount } from "svelte";
export let display_login_create = false; export let display_login_create = false;
</script> </script>
{#if !$has_wallets || display_login_create} {#if !$has_wallets || !$active_wallet || display_login_create}
<main class="container3"> <main class="container3">
<div class="row"> <div class="row">
<Logo class="logo block h-40" alt="NextGraph Logo" /> <Logo class="logo block h-40" alt="NextGraph Logo" />
@ -82,4 +83,11 @@
</a> </a>
</div> </div>
</main> </main>
{:else}
<main class="container3">
<h1>Welcome to test</h1>
<div class="row">
<Test />
</div>
</main>
{/if} {/if}

@ -20,56 +20,8 @@
onMount(async () => { onMount(async () => {
await load_svg(); await load_svg();
console.log(wallet);
await init(); await init();
//console.log(JSON.stringify(await ng.test_create_wallet()));
//console.log(await ng.test_create_wallet());
// let ref = {
// id: {
// Blake3Digest32: [
// 228, 228, 181, 117, 36, 206, 41, 223, 130, 96, 85, 195, 104, 137, 78,
// 145, 42, 176, 58, 244, 111, 97, 246, 39, 11, 76, 135, 150, 188, 111,
// 66, 33,
// ],
// },
// key: {
// ChaCha20Key: [
// 100, 243, 39, 242, 203, 131, 102, 50, 9, 54, 248, 113, 4, 160, 28, 45,
// 73, 56, 217, 112, 95, 150, 144, 137, 9, 57, 106, 5, 39, 202, 146, 94,
// ],
// },
// };
// let img = await ng.doc_get_file_from_store_with_object_ref("ng:", ref);
// let c = {
// security_img: img["File"].V0.content,
// security_txt: " know yourself ",
// pin: [5, 2, 9, 1],
// pazzle_length: 9,
// send_bootstrap: false,
// send_wallet: false,
// result_with_wallet_file: true,
// local_save: false,
// };
// try {
// let res = await ng.wallet_create_wallet(c);
// console.log(res);
// wallet = res.wallet;
// for (const emoji of res.pazzle) {
// let cat = (emoji & 240) >> 4;
// let idx = emoji & 15;
// console.log(emoji_cat[cat], emojis[emoji_cat[cat]][idx].code);
// }
// } catch (e) {
// console.error(e);
// }
//await start_pin();
}); });
async function init() { async function init() {
@ -159,6 +111,7 @@
} }
console.log(pazzle); console.log(pazzle);
console.log(wallet);
// open the wallet // open the wallet
try { try {
@ -168,7 +121,6 @@
pin_code pin_code
); );
step = "end"; step = "end";
console.log(secret_wallet);
dispatch("opened", { dispatch("opened", {
wallet: secret_wallet, wallet: secret_wallet,
id: secret_wallet.V0.wallet_id, id: secret_wallet.V0.wallet_id,
@ -239,7 +191,7 @@
</svg> </svg>
</div> </div>
{:else if step == "pazzle"} {:else if step == "pazzle"}
<div class="h-screen aspect-[3/5] pazzleline max-w-[500px] min-w-[200px]"> <div class="h-screen aspect-[3/5] pazzleline max-w-[600px] min-w-[500px]">
{#each [0, 1, 2, 3, 4] as row} {#each [0, 1, 2, 3, 4] as row}
<div class="columns-3 gap-0"> <div class="columns-3 gap-0">
{#each emojis2[display]?.slice(0 + row * 3, 3 + row * 3) || [] as emoji, i} {#each emojis2[display]?.slice(0 + row * 3, 3 + row * 3) || [] as emoji, i}
@ -258,7 +210,7 @@
</div> </div>
{:else if step == "order"} {:else if step == "order"}
<!-- console.log(cat_idx, emoji_cat[cat_idx], idx, cat[idx].code); --> <!-- console.log(cat_idx, emoji_cat[cat_idx], idx, cat[idx].code); -->
<div class="h-screen aspect-[3/3] pazzleline max-w-[500px] min-w-[200px]"> <div class="h-screen aspect-[3/3] pazzleline max-w-[600px] min-w-[500px]">
{#each [0, 1, 2] as row} {#each [0, 1, 2] as row}
<div class="columns-3 gap-0"> <div class="columns-3 gap-0">
{#each selection.slice(0 + row * 3, 3 + row * 3) || [] as emoji, i} {#each selection.slice(0 + row * 3, 3 + row * 3) || [] as emoji, i}
@ -401,14 +353,17 @@
} }
.sel { .sel {
position: relative; position: absolute;
top: -56%; width: 100%;
top: 45%;
left: 0;
font-size: 100px; font-size: 100px;
font-weight: 700; font-weight: 700;
} }
.sel-emoji { .sel-emoji {
overflow: hidden; /* overflow: hidden; */
position: relative;
} }
.emoji { .emoji {

@ -27,7 +27,7 @@
try { try {
//console.log(JSON.stringify(ref)); //console.log(JSON.stringify(ref));
let file = await ng.doc_get_file_from_store_with_object_ref("ng:", ref); let file = await ng.doc_get_file_from_store_with_object_ref("ng:", ref);
console.log(file); //console.log(file);
var blob = new Blob([file["File"].V0.content], { var blob = new Blob([file["File"].V0.content], {
type: file["File"].V0.content_type, type: file["File"].V0.content_type,
}); });
@ -75,10 +75,10 @@
</script> </script>
<div> <div>
<div class="row"> <!-- <div class="row">
<input id="greet-input" placeholder="Enter a name..." bind:value={name} /> <input id="greet-input" placeholder="Enter a name..." bind:value={name} />
<button on:click={greet}> Greet </button> <button on:click={greet}> Greet </button>
</div> </div> -->
<div class="row mt-2"> <div class="row mt-2">
<button <button
type="button" type="button"
@ -112,7 +112,7 @@
bind:this={fileinput} bind:this={fileinput}
/> />
</div> </div>
<p>{greetMsg}</p> <!-- <p>{greetMsg}</p> -->
{#await commits.load()} {#await commits.load()}
<p>Currently loading...</p> <p>Currently loading...</p>
{:then} {:then}

@ -31,7 +31,7 @@
$s2, $s2,
]); ]);
unsubscribe = combined.subscribe((value) => { unsubscribe = combined.subscribe((value) => {
console.log(value); //console.log(value);
if (!value[0] && value[1]) { if (!value[0] && value[1]) {
push("#/wallet/login"); push("#/wallet/login");
} }

@ -10,14 +10,14 @@
--> -->
<script lang="ts"> <script lang="ts">
import Greet from "../lib/Greet.svelte"; import Test from "../lib/Test.svelte";
export let params = {}; export let params = {};
</script> </script>
<main class="container3"> <main class="container3">
<h1>Welcome to test</h1> <h1>Welcome to test</h1>
<div class="row"> <div class="row">
<Greet /> <Test />
</div> </div>
</main> </main>

@ -20,8 +20,6 @@
import { import {
NG_EU_BSP, NG_EU_BSP,
NG_NET_BSP, NG_NET_BSP,
NG_EU_BSP_REGISTER,
NG_EU_BSP_REGISTERED,
APP_ACCOUNT_REGISTERED_SUFFIX, APP_ACCOUNT_REGISTERED_SUFFIX,
default as ng, default as ng,
} from "../api"; } from "../api";

@ -19,14 +19,16 @@
import { import {
NG_EU_BSP, NG_EU_BSP,
NG_NET_BSP, NG_NET_BSP,
LINK_NG_BOX,
LINK_SELF_HOST,
NG_EU_BSP_REGISTER, NG_EU_BSP_REGISTER,
NG_EU_BSP_REGISTERED, NG_NET_BSP_REGISTER,
APP_WALLET_CREATE_SUFFIX, APP_WALLET_CREATE_SUFFIX,
default as ng, default as ng,
} from "../api"; } from "../api";
import { display_pazzle } from "../wallet_emojis"; import { display_pazzle } from "../wallet_emojis";
import { onMount, tick } from "svelte"; import { onMount, onDestroy, tick } from "svelte";
import { wallets, set_active_session, has_wallets } from "../store"; import { wallets, set_active_session, has_wallets } from "../store";
const param = new URLSearchParams($querystring); const param = new URLSearchParams($querystring);
@ -108,6 +110,10 @@
let animateDownload = true; let animateDownload = true;
let invitation; let invitation;
let unsub_register_accepted;
let unsub_register_error;
let unsub_register_close;
function scrollToTop() { function scrollToTop() {
top.scrollIntoView(); top.scrollIntoView();
} }
@ -202,7 +208,7 @@
pazzle_length: 9, pazzle_length: 9,
send_bootstrap: false, //options.cloud || options.bootstrap ? : undefined, send_bootstrap: false, //options.cloud || options.bootstrap ? : undefined,
send_wallet: options.cloud, send_wallet: options.cloud,
local_save: options.trusted, // this is only used for tauri apps local_save: options.trusted,
result_with_wallet_file: false, // this will be automatically changed to true for browser app result_with_wallet_file: false, // this will be automatically changed to true for browser app
core_bootstrap: invitation.V0.bootstrap, core_bootstrap: invitation.V0.bootstrap,
core_registration, core_registration,
@ -211,10 +217,8 @@
console.log(params); console.log(params);
try { try {
let res = await ng.wallet_create_wallet(params); let res = await ng.wallet_create_wallet(params);
console.log(res); let walls = await ng.get_wallets_from_localstorage();
wallets.set( wallets.set(walls);
Object.fromEntries((await ng.get_wallets_from_localstorage()) || [])
);
if (res[1]) { if (res[1]) {
set_active_session(res[1]); set_active_session(res[1]);
} }
@ -227,26 +231,9 @@
if (ready.wallet_file.length) { if (ready.wallet_file.length) {
const blob = new Blob([ready.wallet_file]); const blob = new Blob([ready.wallet_file]);
download_link = URL.createObjectURL(blob); download_link = URL.createObjectURL(blob);
// we also save the wallet to localStorage here, and only if options.trusted is true
// indeed if a wallet_file is sent in the result, it means we are not on tauri app
// therefor we are on a web-browser.
if (options.trusted) {
//TODO save in localStorage
}
} }
} catch (e) { } catch (e) {
console.log(e); console.log(e);
if (
e == "The operation is insecure." ||
e ==
"Failed to read the 'sessionStorage' property from 'Window': Access is denied for this document." ||
e ==
"Failed to read the 'localStorage' property from 'Window': Access is denied for this document."
) {
e =
"Please allow this website to store cookies, session and local storage.";
}
error = e; error = e;
} }
} }
@ -276,16 +263,32 @@
// }, // },
// }; // };
const selectEU = async (event) => { const unsub_register = () => {
if (unsub_register_accepted) unsub_register_accepted();
if (unsub_register_error) unsub_register_error();
if (unsub_register_close) unsub_register_close();
unsub_register_close = undefined;
unsub_register_error = undefined;
unsub_register_accepted = undefined;
};
onDestroy(() => {
unsub_register();
});
const select_bsp = async (bsp_url, bsp_name) => {
if (!tauri_platform) { if (!tauri_platform) {
let local_url = await ng.get_local_url(location.href); let local_url = await ng.get_local_url(location.href);
if (!import.meta.env.PROD) {
local_url = "http://localhost:1421";
}
let create = { let create = {
V0: { V0: {
redirect_url: local_url + APP_WALLET_CREATE_SUFFIX, redirect_url: local_url + APP_WALLET_CREATE_SUFFIX,
}, },
}; };
let ca = await ng.encode_create_account(create); let ca = await ng.encode_create_account(create);
window.location.href = NG_EU_BSP_REGISTER + "?ca=" + ca; window.location.href = bsp_url + "?ca=" + ca;
//window.open(), "_self").focus(); //window.open(), "_self").focus();
} else { } else {
let create = { let create = {
@ -294,14 +297,60 @@
}, },
}; };
let ca = await ng.encode_create_account(create); let ca = await ng.encode_create_account(create);
// TODO: open window with registration URL : NG_EU_BSP_REGISTER + "?ca=" + ca; await ng.open_window(
bsp_url + "?ca=" + ca,
"registration",
"Registration at a Broker"
);
let window_api = await import("@tauri-apps/plugin-window");
let event_api = await import("@tauri-apps/api/event");
let reg_popup = window_api.WebviewWindow.getByLabel("registration");
unsub_register_accepted = await event_api.listen(
"accepted",
async (event) => {
console.log("got accepted with payload", event.payload);
unsub_register();
await reg_popup.close();
registration_success = bsp_name;
invitation = await ng.decode_invitation(event.payload.invite);
}
);
unsub_register_error = await event_api.listen("error", async (event) => {
console.log("got error with payload", event.payload);
if (event.payload) registration_error = event.payload.error;
else intro = true;
unsub_register();
await reg_popup.close();
});
unsub_register_close = await reg_popup.onCloseRequested(async (event) => {
console.log("onCloseRequested");
intro = true;
unsub_register();
});
} }
}; };
const selectNET = (event) => {}; const selectEU = async (event) => {
await select_bsp(NG_EU_BSP_REGISTER, "nextgraph.eu");
};
const selectNET = async (event) => {
await select_bsp(NG_NET_BSP_REGISTER, "nextgraph.net");
};
const enterINVITE = (event) => {}; const enterINVITE = (event) => {};
const enterQRcode = (event) => {}; const enterQRcode = (event) => {};
const displayNGbox = (event) => {}; const displayNGbox = async (event) => {
const displaySelfHost = (event) => {}; if (!tauri_platform) {
window.open(LINK_NG_BOX, "_blank").focus();
} else {
await ng.open_window(LINK_NG_BOX, "viewer", "Own your NG-Box");
}
};
const displaySelfHost = async (event) => {
if (!tauri_platform) {
window.open(LINK_SELF_HOST, "_blank").focus();
} else {
await ng.open_window(LINK_SELF_HOST, "viewer", "Self-host a broker");
}
};
</script> </script>
<main class="container3" bind:this={top}> <main class="container3" bind:this={top}>
@ -869,7 +918,7 @@
{/if} {/if}
<div class="row mt-5"> <div class="row mt-5">
<button <button
on:click|once={displaySelfHost} on:click={displaySelfHost}
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2" class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
> >
<svg <svg
@ -892,7 +941,7 @@
</div> </div>
<div class="row mt-5 mb-12"> <div class="row mt-5 mb-12">
<button <button
on:click|once={displayNGbox} on:click={displayNGbox}
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2" class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
> >
<svg <svg
@ -921,7 +970,7 @@
{:else if pin.length < 4} {:else if pin.length < 4}
<div class=" max-w-6xl lg:px-8 mx-auto px-4"> <div class=" max-w-6xl lg:px-8 mx-auto px-4">
{#if registration_success} {#if registration_success}
<Alert color="green" class="mt-5 mb-5"> <Alert color="green" class="mb-5">
<span class="font-bold text-xl" <span class="font-bold text-xl"
>You have been successfully registered to {registration_success}</span >You have been successfully registered to {registration_success}</span
> >
@ -934,8 +983,8 @@
<Alert color="yellow" class="mt-5"> <Alert color="yellow" class="mt-5">
We recommend you to choose a PIN code that you already know very well. We recommend you to choose a PIN code that you already know very well.
<br /> <br />
Your credit card PIN, by example, is a good choice. (We at NextGraph will Your credit card PIN, by example, is a good choice.<br />We at
never see your PIN) NextGraph will never see your PIN.
</Alert> </Alert>
</p> </p>
<p class="text-left mt-5">Here are the rules for the PIN :</p> <p class="text-left mt-5">Here are the rules for the PIN :</p>
@ -955,7 +1004,7 @@
>{digit}</span >{digit}</span
>{/each} >{/each}
</Alert> </Alert>
<div class="w-[325px] mx-auto"> <div class="w-[325px] mx-auto mb-4">
{#each [0, 1, 2] as row} {#each [0, 1, 2] as row}
<div class=""> <div class="">
{#each [1, 2, 3] as num} {#each [1, 2, 3] as num}
@ -1202,7 +1251,7 @@
<span class="text-xl">Create a PDF file of your wallet? </span> <br /> <span class="text-xl">Create a PDF file of your wallet? </span> <br />
We can prepare for you a PDF file containing all the information of your We can prepare for you a PDF file containing all the information of your
wallet, unencrypted. You should print this file and then delete the PDF (and wallet, unencrypted. You should print this file and then delete the PDF (and
empty the trash). Keep this printed documented in a safe place. It contains empty the trash). Keep this printed document in a safe place. It contains
all the information to regenerate your wallet in case you lost access to all the information to regenerate your wallet in case you lost access to
it. it.
<br /> <br />

@ -25,7 +25,7 @@
has_wallets, has_wallets,
} from "../store"; } from "../store";
let wallet; let wallet;
let selected; //= "8Hg1Rf7us3LFhs7HCbxCQOWJoV-OUyALbTuSaKp7D-M"; let selected;
let step; let step;
let wallets_unsub; let wallets_unsub;
@ -64,6 +64,8 @@
set_active_session(session); set_active_session(session);
loggedin(); loggedin();
} }
} else {
loggedin();
} }
} }
}); });
@ -112,7 +114,7 @@
<div class="row"> <div class="row">
<Logo class="logo block h-40" alt="NextGraph Logo" /> <Logo class="logo block h-40" alt="NextGraph Logo" />
</div> </div>
<h2 class="pb-5 text-xl">Select a wallet for log in</h2> <h2 class="pb-5 text-xl">Select a wallet to login with</h2>
<div class="flex flex-wrap justify-center gap-5 mb-20"> <div class="flex flex-wrap justify-center gap-5 mb-20">
{#each Object.entries($wallets) as wallet_entry} {#each Object.entries($wallets) as wallet_entry}
<div <div

@ -15,10 +15,7 @@ export const has_wallets = derived(wallets,($wallets) => Object.keys($wallets).l
export const active_session = writable(undefined); export const active_session = writable(undefined);
export const set_active_session = function(session) { export const set_active_session = function(session) {
let v = session.users.values().next().value; active_session.set(session.users);
v.branches_last_seq = Object.fromEntries(v.branches_last_seq);
let users = Object.fromEntries(session.users);
active_session.set(users);
}; };
export { writable, readonly, derived }; export { writable, readonly, derived };
@ -72,6 +69,7 @@ const branch_commits = (nura, sub) => {
} }
}, },
subscribe: (run, invalid) => { subscribe: (run, invalid) => {
let already_subscribed = all_branches[nura]; let already_subscribed = all_branches[nura];
if (!already_subscribed) { if (!already_subscribed) {
const { subscribe, set, update } = writable([]); // create the underlying writable store const { subscribe, set, update } = writable([]); // create the underlying writable store
@ -80,9 +78,11 @@ const branch_commits = (nura, sub) => {
already_subscribed = { already_subscribed = {
load: async () => { load: async () => {
unsub = await ng.doc_sync_branch(nura, async (commit) => { unsub = await ng.doc_sync_branch(nura, async (commit) => {
console.log(commit); console.log("GOT COMMIT", commit);
update( (old) => {old.unshift(commit); return old;} ) update( (old) => {old.unshift(commit); return old;} )
}); });
// this is in case decrease has been called before the load function returned.
if (count == 0) {unsub();}
}, },
increase: () => { increase: () => {
count += 1; count += 1;

@ -29,7 +29,6 @@ serde_bytes = "0.11.7"
# snow = "0.9.2" # snow = "0.9.2"
getrandom = { version = "0.1.1", features = ["wasm-bindgen"] } getrandom = { version = "0.1.1", features = ["wasm-bindgen"] }
serde_json = "1.0" serde_json = "1.0"
crypto_box = { version = "0.8.2", features = ["seal"] }
rand = { version = "0.7", features = ["getrandom"] } rand = { version = "0.7", features = ["getrandom"] }
base64-url = "2.0.0" base64-url = "2.0.0"

@ -20,15 +20,12 @@
<script type="module"> <script type="module">
import init, { start, test } from "./web/ng_sdk_js.js"; import init, { start, test } from "./web/ng_sdk_js.js";
init().then(() => { init().then(() => {
//greet("WebAssembly");
test();
start(); start();
test();
}); });
// DON'T DO THE FOLLOWING: // DON'T DO THE FOLLOWING:
// it will instantiate twice the SDK, which is not what we want // it will instantiate twice the SDK, which is not what we want
// init().then(() => { // init().then(() => {
// test(); // start();
// }); // });
</script> </script>
</body> </body>

@ -18,7 +18,21 @@ export function session_save(key,value) {
} catch(e) { } catch(e) {
console.error(e); console.error(e);
return e.message; return convert_error(e.message);
}
}
function convert_error(e) {
if (
e == "The operation is insecure." ||
e ==
"Failed to read the 'sessionStorage' property from 'Window': Access is denied for this document." ||
e ==
"Failed to read the 'localStorage' property from 'Window': Access is denied for this document."
) {
return "Please allow this website to store cookies, session and local storage.";
} else {
return e
} }
} }
@ -39,7 +53,7 @@ export function local_save(key,value) {
} catch(e) { } catch(e) {
console.error(e); console.error(e);
return e.message; return convert_error(e.message);
} }
} }

@ -143,46 +143,6 @@ pub fn wallet_update(js_wallet_id: JsValue, js_operations: JsValue) -> Result<Js
// } // }
} }
#[derive(Clone, Debug, Serialize, Deserialize)]
struct SessionWalletStorageV0 {
// string is base64_url encoding of userId(pubkey)
users: HashMap<String, SessionPeerStorageV0>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
enum SessionWalletStorage {
V0(SessionWalletStorageV0),
}
impl SessionWalletStorageV0 {
fn new() -> Self {
SessionWalletStorageV0 {
users: HashMap::new(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct SessionPeerStorageV0 {
user: UserId,
peer_key: PrivKey,
last_wallet_nonce: u64,
// string is base64_url encoding of branchId(pubkey)
branches_last_seq: HashMap<String, u64>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct LocalWalletStorageV0 {
bootstrap: BootstrapContent,
wallet: Wallet,
client: ClientId,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
enum LocalWalletStorage {
V0(HashMap<String, LocalWalletStorageV0>),
}
fn get_local_wallets_v0() -> Result<HashMap<String, LocalWalletStorageV0>, ()> { fn get_local_wallets_v0() -> Result<HashMap<String, LocalWalletStorageV0>, ()> {
let wallets_string = local_get("ng_wallets".to_string()); let wallets_string = local_get("ng_wallets".to_string());
if wallets_string.is_some() { if wallets_string.is_some() {
@ -205,6 +165,25 @@ pub fn get_wallets_from_localstorage() -> JsValue {
JsValue::UNDEFINED JsValue::UNDEFINED
} }
fn save_new_session(
wallet_name: &String,
wallet_id: PubKey,
user: PubKey,
) -> Result<SessionWalletStorageV0, String> {
let res = create_new_session(wallet_id, user);
if res.is_ok() {
let sws = res.unwrap();
let encoded = base64_url::encode(&sws.1);
let r = session_save(format!("ng_wallet@{}", wallet_name), encoded);
if r.is_some() {
return Err(r.unwrap());
}
Ok(sws.0)
} else {
Err(res.unwrap_err().to_string())
}
}
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[wasm_bindgen] #[wasm_bindgen]
pub fn get_local_session(id: String, key_js: JsValue, user_js: JsValue) -> JsValue { pub fn get_local_session(id: String, key_js: JsValue, user_js: JsValue) -> JsValue {
@ -213,78 +192,28 @@ pub fn get_local_session(id: String, key_js: JsValue, user_js: JsValue) -> JsVal
log_debug!("RESUMING SESSION"); log_debug!("RESUMING SESSION");
let key = serde_wasm_bindgen::from_value::<PrivKey>(key_js).unwrap(); let key = serde_wasm_bindgen::from_value::<PrivKey>(key_js).unwrap();
let decoded = base64_url::decode(&res.unwrap()).unwrap(); let decoded = base64_url::decode(&res.unwrap()).unwrap();
let session_ser = crypto_box::seal_open(&(*key.to_dh().slice()).into(), &decoded).unwrap(); let v0 = dec_session(key, &decoded);
let session: SessionWalletStorage = serde_bare::from_slice(&session_ser).unwrap(); if v0.is_ok() {
let SessionWalletStorage::V0(v0) = session; return serde_wasm_bindgen::to_value(&v0.unwrap()).unwrap();
return serde_wasm_bindgen::to_value(&v0).unwrap(); }
} else { }
// create a new session // create a new session
let user = serde_wasm_bindgen::from_value::<PubKey>(user_js).unwrap(); let user = serde_wasm_bindgen::from_value::<PubKey>(user_js).unwrap();
let wallet_id: PubKey = id.as_str().try_into().unwrap(); let wallet_id: PubKey = id.as_str().try_into().unwrap();
let session_v0 = create_new_session(&id, wallet_id, user); let session_v0 = save_new_session(&id, wallet_id, user);
if session_v0.is_err() { if session_v0.is_err() {
return JsValue::UNDEFINED; return JsValue::UNDEFINED;
} }
return serde_wasm_bindgen::to_value(&session_v0.unwrap()).unwrap(); return serde_wasm_bindgen::to_value(&session_v0.unwrap()).unwrap();
} }
JsValue::UNDEFINED
}
fn create_new_session(
wallet_name: &String,
wallet_id: PubKey,
user: PubKey,
) -> Result<SessionWalletStorageV0, String> {
let peer = generate_keypair();
let mut sws = SessionWalletStorageV0::new();
let sps = SessionPeerStorageV0 {
user,
peer_key: peer.0,
last_wallet_nonce: 0,
branches_last_seq: HashMap::new(),
};
sws.users.insert(user.to_string(), sps);
let sws_ser = serde_bare::to_vec(&SessionWalletStorage::V0(sws.clone())).unwrap();
let mut rng = crypto_box::aead::OsRng {};
let cipher = crypto_box::seal(&mut rng, &wallet_id.to_dh_slice().into(), &sws_ser);
if cipher.is_ok() {
let encoded = base64_url::encode(&cipher.unwrap());
let r = session_save(format!("ng_wallet@{}", wallet_name), encoded);
if r.is_some() {
return Err(r.unwrap());
}
}
Ok(sws)
}
fn save_wallet_locally(res: &CreateWalletResultV0) -> Result<SessionWalletStorageV0, String> { fn save_wallet_locally(res: &CreateWalletResultV0) -> Result<SessionWalletStorageV0, String> {
// let mut sws = SessionWalletStorageV0::new(); let sws = save_new_session(&res.wallet_name, res.wallet.id(), res.user)?;
// let sps = SessionPeerStorageV0 {
// user: res.user,
// peer_key: res.peer_key.clone(),
// last_wallet_nonce: res.nonce,
// branches_last_seq: HashMap::new(),
// };
// sws.users.insert(res.user.to_string(), sps);
// let sws_ser = serde_bare::to_vec(&SessionWalletStorage::V0(sws.clone())).unwrap();
// let mut rng = crypto_box::aead::OsRng {};
// let cipher = crypto_box::seal(&mut rng, &res.wallet.id().to_dh_slice().into(), &sws_ser);
// if cipher.is_ok() {
// let encoded = base64_url::encode(&cipher.unwrap());
// let r = session_save(format!("ng_wallet@{}", res.wallet_name), encoded);
// if r.is_some() {
// return Err(r.unwrap());
// }
// }
let sws = create_new_session(&res.wallet_name, res.wallet.id(), res.user)?;
let mut wallets: HashMap<String, LocalWalletStorageV0> = let mut wallets: HashMap<String, LocalWalletStorageV0> =
get_local_wallets_v0().unwrap_or(HashMap::new()); get_local_wallets_v0().unwrap_or(HashMap::new());
// TODO: check that the wallet is not already present in localStorage // TODO: check that the wallet is not already present in localStorage
let lws = LocalWalletStorageV0 { let lws: LocalWalletStorageV0 = res.into();
bootstrap: BootstrapContent::V0(BootstrapContentV0 { servers: vec![] }),
wallet: res.wallet.clone(),
client: res.client.priv_key.to_pub(),
};
wallets.insert(res.wallet_name.clone(), lws); wallets.insert(res.wallet_name.clone(), lws);
let lws_ser = serde_bare::to_vec(&LocalWalletStorage::V0(wallets)).unwrap(); let lws_ser = serde_bare::to_vec(&LocalWalletStorage::V0(wallets)).unwrap();
let encoded = base64_url::encode(&lws_ser); let encoded = base64_url::encode(&lws_ser);

@ -28,3 +28,4 @@ async-std = { version = "1.12.0", features = ["attributes","unstable"] }
web-time = "0.2.0" web-time = "0.2.0"
lazy_static = "1.4.0" lazy_static = "1.4.0"
zeroize = { version = "1.6.0", features = ["zeroize_derive"] } zeroize = { version = "1.6.0", features = ["zeroize_derive"] }
crypto_box = { version = "0.8.2", features = ["seal"] }

@ -35,9 +35,9 @@ use image::{imageops::FilterType, io::Reader as ImageReader, ImageOutputFormat};
use safe_transmute::transmute_to_bytes; use safe_transmute::transmute_to_bytes;
use p2p_net::types::{SiteType, SiteV0}; use p2p_net::types::{SiteType, SiteV0};
use p2p_repo::log::*;
use p2p_repo::types::{PubKey, Timestamp}; use p2p_repo::types::{PubKey, Timestamp};
use p2p_repo::utils::{generate_keypair, now_timestamp, sign, verify}; use p2p_repo::utils::{generate_keypair, now_timestamp, sign, verify};
use p2p_repo::{log::*, types::PrivKey};
use rand::prelude::*; use rand::prelude::*;
use serde_bare::{from_slice, to_vec}; use serde_bare::{from_slice, to_vec};
use web_time::Instant; use web_time::Instant;
@ -130,6 +130,35 @@ pub fn enc_wallet_log(
Ok(buffer) Ok(buffer)
} }
pub fn dec_session(key: PrivKey, vec: &Vec<u8>) -> Result<SessionWalletStorageV0, NgWalletError> {
let session_ser = crypto_box::seal_open(&(*key.to_dh().slice()).into(), vec)
.map_err(|_| NgWalletError::DecryptionError)?;
let session: SessionWalletStorage =
serde_bare::from_slice(&session_ser).map_err(|_| NgWalletError::SerializationError)?;
let SessionWalletStorage::V0(v0) = session;
Ok(v0)
}
pub fn create_new_session(
wallet_id: PubKey,
user: PubKey,
) -> Result<(SessionWalletStorageV0, Vec<u8>), NgWalletError> {
let peer = generate_keypair();
let mut sws = SessionWalletStorageV0::new();
let sps = SessionPeerStorageV0 {
user,
peer_key: peer.0,
last_wallet_nonce: 0,
branches_last_seq: HashMap::new(),
};
sws.users.insert(user.to_string(), sps);
let sws_ser = serde_bare::to_vec(&SessionWalletStorage::V0(sws.clone())).unwrap();
let mut rng = crypto_box::aead::OsRng {};
let cipher = crypto_box::seal(&mut rng, &wallet_id.to_dh_slice().into(), &sws_ser)
.map_err(|_| NgWalletError::EncryptionError)?;
Ok((sws, cipher))
}
pub fn dec_encrypted_block( pub fn dec_encrypted_block(
mut ciphertext: Vec<u8>, mut ciphertext: Vec<u8>,
master_key: &mut [u8; 32], master_key: &mut [u8; 32],
@ -151,14 +180,14 @@ pub fn dec_encrypted_block(
) )
.map_err(|e| NgWalletError::DecryptionError)?; .map_err(|e| NgWalletError::DecryptionError)?;
// `ciphertext` now contains the decrypted block
//log_debug!("decrypted_block {:?}", ciphertext);
let decrypted_log = let decrypted_log =
from_slice::<WalletLog>(&ciphertext).map_err(|e| NgWalletError::DecryptionError)?; from_slice::<WalletLog>(&ciphertext).map_err(|e| NgWalletError::DecryptionError)?;
master_key.zeroize(); master_key.zeroize();
// `ciphertext` now contains the decrypted block
//log_debug!("decrypted_block {:?}", ciphertext);
match decrypted_log { match decrypted_log {
WalletLog::V0(v0) => v0.reduce(), WalletLog::V0(v0) => v0.reduce(),
} }

@ -7,6 +7,7 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
use p2p_repo::log::*;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::{collections::HashMap, fmt}; use std::{collections::HashMap, fmt};
use web_time::SystemTime; use web_time::SystemTime;
@ -61,6 +62,66 @@ impl Bootstrap {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SessionWalletStorageV0 {
// string is base64_url encoding of userId(pubkey)
pub users: HashMap<String, SessionPeerStorageV0>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum SessionWalletStorage {
V0(SessionWalletStorageV0),
}
impl SessionWalletStorageV0 {
pub fn new() -> Self {
SessionWalletStorageV0 {
users: HashMap::new(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SessionPeerStorageV0 {
pub user: UserId,
pub peer_key: PrivKey,
pub last_wallet_nonce: u64,
// string is base64_url encoding of branchId(pubkey)
pub branches_last_seq: HashMap<String, u64>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LocalWalletStorageV0 {
pub bootstrap: BootstrapContent,
pub wallet: Wallet,
pub client: ClientId,
}
impl From<&CreateWalletResultV0> for LocalWalletStorageV0 {
fn from(res: &CreateWalletResultV0) -> Self {
LocalWalletStorageV0 {
bootstrap: BootstrapContent::V0(BootstrapContentV0 { servers: vec![] }),
wallet: res.wallet.clone(),
client: res.client.priv_key.to_pub(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum LocalWalletStorage {
V0(HashMap<String, LocalWalletStorageV0>),
}
impl LocalWalletStorage {
pub fn v0_from_vec(vec: &Vec<u8>) -> Self {
let wallets: LocalWalletStorage = serde_bare::from_slice(vec).unwrap();
wallets
}
pub fn v0_to_vec(wallets: HashMap<String, LocalWalletStorageV0>) -> Vec<u8> {
serde_bare::to_vec(&LocalWalletStorage::V0(wallets)).unwrap()
}
}
/// Device info Version 0 /// Device info Version 0
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ClientV0 { pub struct ClientV0 {
@ -121,18 +182,18 @@ pub struct EncryptedWalletV0 {
pub personal_site: PubKey, pub personal_site: PubKey,
#[zeroize(skip)] #[zeroize(skip)]
pub sites: HashMap<PubKey, SiteV0>, pub sites: HashMap<String, SiteV0>,
// map of brokers and their connection details // map of brokers and their connection details
#[zeroize(skip)] #[zeroize(skip)]
pub brokers: HashMap<PubKey, Vec<BrokerInfoV0>>, pub brokers: HashMap<String, Vec<BrokerInfoV0>>,
// map of all devices of the user // map of all devices of the user
#[zeroize(skip)] #[zeroize(skip)]
pub clients: HashMap<PubKey, ClientV0>, pub clients: HashMap<String, ClientV0>,
#[zeroize(skip)] #[zeroize(skip)]
pub overlay_core_overrides: HashMap<OverlayId, Vec<PubKey>>, pub overlay_core_overrides: HashMap<String, Vec<PubKey>>,
/// third parties data saved in the wallet. the string (key) in the hashmap should be unique among vendors. /// third parties data saved in the wallet. the string (key) in the hashmap should be unique among vendors.
/// the format of the byte array (value) is up to the vendor, to serde as needed. /// the format of the byte array (value) is up to the vendor, to serde as needed.
@ -143,26 +204,28 @@ pub struct EncryptedWalletV0 {
impl EncryptedWalletV0 { impl EncryptedWalletV0 {
pub fn add_site(&mut self, site: SiteV0) { pub fn add_site(&mut self, site: SiteV0) {
let site_id = site.site_key.to_pub(); let site_id = site.site_key.to_pub();
let _ = self.sites.insert(site_id, site); let _ = self.sites.insert(site_id.to_string(), site);
} }
pub fn add_brokers(&mut self, brokers: Vec<BrokerInfoV0>) { pub fn add_brokers(&mut self, brokers: Vec<BrokerInfoV0>) {
for broker in brokers { for broker in brokers {
let key = broker.get_id(); let key = broker.get_id().to_string();
let mut list = self.brokers.get_mut(&key); let mut list = self.brokers.get_mut(&key);
if list.is_none() { if list.is_none() {
let new_list = vec![]; let new_list = vec![];
self.brokers.insert(key, new_list); self.brokers.insert(key.clone(), new_list);
list = self.brokers.get_mut(&key); list = self.brokers.get_mut(&key);
} }
list.unwrap().push(broker); list.unwrap().push(broker);
} }
} }
pub fn add_client(&mut self, client: ClientV0) { pub fn add_client(&mut self, client: ClientV0) {
let client_id = client.priv_key.to_pub(); let client_id = client.priv_key.to_pub().to_string();
let _ = self.clients.insert(client_id, client); let _ = self.clients.insert(client_id, client);
} }
pub fn add_overlay_core_overrides(&mut self, overlay: &OverlayId, cores: &Vec<PubKey>) { pub fn add_overlay_core_overrides(&mut self, overlay: &OverlayId, cores: &Vec<PubKey>) {
let _ = self.overlay_core_overrides.insert(*overlay, cores.to_vec()); let _ = self
.overlay_core_overrides
.insert(overlay.to_string(), cores.to_vec());
} }
} }
@ -296,7 +359,7 @@ impl WalletLogV0 {
WalletOperation::RemoveOverlayCoreOverrideV0(_) => {} WalletOperation::RemoveOverlayCoreOverrideV0(_) => {}
WalletOperation::AddSiteCoreV0((site, core, registration)) => { WalletOperation::AddSiteCoreV0((site, core, registration)) => {
if self.is_first_and_not_deleted_afterwards(op, "RemoveSiteCoreV0") { if self.is_first_and_not_deleted_afterwards(op, "RemoveSiteCoreV0") {
let _ = wallet.sites.get_mut(&site).and_then(|site| { let _ = wallet.sites.get_mut(&site.to_string()).and_then(|site| {
site.cores.push((*core, *registration)); site.cores.push((*core, *registration));
None::<SiteV0> None::<SiteV0>
}); });
@ -305,7 +368,7 @@ impl WalletLogV0 {
WalletOperation::RemoveSiteCoreV0(_) => {} WalletOperation::RemoveSiteCoreV0(_) => {}
WalletOperation::AddSiteBootstrapV0((site, server)) => { WalletOperation::AddSiteBootstrapV0((site, server)) => {
if self.is_first_and_not_deleted_afterwards(op, "RemoveSiteBootstrapV0") { if self.is_first_and_not_deleted_afterwards(op, "RemoveSiteBootstrapV0") {
let _ = wallet.sites.get_mut(&site).and_then(|site| { let _ = wallet.sites.get_mut(&site.to_string()).and_then(|site| {
site.bootstraps.push(*server); site.bootstraps.push(*server);
None::<SiteV0> None::<SiteV0>
}); });
@ -320,7 +383,7 @@ impl WalletLogV0 {
WalletOperation::RemoveThirdPartyDataV0(_) => {} WalletOperation::RemoveThirdPartyDataV0(_) => {}
WalletOperation::SetSiteRBDRefV0((site, store_type, rbdr)) => { WalletOperation::SetSiteRBDRefV0((site, store_type, rbdr)) => {
if self.is_last_occurrence(op.0, &op.1) != 0 { if self.is_last_occurrence(op.0, &op.1) != 0 {
let _ = wallet.sites.get_mut(&site).and_then(|site| { let _ = wallet.sites.get_mut(&site.to_string()).and_then(|site| {
match store_type { match store_type {
SiteStoreType::Public => { SiteStoreType::Public => {
site.public.root_branch_def_ref = rbdr.clone() site.public.root_branch_def_ref = rbdr.clone()
@ -338,7 +401,7 @@ impl WalletLogV0 {
} }
WalletOperation::SetSiteRepoSecretV0((site, store_type, secret)) => { WalletOperation::SetSiteRepoSecretV0((site, store_type, secret)) => {
if self.is_last_occurrence(op.0, &op.1) != 0 { if self.is_last_occurrence(op.0, &op.1) != 0 {
let _ = wallet.sites.get_mut(&site).and_then(|site| { let _ = wallet.sites.get_mut(&site.to_string()).and_then(|site| {
match store_type { match store_type {
SiteStoreType::Public => { SiteStoreType::Public => {
site.public.repo_secret = secret.clone() site.public.repo_secret = secret.clone()
@ -356,7 +419,7 @@ impl WalletLogV0 {
} }
} }
} }
log_debug!("reduced {:?}", wallet);
Ok(wallet) Ok(wallet)
} else { } else {
Err(NgWalletError::NoCreateWalletPresent) Err(NgWalletError::NoCreateWalletPresent)
@ -840,6 +903,7 @@ pub enum NgWalletError {
InvalidSignature, InvalidSignature,
NoCreateWalletPresent, NoCreateWalletPresent,
InvalidBootstrap, InvalidBootstrap,
SerializationError,
} }
impl fmt::Display for NgWalletError { impl fmt::Display for NgWalletError {

@ -14,9 +14,10 @@ pnpm --ignore-workspace install
``` ```
cd web cd web
pnpm run dev pnpm run dev --host
// in another terminal // in another terminal
cd ../ cd ../
export NG_ACCOUNT_DOMAIN=[?]; export NG_ACCOUNT_ADMIN=[?]; export NG_ACCOUNT_LOCAL_PEER_KEY=[?]; export NG_ACCOUNT_SERVER=127.0.0.1,14400,[?]; export RUST_LOG=debug
cargo watch -c -w src -x run cargo watch -c -w src -x run
// then open http://localhost:5173/ // then open http://localhost:5173/
``` ```

@ -16,6 +16,7 @@ use p2p_client_ws::remote_ws::ConnectionWebSocket;
use p2p_net::actors::add_invitation::*; use p2p_net::actors::add_invitation::*;
use p2p_net::broker::BROKER; use p2p_net::broker::BROKER;
use p2p_repo::store::StorageError; use p2p_repo::store::StorageError;
use serde::{Deserialize, Serialize};
use warp::reply::Response; use warp::reply::Response;
use warp::{Filter, Reply}; use warp::{Filter, Reply};
@ -51,11 +52,26 @@ struct Server {
domain: String, domain: String,
} }
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
struct RegisterResponse {
url: Option<String>,
invite: Option<String>,
error: Option<String>,
}
impl Server { impl Server {
async fn register_(&self, ca: String) -> Result<String, Option<String>> { async fn register_(&self, ca: String) -> RegisterResponse {
log_debug!("registering {}", ca); log_debug!("registering {}", ca);
let mut cabsp: CreateAccountBSP = ca.try_into().map_err(|_| None)?; let cabsp = TryInto::<CreateAccountBSP>::try_into(ca);
if cabsp.is_err() {
return RegisterResponse {
error: Some("invalid request".into()),
invite: None,
url: None,
};
}
let mut cabsp = cabsp.unwrap();
log_debug!("{:?}", cabsp); log_debug!("{:?}", cabsp);
@ -90,51 +106,62 @@ impl Server {
) )
.await; .await;
let redirect_url = cabsp let redirect_url = cabsp.redirect_url().clone().unwrap_or(format!(
.redirect_url() "https://{}{}",
.clone() self.domain, APP_ACCOUNT_REGISTERED_SUFFIX
.unwrap_or(format!("{}{}", self.domain, APP_ACCOUNT_REGISTERED_SUFFIX)); ));
match res { match res {
Err(e) => { Err(e) => {
log_err!("error while registering: {e} {:?}", cabsp); log_err!("error while registering: {e} {:?}", cabsp);
Err(Some(format!("{}?re={}", redirect_url, e))) RegisterResponse {
url: Some(format!("{}?re={}", redirect_url, e)),
invite: None,
error: Some(e.to_string()),
}
} }
Ok(AdminResponseContentV0::Invitation(Invitation::V0(mut invitation))) => { Ok(AdminResponseContentV0::Invitation(Invitation::V0(mut invitation))) => {
log_info!("invitation created successfully {:?}", invitation); log_info!("invitation created successfully {:?}", invitation);
invitation.name = Some(self.domain.clone()); invitation.name = Some(self.domain.clone());
Ok(format!( let inv = Invitation::V0(invitation);
"{}?i={}&rs={}", RegisterResponse {
redirect_url, url: Some(format!("{}?i={}&rs={}", redirect_url, inv, self.domain)),
Invitation::V0(invitation), invite: Some(format!("{inv}")),
self.domain error: None,
)) }
} }
_ => { _ => {
log_err!( log_err!(
"error while registering: invalid response from add_invitation {:?}", "error while registering: invalid response from add_invitation {:?}",
cabsp cabsp
); );
Err(Some(format!( let e = "add_invitation_failed";
"{}?re={}", RegisterResponse {
redirect_url, "add_invitation_failed" url: Some(format!("{}?re={}", redirect_url, e)),
))) invite: None,
error: Some(e.to_string()),
}
} }
} }
} }
pub async fn register(self: Arc<Self>, ca: String) -> Result<Response, Infallible> { pub async fn register(self: Arc<Self>, ca: String) -> Result<Response, Infallible> {
match self.register_(ca).await { let res = self.register_(ca).await;
Ok(redirect_url) => { match &res {
let response = Response::new(redirect_url.into()); RegisterResponse { error: None, .. } => {
let response = warp::reply::json(&res).into_response(); //Response::new(redirect_url.into());
let (mut parts, body) = response.into_parts(); let (mut parts, body) = response.into_parts();
parts.status = warp::http::StatusCode::OK; parts.status = warp::http::StatusCode::OK;
let response = Response::from_parts(parts, body); let response = Response::from_parts(parts, body);
Ok(response) Ok(response)
} }
Err(redirect_url) => { RegisterResponse {
error: Some(e),
url: redirect_url,
..
} => {
if redirect_url.is_some() { if redirect_url.is_some() {
let response = Response::new(redirect_url.unwrap().into()); let response = warp::reply::json(&res).into_response();
let (mut parts, body) = response.into_parts(); let (mut parts, body) = response.into_parts();
parts.status = warp::http::StatusCode::BAD_REQUEST; parts.status = warp::http::StatusCode::BAD_REQUEST;
let response = Response::from_parts(parts, body); let response = Response::from_parts(parts, body);

@ -10,21 +10,22 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-alpha.4",
"@tauri-apps/plugin-window": "2.0.0-alpha.0",
"flowbite": "^1.6.5", "flowbite": "^1.6.5",
"flowbite-svelte": "^0.37.1", "flowbite-svelte": "^0.37.1",
"svelte-spa-router": "^3.3.0", "svelte-spa-router": "^3.3.0"
"@tauri-apps/api": "2.0.0-alpha.4"
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.0.4", "@sveltejs/vite-plugin-svelte": "^2.0.4",
"svelte": "^3.58.0", "autoprefixer": "^10.4.14",
"vite": "^4.3.9", "cross-env": "^7.0.3",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"postcss-load-config": "^4.0.1", "postcss-load-config": "^4.0.1",
"svelte": "^3.58.0",
"svelte-preprocess": "^5.0.3", "svelte-preprocess": "^5.0.3",
"tailwindcss": "^3.3.1", "tailwindcss": "^3.3.1",
"autoprefixer": "^10.4.14", "vite": "^4.3.9",
"vite-plugin-svelte-svg": "^2.2.1", "vite-plugin-svelte-svg": "^2.2.1"
"cross-env": "^7.0.3"
} }
} }

@ -3,6 +3,7 @@ lockfileVersion: 5.4
specifiers: specifiers:
'@sveltejs/vite-plugin-svelte': ^2.0.4 '@sveltejs/vite-plugin-svelte': ^2.0.4
'@tauri-apps/api': 2.0.0-alpha.4 '@tauri-apps/api': 2.0.0-alpha.4
'@tauri-apps/plugin-window': 2.0.0-alpha.0
autoprefixer: ^10.4.14 autoprefixer: ^10.4.14
cross-env: ^7.0.3 cross-env: ^7.0.3
flowbite: ^1.6.5 flowbite: ^1.6.5
@ -18,6 +19,7 @@ specifiers:
dependencies: dependencies:
'@tauri-apps/api': 2.0.0-alpha.4 '@tauri-apps/api': 2.0.0-alpha.4
'@tauri-apps/plugin-window': 2.0.0-alpha.0
flowbite: 1.6.6 flowbite: 1.6.6
flowbite-svelte: 0.37.5_svelte@3.59.1 flowbite-svelte: 0.37.5_svelte@3.59.1
svelte-spa-router: 3.3.0 svelte-spa-router: 3.3.0
@ -339,6 +341,12 @@ packages:
engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'} engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
dev: false dev: false
/@tauri-apps/plugin-window/2.0.0-alpha.0:
resolution: {integrity: sha512-ZXFXOu9m8QiDB8d8LFFgwcfxIAbr0bhzj06YvmZDB3isuVtlFP9EyU4D+zmumWEWvNN2XP7xgpn68ivOVhmNNQ==}
dependencies:
'@tauri-apps/api': 2.0.0-alpha.4
dev: false
/@trysound/sax/0.2.0: /@trysound/sax/0.2.0:
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}

@ -36,35 +36,59 @@
try { try {
const response = await fetch(api_url + "register/" + ca, opts); const response = await fetch(api_url + "register/" + ca, opts);
const result = await response.text(); const result = await response.json();
console.log("Result:", response.status, result); // 400 is error with redirect, 200 ok, 406 is error without known redirect console.log("Result:", response.status, result); // 400 is error with redirect, 200 ok, 406 is error without known redirect
if (response.status == 406) { if (response.status == 406) {
close(); await close();
} else if (response.status == 400) { } else if (response.status == 400) {
close(result); await close(result);
error = "We are redirecting you...";
go_back = false;
} else { } else {
//console.log(result); //console.log(result);
success(result); await success(result);
} }
} catch (e) { } catch (e) {
error = e.message; error = e.message;
} }
} }
function close(url) { async function close(result) {
// TODO tauri error code // @ts-ignore
if (url) { if (window.__TAURI__) {
window.location.href = url; go_back = false;
if (result) {
error = "Closing due to " + (result.error || "an error");
}
let window_api = await import("@tauri-apps/plugin-window");
let main = window_api.WebviewWindow.getByLabel("main");
if (main) {
await main.emit("error", result);
} else {
await window_api.appWindow.close();
}
} else {
if (result && result.url) {
error = "We are redirecting you...";
go_back = false;
window.location.href = result.url;
} else { } else {
window.history.go(-1); window.history.go(-1);
} }
} }
}
function success(url) { async function success(result) {
window.location.href = url; // @ts-ignore
// TODO tauri success code if (window.__TAURI__) {
let window_api = await import("@tauri-apps/plugin-window");
let main = window_api.WebviewWindow.getByLabel("main");
if (main) {
await main.emit("accepted", result);
} else {
await window_api.appWindow.close();
}
} else {
window.location.href = result.url;
}
} }
async function bootstrap() {} async function bootstrap() {}
@ -127,7 +151,7 @@
<p class="max-w-xl md:mx-auto lg:max-w-2xl"> <p class="max-w-xl md:mx-auto lg:max-w-2xl">
You would like to choose <b>{domain}</b> as your Broker Service You would like to choose <b>{domain}</b> as your Broker Service
Provider.<br />Please read carefully the Terms of Service here below, Provider.<br />Please read carefully the Terms of Service here below,
before you accept them. before accepting them.
</p> </p>
</div> </div>
{/if} {/if}
@ -287,7 +311,6 @@
<div class="row mb-20"> <div class="row mb-20">
<button <button
on:click|once={accept} on:click|once={accept}
role="button"
class="mr-5 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:outline-none 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-700/55 mb-2" class="mr-5 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:outline-none 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-700/55 mb-2"
> >
<svg <svg

@ -11,7 +11,9 @@
<script> <script>
import { Button } from "flowbite-svelte"; import { Button } from "flowbite-svelte";
// @ts-ignore
import EULogo from "../assets/EU.svg?component"; import EULogo from "../assets/EU.svg?component";
// @ts-ignore
import Logo from "../assets/nextgraph.svg?component"; import Logo from "../assets/nextgraph.svg?component";
import { link, querystring } from "svelte-spa-router"; import { link, querystring } from "svelte-spa-router";
@ -155,7 +157,6 @@
<div class="row mb-20"> <div class="row mb-20">
<button <button
on:click|once={accept} on:click|once={accept}
role="button"
class="mr-5 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:outline-none 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-700/55 mb-2" class="mr-5 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:outline-none 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-700/55 mb-2"
> >
<svg <svg

@ -8,4 +8,4 @@ pub mod utils;
pub mod interfaces; pub mod interfaces;
pub mod storage; pub mod server_storage;

@ -15,8 +15,8 @@ use crate::broker_store::account::Account;
use crate::broker_store::invitation::Invitation; use crate::broker_store::invitation::Invitation;
use crate::broker_store::wallet::Wallet; use crate::broker_store::wallet::Wallet;
use crate::types::*; use crate::types::*;
use p2p_net::broker_storage::*;
use p2p_net::errors::ProtocolError; use p2p_net::errors::ProtocolError;
use p2p_net::server_storage::*;
use p2p_net::types::{BootstrapContentV0, InvitationCode, InvitationV0}; use p2p_net::types::{BootstrapContentV0, InvitationCode, InvitationV0};
use p2p_repo::kcv_store::KCVStore; use p2p_repo::kcv_store::KCVStore;
use p2p_repo::log::*; use p2p_repo::log::*;
@ -26,20 +26,19 @@ use stores_lmdb::kcv_store::LmdbKCVStore;
use stores_lmdb::repo_store::LmdbRepoStore; use stores_lmdb::repo_store::LmdbRepoStore;
#[derive(Debug)] #[derive(Debug)]
pub struct LmdbBrokerStorage { pub struct LmdbServerStorage {
wallet_storage: LmdbKCVStore, wallet_storage: LmdbKCVStore,
accounts_storage: LmdbKCVStore, accounts_storage: LmdbKCVStore,
peers_storage: LmdbKCVStore, peers_storage: LmdbKCVStore,
} }
impl LmdbBrokerStorage { impl LmdbServerStorage {
pub fn open( pub fn open(
path: &mut PathBuf, path: &mut PathBuf,
master_key: SymKey, master_key: SymKey,
admin_invite: Option<BootstrapContentV0>, admin_invite: Option<BootstrapContentV0>,
) -> Result<Self, StorageError> { ) -> Result<Self, StorageError> {
// create/open the WALLET // create/open the WALLET
let mut wallet_path = path.clone(); let mut wallet_path = path.clone();
wallet_path.push("wallet"); wallet_path.push("wallet");
std::fs::create_dir_all(wallet_path.clone()).unwrap(); std::fs::create_dir_all(wallet_path.clone()).unwrap();
@ -48,7 +47,6 @@ impl LmdbBrokerStorage {
let wallet = Wallet::open(&wallet_storage); let wallet = Wallet::open(&wallet_storage);
// create/open the ACCOUNTS storage // create/open the ACCOUNTS storage
let mut accounts_path = path.clone(); let mut accounts_path = path.clone();
let accounts_key; let accounts_key;
accounts_path.push("accounts"); accounts_path.push("accounts");
@ -93,7 +91,7 @@ impl LmdbBrokerStorage {
//TODO redo the whole key passing mechanism in RKV so it uses zeroize all the way //TODO redo the whole key passing mechanism in RKV so it uses zeroize all the way
let peers_storage = LmdbKCVStore::open(&peers_path, peers_key.slice().clone()); let peers_storage = LmdbKCVStore::open(&peers_path, peers_key.slice().clone());
Ok(LmdbBrokerStorage { Ok(LmdbServerStorage {
wallet_storage, wallet_storage,
accounts_storage, accounts_storage,
peers_storage, peers_storage,
@ -101,7 +99,7 @@ impl LmdbBrokerStorage {
} }
} }
impl BrokerStorage for LmdbBrokerStorage { impl ServerStorage for LmdbServerStorage {
fn get_user(&self, user_id: PubKey) -> Result<bool, ProtocolError> { fn get_user(&self, user_id: PubKey) -> Result<bool, ProtocolError> {
log_debug!("get_user {user_id}"); log_debug!("get_user {user_id}");
Ok(Account::open(&user_id, &self.accounts_storage)?.is_admin()?) Ok(Account::open(&user_id, &self.accounts_storage)?.is_admin()?)

@ -12,7 +12,7 @@
//! WebSocket implementation of the Broker //! WebSocket implementation of the Broker
use crate::interfaces::*; use crate::interfaces::*;
use crate::storage::LmdbBrokerStorage; use crate::server_storage::LmdbServerStorage;
use crate::types::*; use crate::types::*;
use async_std::io::ReadExt; use async_std::io::ReadExt;
use async_std::net::{TcpListener, TcpStream}; use async_std::net::{TcpListener, TcpStream};
@ -767,7 +767,7 @@ pub async fn run_server_v0(
std::fs::create_dir_all(path.clone()).unwrap(); std::fs::create_dir_all(path.clone()).unwrap();
// opening the server storage (that contains the encryption keys for each store/overlay ) // opening the server storage (that contains the encryption keys for each store/overlay )
let broker_storage = LmdbBrokerStorage::open( let broker_storage = LmdbServerStorage::open(
&mut path, &mut path,
wallet_master_key, wallet_master_key,
if admin_invite { if admin_invite {
@ -776,10 +776,10 @@ pub async fn run_server_v0(
None None
}, },
) )
.map_err(|e| log_err!("Error while opening broker storage: {:?}", e))?; .map_err(|e| log_err!("Error while opening server storage: {:?}", e))?;
let mut broker = BROKER.write().await; let mut broker = BROKER.write().await;
broker.set_storage(broker_storage); broker.set_server_storage(broker_storage);
LISTENERS_INFO LISTENERS_INFO
.set(broker.set_listeners(listener_infos)) .set(broker.set_listeners(listener_infos))
.unwrap(); .unwrap();

@ -102,7 +102,7 @@ impl EActor for Actor<'_, AddInvitation, AdminResponse> {
let req = AddInvitation::try_from(msg)?; let req = AddInvitation::try_from(msg)?;
let broker = BROKER.read().await; let broker = BROKER.read().await;
broker broker
.get_storage()? .get_server_storage()?
.add_invitation(req.code(), req.expiry(), req.memo())?; .add_invitation(req.code(), req.expiry(), req.memo())?;
let invitation = crate::types::Invitation::V0(InvitationV0::new( let invitation = crate::types::Invitation::V0(InvitationV0::new(

@ -101,7 +101,7 @@ impl EActor for Actor<'_, AddUser, AdminResponse> {
is_admin = true; is_admin = true;
} }
} }
let res = broker.get_storage()?.add_user(req.user(), is_admin); let res = broker.get_server_storage()?.add_user(req.user(), is_admin);
let response: AdminResponseV0 = res.into(); let response: AdminResponseV0 = res.into();
fsm.lock().await.send(response.into()).await?; fsm.lock().await.send(response.into()).await?;
Ok(()) Ok(())

@ -81,7 +81,7 @@ impl EActor for Actor<'_, DelUser, AdminResponse> {
) -> Result<(), ProtocolError> { ) -> Result<(), ProtocolError> {
let req = DelUser::try_from(msg)?; let req = DelUser::try_from(msg)?;
let broker = BROKER.read().await; let broker = BROKER.read().await;
let res = broker.get_storage()?.del_user(req.user()); let res = broker.get_server_storage()?.del_user(req.user());
let response: AdminResponseV0 = res.into(); let response: AdminResponseV0 = res.into();
fsm.lock().await.send(response.into()).await?; fsm.lock().await.send(response.into()).await?;
Ok(()) Ok(())

@ -97,7 +97,7 @@ impl EActor for Actor<'_, ListInvitations, AdminResponse> {
fsm: Arc<Mutex<NoiseFSM>>, fsm: Arc<Mutex<NoiseFSM>>,
) -> Result<(), ProtocolError> { ) -> Result<(), ProtocolError> {
let req = ListInvitations::try_from(msg)?; let req = ListInvitations::try_from(msg)?;
let res = BROKER.read().await.get_storage()?.list_invitations( let res = BROKER.read().await.get_server_storage()?.list_invitations(
req.admin(), req.admin(),
req.unique(), req.unique(),
req.multi(), req.multi(),

@ -83,7 +83,11 @@ impl EActor for Actor<'_, ListUsers, AdminResponse> {
fsm: Arc<Mutex<NoiseFSM>>, fsm: Arc<Mutex<NoiseFSM>>,
) -> Result<(), ProtocolError> { ) -> Result<(), ProtocolError> {
let req = ListUsers::try_from(msg)?; let req = ListUsers::try_from(msg)?;
let res = BROKER.read().await.get_storage()?.list_users(req.admins()); let res = BROKER
.read()
.await
.get_server_storage()?
.list_users(req.admins());
let response: AdminResponseV0 = res.into(); let response: AdminResponseV0 = res.into();
fsm.lock().await.send(response.into()).await?; fsm.lock().await.send(response.into()).await?;
Ok(()) Ok(())

@ -10,9 +10,9 @@
*/ */
use crate::actor::*; use crate::actor::*;
use crate::broker_storage::BrokerStorage;
use crate::connection::*; use crate::connection::*;
use crate::errors::*; use crate::errors::*;
use crate::server_storage::ServerStorage;
use crate::types::*; use crate::types::*;
use crate::utils::spawn_and_log_error; use crate::utils::spawn_and_log_error;
use crate::utils::{Receiver, ResultSend, Sender}; use crate::utils::{Receiver, ResultSend, Sender};
@ -85,7 +85,7 @@ pub struct Broker<'a> {
shutdown: Option<Receiver<ProtocolError>>, shutdown: Option<Receiver<ProtocolError>>,
shutdown_sender: Sender<ProtocolError>, shutdown_sender: Sender<ProtocolError>,
closing: bool, closing: bool,
storage: Option<Box<dyn BrokerStorage + Send + Sync + 'a>>, server_storage: Option<Box<dyn ServerStorage + Send + Sync + 'a>>,
test: u32, test: u32,
tauri_streams: HashMap<String, Sender<Commit>>, tauri_streams: HashMap<String, Sender<Commit>>,
@ -124,11 +124,16 @@ impl<'a> Broker<'a> {
.ok_or(ProtocolError::BrokerError) .ok_or(ProtocolError::BrokerError)
} }
pub fn set_storage(&mut self, storage: impl BrokerStorage + 'a) { pub fn set_server_storage(&mut self, storage: impl ServerStorage + 'a) {
//log_debug!("set_storage"); //log_debug!("set_storage");
self.storage = Some(Box::new(storage)); self.server_storage = Some(Box::new(storage));
} }
// pub fn set_local_storage(&mut self, storage: impl LocalStorage + 'a) {
// //log_debug!("set_storage");
// self.local_storage = Some(Box::new(storage));
// }
pub fn set_server_config(&mut self, config: ServerConfig) { pub fn set_server_config(&mut self, config: ServerConfig) {
self.config = Some(config); self.config = Some(config);
} }
@ -151,9 +156,13 @@ impl<'a> Broker<'a> {
(copy_listeners, copy_bind_addresses) (copy_listeners, copy_bind_addresses)
} }
pub fn get_storage(&self) -> Result<&Box<dyn BrokerStorage + Send + Sync + 'a>, ProtocolError> { pub fn get_server_storage(
//log_debug!("GET STORAGE {:?}", self.storage); &self,
self.storage.as_ref().ok_or(ProtocolError::BrokerError) ) -> Result<&Box<dyn ServerStorage + Send + Sync + 'a>, ProtocolError> {
//log_debug!("GET STORAGE {:?}", self.server_storage);
self.server_storage
.as_ref()
.ok_or(ProtocolError::BrokerError)
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
@ -184,8 +193,8 @@ impl<'a> Broker<'a> {
Authorization::ExtMessage => Err(ProtocolError::AccessDenied), Authorization::ExtMessage => Err(ProtocolError::AccessDenied),
Authorization::Client(user_and_registration) => { Authorization::Client(user_and_registration) => {
if user_and_registration.1.is_some() { if user_and_registration.1.is_some() {
// use wants to register // user wants to register
let storage = self.storage.as_ref().unwrap(); let storage = self.get_server_storage()?;
if storage.get_user(user_and_registration.0).is_ok() { if storage.get_user(user_and_registration.0).is_ok() {
return Ok(()); return Ok(());
} }
@ -210,10 +219,7 @@ impl<'a> Broker<'a> {
} else if inv_type == 1u8 { } else if inv_type == 1u8 {
storage.remove_invitation(code)?; storage.remove_invitation(code)?;
} }
self.storage storage.add_user(user_and_registration.0, is_admin)?;
.as_ref()
.unwrap()
.add_user(user_and_registration.0, is_admin)?;
Ok(()) Ok(())
} }
} }
@ -232,9 +238,7 @@ impl<'a> Broker<'a> {
storage.remove_invitation(code)?; storage.remove_invitation(code)?;
} }
} }
self.storage self.get_server_storage()?
.as_ref()
.unwrap()
.add_user(user_and_registration.0, is_admin)?; .add_user(user_and_registration.0, is_admin)?;
Ok(()) Ok(())
} }
@ -258,7 +262,7 @@ impl<'a> Broker<'a> {
return Ok(()); return Ok(());
} }
} }
let found = self.get_storage()?.get_user(admin_user); let found = self.get_server_storage()?.get_user(admin_user);
if found.is_ok() && found.unwrap() { if found.is_ok() && found.unwrap() {
return Ok(()); return Ok(());
} }
@ -270,11 +274,11 @@ impl<'a> Broker<'a> {
} }
// pub fn add_user(&self, user: PubKey, is_admin: bool) -> Result<(), ProtocolError> { // pub fn add_user(&self, user: PubKey, is_admin: bool) -> Result<(), ProtocolError> {
// self.get_storage()?.add_user(user, is_admin) // self.get_server_storage()?.add_user(user, is_admin)
// } // }
// pub fn list_users(&self, admins: bool) -> Result<Vec<PubKey>, ProtocolError> { // pub fn list_users(&self, admins: bool) -> Result<Vec<PubKey>, ProtocolError> {
// self.get_storage()?.list_users(admins) // self.get_server_storage()?.list_users(admins)
// } // }
pub async fn get_block_from_store_with_block_id( pub async fn get_block_from_store_with_block_id(
@ -433,7 +437,7 @@ impl<'a> Broker<'a> {
tauri_streams: HashMap::new(), tauri_streams: HashMap::new(),
closing: false, closing: false,
test: u32::from_be_bytes(random_buf), test: u32::from_be_bytes(random_buf),
storage: None, server_storage: None,
} }
} }

@ -19,7 +19,7 @@ pub mod errors;
pub mod broker; pub mod broker;
pub mod broker_storage; pub mod server_storage;
pub mod connection; pub mod connection;

@ -12,7 +12,7 @@
use crate::{errors::ProtocolError, types::*}; use crate::{errors::ProtocolError, types::*};
use p2p_repo::{kcv_store::KCVStore, types::PubKey}; use p2p_repo::{kcv_store::KCVStore, types::PubKey};
pub trait BrokerStorage: Send + Sync + std::fmt::Debug { pub trait ServerStorage: Send + Sync + std::fmt::Debug {
fn get_user(&self, user_id: PubKey) -> Result<bool, ProtocolError>; fn get_user(&self, user_id: PubKey) -> Result<bool, ProtocolError>;
fn add_user(&self, user_id: PubKey, is_admin: bool) -> Result<(), ProtocolError>; fn add_user(&self, user_id: PubKey, is_admin: bool) -> Result<(), ProtocolError>;
fn del_user(&self, user_id: PubKey) -> Result<(), ProtocolError>; fn del_user(&self, user_id: PubKey) -> Result<(), ProtocolError>;

@ -41,7 +41,7 @@ pub enum Digest {
impl fmt::Display for Digest { impl fmt::Display for Digest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Digest::Blake3Digest32(d) => write!(f, "{}", hex::encode(d)), Digest::Blake3Digest32(d) => write!(f, "{}", base64_url::encode(d)),
} }
} }
} }

Loading…
Cancel
Save