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 = [
"async-std",
"base64-url",
"crypto_box",
"futures",
"getrandom 0.1.16",
"gloo-timers",
@ -2816,6 +2815,7 @@ dependencies = [
"async-std",
"base64-url",
"chacha20poly1305",
"crypto_box",
"getrandom 0.1.16",
"image",
"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
// All rights reserved.
// Licensed under the Apache License, Version 2.0
@ -10,11 +13,13 @@ use async_std::stream::StreamExt;
use ng_wallet::types::*;
use ng_wallet::*;
use p2p_net::broker::*;
use p2p_net::types::CreateAccountBSP;
use p2p_net::utils::{spawn_and_log_error, Receiver, ResultSend};
use p2p_net::types::{CreateAccountBSP, Invitation};
use p2p_net::utils::{decode_invitation_string, spawn_and_log_error, Receiver, ResultSend};
use p2p_repo::log::*;
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)]
mod mobile;
@ -61,19 +66,132 @@ async fn wallet_open_wallet_with_pazzle(
}
#[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);
params.result_with_wallet_file = false;
params.result_with_wallet_file = !params.local_save;
let local_save = params.local_save;
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 {
// 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 {
// 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")]
@ -82,6 +200,34 @@ async fn encode_create_account(payload: CreateAccountBSP) -> Result<String, ()>
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")]
async fn doc_sync_branch(nuri: &str, stream_id: &str, app: tauri::AppHandle) -> Result<(), ()> {
log_debug!("doc_sync_branch {} {}", nuri, stream_id);
@ -157,6 +303,11 @@ pub struct AppBuilder {
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 {
pub fn new() -> Self {
Self::default()
@ -178,7 +329,13 @@ impl AppBuilder {
if let Some(setup) = setup {
(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(())
})
.plugin(tauri_plugin_window::init())
@ -192,6 +349,10 @@ impl AppBuilder {
wallet_open_wallet_with_pazzle,
wallet_create_wallet,
encode_create_account,
get_local_session,
get_wallets_from_localstorage,
open_window,
decode_invitation,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");

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

@ -46,30 +46,44 @@
let unsubscribe = () => {};
let wallet_channel;
let unsub_main_close;
onMount(async () => {
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) => {
if (event.storageArea != localStorage) return;
if (event.key === "ng_wallets") {
wallets.set(
Object.fromEntries(await ng.get_wallets_from_localstorage())
);
wallets.set(await ng.get_wallets_from_localstorage());
}
});
wallets.set(
Object.fromEntries((await ng.get_wallets_from_localstorage()) || [])
);
wallets.set(await ng.get_wallets_from_localstorage());
wallet_channel = new BroadcastChannel("ng_wallet");
wallet_channel.postMessage({ cmd: "is_opened" }, location.href);
wallet_channel.onmessage = (event) => {
console.log(event);
if (!location.href.startsWith(event.origin)) return;
console.log("ng_wallet", event.data);
switch (event.data.cmd) {
case "is_opened":
console.log($active_wallet);
if ($active_wallet && $active_wallet.wallet) {
wallet_channel.postMessage(
{ cmd: "opened", wallet: $active_wallet },
@ -128,7 +142,10 @@
}
});
onDestroy(unsubscribe);
onDestroy(() => {
unsubscribe();
if (unsub_main_close) unsub_main_close();
});
</script>
<main class="">

@ -19,6 +19,10 @@ const mapping = {
"wallet_open_wallet_with_pazzle": ["wallet","pazzle","pin"],
"wallet_create_wallet": ["params"],
"encode_create_account": ["payload"],
"get_local_session": ["id","key","user"],
"get_wallets_from_localstorage": [],
"open_window": ["url","label","title"],
"decode_invitation": ["invite"],
"test": [ ]
}
@ -34,6 +38,23 @@ const handler = {
client_info.version=version;
//console.log(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 {
return Reflect.apply(sdk[path], caller, args)
}
@ -88,16 +109,28 @@ const handler = {
let res = await tauri.invoke(path[0],arg);
res['File'].V0.content = Uint8Array.from(res['File'].V0.content);
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") {
let params = args[0];
params.result_with_wallet_file = false;
params.security_img = Array.from(new Uint8Array(params.security_img));
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;
} else if (path[0].starts_with("get_local_url")) {
} else if (path[0] === "get_local_url") {
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 {
let arg = {};
@ -111,15 +144,20 @@ const handler = {
const api = createAsyncProxy({}, handler);
export const NG_EU_BSP = "https://nextgraph.eu";
export const NG_EU_BSP_REGISTER = "https://account.nextgraph.eu/#/create";
export const NG_EU_BSP_REGISTERED = "https://nextgraph.eu/#/user/registered";
export const NG_EU_BSP_REGISTER = import.meta.env.PROD
? "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_WALLET_CREATE_SUFFIX = "/#/wallet/create";
export const NG_NET_BSP = "https://nextgraph.net";
export const NG_NET_BSP_REGISTER = "https://account.nextgraph.net/#/create";
export const NG_NET_BSP_REGISTERED = "https://nextgraph.net/#/user/registered";
export const LINK_NG_BOX = "https://nextgraph.org/ng-box/";
export const LINK_SELF_HOST = "https://nextgraph.org/self-host/";
export default api;

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

@ -20,56 +20,8 @@
onMount(async () => {
await load_svg();
console.log(wallet);
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() {
@ -159,6 +111,7 @@
}
console.log(pazzle);
console.log(wallet);
// open the wallet
try {
@ -168,7 +121,6 @@
pin_code
);
step = "end";
console.log(secret_wallet);
dispatch("opened", {
wallet: secret_wallet,
id: secret_wallet.V0.wallet_id,
@ -239,7 +191,7 @@
</svg>
</div>
{: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}
<div class="columns-3 gap-0">
{#each emojis2[display]?.slice(0 + row * 3, 3 + row * 3) || [] as emoji, i}
@ -258,7 +210,7 @@
</div>
{:else if step == "order"}
<!-- 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}
<div class="columns-3 gap-0">
{#each selection.slice(0 + row * 3, 3 + row * 3) || [] as emoji, i}
@ -401,14 +353,17 @@
}
.sel {
position: relative;
top: -56%;
position: absolute;
width: 100%;
top: 45%;
left: 0;
font-size: 100px;
font-weight: 700;
}
.sel-emoji {
overflow: hidden;
/* overflow: hidden; */
position: relative;
}
.emoji {

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

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

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

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

@ -19,14 +19,16 @@
import {
NG_EU_BSP,
NG_NET_BSP,
LINK_NG_BOX,
LINK_SELF_HOST,
NG_EU_BSP_REGISTER,
NG_EU_BSP_REGISTERED,
NG_NET_BSP_REGISTER,
APP_WALLET_CREATE_SUFFIX,
default as ng,
} from "../api";
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";
const param = new URLSearchParams($querystring);
@ -108,6 +110,10 @@
let animateDownload = true;
let invitation;
let unsub_register_accepted;
let unsub_register_error;
let unsub_register_close;
function scrollToTop() {
top.scrollIntoView();
}
@ -202,7 +208,7 @@
pazzle_length: 9,
send_bootstrap: false, //options.cloud || options.bootstrap ? : undefined,
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
core_bootstrap: invitation.V0.bootstrap,
core_registration,
@ -211,10 +217,8 @@
console.log(params);
try {
let res = await ng.wallet_create_wallet(params);
console.log(res);
wallets.set(
Object.fromEntries((await ng.get_wallets_from_localstorage()) || [])
);
let walls = await ng.get_wallets_from_localstorage();
wallets.set(walls);
if (res[1]) {
set_active_session(res[1]);
}
@ -227,26 +231,9 @@
if (ready.wallet_file.length) {
const blob = new Blob([ready.wallet_file]);
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) {
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;
}
}
@ -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) {
let local_url = await ng.get_local_url(location.href);
if (!import.meta.env.PROD) {
local_url = "http://localhost:1421";
}
let create = {
V0: {
redirect_url: local_url + APP_WALLET_CREATE_SUFFIX,
},
};
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();
} else {
let create = {
@ -294,14 +297,60 @@
},
};
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 enterQRcode = (event) => {};
const displayNGbox = (event) => {};
const displaySelfHost = (event) => {};
const displayNGbox = async (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>
<main class="container3" bind:this={top}>
@ -869,7 +918,7 @@
{/if}
<div class="row mt-5">
<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"
>
<svg
@ -892,7 +941,7 @@
</div>
<div class="row mt-5 mb-12">
<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"
>
<svg
@ -921,7 +970,7 @@
{:else if pin.length < 4}
<div class=" max-w-6xl lg:px-8 mx-auto px-4">
{#if registration_success}
<Alert color="green" class="mt-5 mb-5">
<Alert color="green" class="mb-5">
<span class="font-bold text-xl"
>You have been successfully registered to {registration_success}</span
>
@ -934,8 +983,8 @@
<Alert color="yellow" class="mt-5">
We recommend you to choose a PIN code that you already know very well.
<br />
Your credit card PIN, by example, is a good choice. (We at NextGraph will
never see your PIN)
Your credit card PIN, by example, is a good choice.<br />We at
NextGraph will never see your PIN.
</Alert>
</p>
<p class="text-left mt-5">Here are the rules for the PIN :</p>
@ -955,7 +1004,7 @@
>{digit}</span
>{/each}
</Alert>
<div class="w-[325px] mx-auto">
<div class="w-[325px] mx-auto mb-4">
{#each [0, 1, 2] as row}
<div class="">
{#each [1, 2, 3] as num}
@ -1202,7 +1251,7 @@
<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
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
it.
<br />

@ -25,7 +25,7 @@
has_wallets,
} from "../store";
let wallet;
let selected; //= "8Hg1Rf7us3LFhs7HCbxCQOWJoV-OUyALbTuSaKp7D-M";
let selected;
let step;
let wallets_unsub;
@ -64,6 +64,8 @@
set_active_session(session);
loggedin();
}
} else {
loggedin();
}
}
});
@ -112,7 +114,7 @@
<div class="row">
<Logo class="logo block h-40" alt="NextGraph Logo" />
</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">
{#each Object.entries($wallets) as wallet_entry}
<div

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

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

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

@ -18,7 +18,21 @@ export function session_save(key,value) {
} catch(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) {
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>, ()> {
let wallets_string = local_get("ng_wallets".to_string());
if wallets_string.is_some() {
@ -205,6 +165,25 @@ pub fn get_wallets_from_localstorage() -> JsValue {
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")]
#[wasm_bindgen]
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");
let key = serde_wasm_bindgen::from_value::<PrivKey>(key_js).unwrap();
let decoded = base64_url::decode(&res.unwrap()).unwrap();
let session_ser = crypto_box::seal_open(&(*key.to_dh().slice()).into(), &decoded).unwrap();
let session: SessionWalletStorage = serde_bare::from_slice(&session_ser).unwrap();
let SessionWalletStorage::V0(v0) = session;
return serde_wasm_bindgen::to_value(&v0).unwrap();
} else {
let v0 = dec_session(key, &decoded);
if v0.is_ok() {
return serde_wasm_bindgen::to_value(&v0.unwrap()).unwrap();
}
}
// create a new session
let user = serde_wasm_bindgen::from_value::<PubKey>(user_js).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() {
return JsValue::UNDEFINED;
}
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> {
// let mut sws = SessionWalletStorageV0::new();
// 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 sws = save_new_session(&res.wallet_name, res.wallet.id(), res.user)?;
let mut wallets: HashMap<String, LocalWalletStorageV0> =
get_local_wallets_v0().unwrap_or(HashMap::new());
// TODO: check that the wallet is not already present in localStorage
let lws = LocalWalletStorageV0 {
bootstrap: BootstrapContent::V0(BootstrapContentV0 { servers: vec![] }),
wallet: res.wallet.clone(),
client: res.client.priv_key.to_pub(),
};
let lws: LocalWalletStorageV0 = res.into();
wallets.insert(res.wallet_name.clone(), lws);
let lws_ser = serde_bare::to_vec(&LocalWalletStorage::V0(wallets)).unwrap();
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"
lazy_static = "1.4.0"
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 p2p_net::types::{SiteType, SiteV0};
use p2p_repo::log::*;
use p2p_repo::types::{PubKey, Timestamp};
use p2p_repo::utils::{generate_keypair, now_timestamp, sign, verify};
use p2p_repo::{log::*, types::PrivKey};
use rand::prelude::*;
use serde_bare::{from_slice, to_vec};
use web_time::Instant;
@ -130,6 +130,35 @@ pub fn enc_wallet_log(
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(
mut ciphertext: Vec<u8>,
master_key: &mut [u8; 32],
@ -151,14 +180,14 @@ pub fn dec_encrypted_block(
)
.map_err(|e| NgWalletError::DecryptionError)?;
// `ciphertext` now contains the decrypted block
//log_debug!("decrypted_block {:?}", ciphertext);
let decrypted_log =
from_slice::<WalletLog>(&ciphertext).map_err(|e| NgWalletError::DecryptionError)?;
master_key.zeroize();
// `ciphertext` now contains the decrypted block
//log_debug!("decrypted_block {:?}", ciphertext);
match decrypted_log {
WalletLog::V0(v0) => v0.reduce(),
}

@ -7,6 +7,7 @@
// notice may not be copied, modified, or distributed except
// according to those terms.
use p2p_repo::log::*;
use std::hash::{Hash, Hasher};
use std::{collections::HashMap, fmt};
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
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ClientV0 {
@ -121,18 +182,18 @@ pub struct EncryptedWalletV0 {
pub personal_site: PubKey,
#[zeroize(skip)]
pub sites: HashMap<PubKey, SiteV0>,
pub sites: HashMap<String, SiteV0>,
// map of brokers and their connection details
#[zeroize(skip)]
pub brokers: HashMap<PubKey, Vec<BrokerInfoV0>>,
pub brokers: HashMap<String, Vec<BrokerInfoV0>>,
// map of all devices of the user
#[zeroize(skip)]
pub clients: HashMap<PubKey, ClientV0>,
pub clients: HashMap<String, ClientV0>,
#[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.
/// 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 {
pub fn add_site(&mut self, site: SiteV0) {
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>) {
for broker in brokers {
let key = broker.get_id();
let key = broker.get_id().to_string();
let mut list = self.brokers.get_mut(&key);
if list.is_none() {
let new_list = vec![];
self.brokers.insert(key, new_list);
self.brokers.insert(key.clone(), new_list);
list = self.brokers.get_mut(&key);
}
list.unwrap().push(broker);
}
}
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);
}
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::AddSiteCoreV0((site, core, registration)) => {
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));
None::<SiteV0>
});
@ -305,7 +368,7 @@ impl WalletLogV0 {
WalletOperation::RemoveSiteCoreV0(_) => {}
WalletOperation::AddSiteBootstrapV0((site, server)) => {
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);
None::<SiteV0>
});
@ -320,7 +383,7 @@ impl WalletLogV0 {
WalletOperation::RemoveThirdPartyDataV0(_) => {}
WalletOperation::SetSiteRBDRefV0((site, store_type, rbdr)) => {
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 {
SiteStoreType::Public => {
site.public.root_branch_def_ref = rbdr.clone()
@ -338,7 +401,7 @@ impl WalletLogV0 {
}
WalletOperation::SetSiteRepoSecretV0((site, store_type, secret)) => {
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 {
SiteStoreType::Public => {
site.public.repo_secret = secret.clone()
@ -356,7 +419,7 @@ impl WalletLogV0 {
}
}
}
log_debug!("reduced {:?}", wallet);
Ok(wallet)
} else {
Err(NgWalletError::NoCreateWalletPresent)
@ -840,6 +903,7 @@ pub enum NgWalletError {
InvalidSignature,
NoCreateWalletPresent,
InvalidBootstrap,
SerializationError,
}
impl fmt::Display for NgWalletError {

@ -14,9 +14,10 @@ pnpm --ignore-workspace install
```
cd web
pnpm run dev
pnpm run dev --host
// in another terminal
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
// 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::broker::BROKER;
use p2p_repo::store::StorageError;
use serde::{Deserialize, Serialize};
use warp::reply::Response;
use warp::{Filter, Reply};
@ -51,11 +52,26 @@ struct Server {
domain: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
struct RegisterResponse {
url: Option<String>,
invite: Option<String>,
error: Option<String>,
}
impl Server {
async fn register_(&self, ca: String) -> Result<String, Option<String>> {
async fn register_(&self, ca: String) -> RegisterResponse {
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);
@ -90,51 +106,62 @@ impl Server {
)
.await;
let redirect_url = cabsp
.redirect_url()
.clone()
.unwrap_or(format!("{}{}", self.domain, APP_ACCOUNT_REGISTERED_SUFFIX));
let redirect_url = cabsp.redirect_url().clone().unwrap_or(format!(
"https://{}{}",
self.domain, APP_ACCOUNT_REGISTERED_SUFFIX
));
match res {
Err(e) => {
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))) => {
log_info!("invitation created successfully {:?}", invitation);
invitation.name = Some(self.domain.clone());
Ok(format!(
"{}?i={}&rs={}",
redirect_url,
Invitation::V0(invitation),
self.domain
))
let inv = Invitation::V0(invitation);
RegisterResponse {
url: Some(format!("{}?i={}&rs={}", redirect_url, inv, self.domain)),
invite: Some(format!("{inv}")),
error: None,
}
}
_ => {
log_err!(
"error while registering: invalid response from add_invitation {:?}",
cabsp
);
Err(Some(format!(
"{}?re={}",
redirect_url, "add_invitation_failed"
)))
let e = "add_invitation_failed";
RegisterResponse {
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> {
match self.register_(ca).await {
Ok(redirect_url) => {
let response = Response::new(redirect_url.into());
let res = self.register_(ca).await;
match &res {
RegisterResponse { error: None, .. } => {
let response = warp::reply::json(&res).into_response(); //Response::new(redirect_url.into());
let (mut parts, body) = response.into_parts();
parts.status = warp::http::StatusCode::OK;
let response = Response::from_parts(parts, body);
Ok(response)
}
Err(redirect_url) => {
RegisterResponse {
error: Some(e),
url: redirect_url,
..
} => {
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();
parts.status = warp::http::StatusCode::BAD_REQUEST;
let response = Response::from_parts(parts, body);

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

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

@ -36,35 +36,59 @@
try {
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
if (response.status == 406) {
close();
await close();
} else if (response.status == 400) {
close(result);
error = "We are redirecting you...";
go_back = false;
await close(result);
} else {
//console.log(result);
success(result);
await success(result);
}
} catch (e) {
error = e.message;
}
}
function close(url) {
// TODO tauri error code
if (url) {
window.location.href = url;
async function close(result) {
// @ts-ignore
if (window.__TAURI__) {
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 {
window.history.go(-1);
}
}
}
function success(url) {
window.location.href = url;
// TODO tauri success code
async function success(result) {
// @ts-ignore
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() {}
@ -127,7 +151,7 @@
<p class="max-w-xl md:mx-auto lg:max-w-2xl">
You would like to choose <b>{domain}</b> as your Broker Service
Provider.<br />Please read carefully the Terms of Service here below,
before you accept them.
before accepting them.
</p>
</div>
{/if}
@ -287,7 +311,6 @@
<div class="row mb-20">
<button
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"
>
<svg

@ -11,7 +11,9 @@
<script>
import { Button } from "flowbite-svelte";
// @ts-ignore
import EULogo from "../assets/EU.svg?component";
// @ts-ignore
import Logo from "../assets/nextgraph.svg?component";
import { link, querystring } from "svelte-spa-router";
@ -155,7 +157,6 @@
<div class="row mb-20">
<button
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"
>
<svg

@ -8,4 +8,4 @@ pub mod utils;
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::wallet::Wallet;
use crate::types::*;
use p2p_net::broker_storage::*;
use p2p_net::errors::ProtocolError;
use p2p_net::server_storage::*;
use p2p_net::types::{BootstrapContentV0, InvitationCode, InvitationV0};
use p2p_repo::kcv_store::KCVStore;
use p2p_repo::log::*;
@ -26,20 +26,19 @@ use stores_lmdb::kcv_store::LmdbKCVStore;
use stores_lmdb::repo_store::LmdbRepoStore;
#[derive(Debug)]
pub struct LmdbBrokerStorage {
pub struct LmdbServerStorage {
wallet_storage: LmdbKCVStore,
accounts_storage: LmdbKCVStore,
peers_storage: LmdbKCVStore,
}
impl LmdbBrokerStorage {
impl LmdbServerStorage {
pub fn open(
path: &mut PathBuf,
master_key: SymKey,
admin_invite: Option<BootstrapContentV0>,
) -> Result<Self, StorageError> {
// create/open the WALLET
let mut wallet_path = path.clone();
wallet_path.push("wallet");
std::fs::create_dir_all(wallet_path.clone()).unwrap();
@ -48,7 +47,6 @@ impl LmdbBrokerStorage {
let wallet = Wallet::open(&wallet_storage);
// create/open the ACCOUNTS storage
let mut accounts_path = path.clone();
let accounts_key;
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
let peers_storage = LmdbKCVStore::open(&peers_path, peers_key.slice().clone());
Ok(LmdbBrokerStorage {
Ok(LmdbServerStorage {
wallet_storage,
accounts_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> {
log_debug!("get_user {user_id}");
Ok(Account::open(&user_id, &self.accounts_storage)?.is_admin()?)

@ -12,7 +12,7 @@
//! WebSocket implementation of the Broker
use crate::interfaces::*;
use crate::storage::LmdbBrokerStorage;
use crate::server_storage::LmdbServerStorage;
use crate::types::*;
use async_std::io::ReadExt;
use async_std::net::{TcpListener, TcpStream};
@ -767,7 +767,7 @@ pub async fn run_server_v0(
std::fs::create_dir_all(path.clone()).unwrap();
// 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,
wallet_master_key,
if admin_invite {
@ -776,10 +776,10 @@ pub async fn run_server_v0(
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;
broker.set_storage(broker_storage);
broker.set_server_storage(broker_storage);
LISTENERS_INFO
.set(broker.set_listeners(listener_infos))
.unwrap();

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

@ -101,7 +101,7 @@ impl EActor for Actor<'_, AddUser, AdminResponse> {
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();
fsm.lock().await.send(response.into()).await?;
Ok(())

@ -81,7 +81,7 @@ impl EActor for Actor<'_, DelUser, AdminResponse> {
) -> Result<(), ProtocolError> {
let req = DelUser::try_from(msg)?;
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();
fsm.lock().await.send(response.into()).await?;
Ok(())

@ -97,7 +97,7 @@ impl EActor for Actor<'_, ListInvitations, AdminResponse> {
fsm: Arc<Mutex<NoiseFSM>>,
) -> Result<(), ProtocolError> {
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.unique(),
req.multi(),

@ -83,7 +83,11 @@ impl EActor for Actor<'_, ListUsers, AdminResponse> {
fsm: Arc<Mutex<NoiseFSM>>,
) -> Result<(), ProtocolError> {
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();
fsm.lock().await.send(response.into()).await?;
Ok(())

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

@ -12,7 +12,7 @@
use crate::{errors::ProtocolError, types::*};
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 add_user(&self, user_id: PubKey, is_admin: bool) -> Result<(), ProtocolError>;
fn del_user(&self, user_id: PubKey) -> Result<(), ProtocolError>;

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

Loading…
Cancel
Save