Niko PLP 10 months ago
parent 2e4f44a838
commit 58f21626a3
  1. 55
      Cargo.lock
  2. 112
      ng-app/src/App.svelte
  3. 4
      ng-app/src/api.ts
  4. 11
      ng-app/src/lib/Home.svelte
  5. 421
      ng-app/src/lib/Login.svelte
  6. 287
      ng-app/src/routes/Grid.svelte
  7. 45
      ng-app/src/routes/Home.svelte
  8. 13
      ng-app/src/routes/Install.svelte
  9. 125
      ng-app/src/routes/UserRegistered.svelte
  10. 955
      ng-app/src/routes/WalletCreate.svelte
  11. 270
      ng-app/src/routes/WalletLogin.svelte
  12. 108
      ng-app/src/store.ts
  13. 2
      ng-app/src/styles.css
  14. 3
      ng-sdk-js/Cargo.toml
  15. 44
      ng-sdk-js/js/browser.js
  16. 16
      ng-sdk-js/js/node.js
  17. 239
      ng-sdk-js/src/lib.rs
  18. 62
      ng-wallet/src/types.rs
  19. 1
      ngaccount/Cargo.toml
  20. 163
      ngaccount/src/main.rs
  21. 3
      ngaccount/web/package.json
  22. 7
      ngaccount/web/pnpm-lock.yaml
  23. 77
      ngaccount/web/src/routes/Create.svelte
  24. 4
      ngaccount/web/src/routes/Delete.svelte
  25. 2
      ngaccount/web/src/routes/Home.svelte
  26. 30
      ngcli/src/main.rs
  27. 2
      ngone/web/src/routes/Home.svelte
  28. 12
      ngone/web/src/routes/WalletCreate.svelte
  29. 39
      p2p-broker/src/broker_store/invitation.rs
  30. 17
      p2p-broker/src/storage.rs
  31. 1
      p2p-client-ws/src/remote_ws.rs
  32. 19
      p2p-net/src/actors/del_user.rs
  33. 82
      p2p-net/src/broker.rs
  34. 3
      p2p-net/src/broker_storage.rs
  35. 2
      p2p-net/src/connection.rs
  36. 2
      p2p-net/src/errors.rs
  37. 77
      p2p-net/src/types.rs
  38. 84
      p2p-net/src/utils.rs
  39. 1
      p2p-repo/src/utils.rs
  40. 14
      stores-lmdb/src/kcv_store.rs

55
Cargo.lock generated

@ -997,6 +997,22 @@ dependencies = [
"typenum",
]
[[package]]
name = "crypto_box"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd26c32de5307fd08aac445a75c43472b14559d5dccdfba8022dbcd075838ebc"
dependencies = [
"aead",
"blake2",
"chacha20",
"chacha20poly1305",
"salsa20",
"x25519-dalek 1.1.1",
"xsalsa20poly1305",
"zeroize",
]
[[package]]
name = "cssparser"
version = "0.27.2"
@ -2768,6 +2784,8 @@ name = "ng-sdk-js"
version = "0.1.0"
dependencies = [
"async-std",
"base64-url",
"crypto_box",
"futures",
"getrandom 0.1.16",
"gloo-timers",
@ -2777,6 +2795,7 @@ dependencies = [
"p2p-net",
"p2p-repo",
"pharos",
"rand 0.7.3",
"serde",
"serde-wasm-bindgen",
"serde_bare",
@ -2822,6 +2841,7 @@ dependencies = [
"env_logger",
"log",
"ng-wallet",
"p2p-client-ws",
"p2p-net",
"p2p-repo",
"rust-embed",
@ -2937,7 +2957,7 @@ dependencies = [
"chacha20poly1305",
"noise-protocol",
"sha2 0.10.7",
"x25519-dalek",
"x25519-dalek 2.0.0-rc.2",
"zeroize",
]
@ -4019,6 +4039,15 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
[[package]]
name = "salsa20"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
dependencies = [
"cipher",
]
[[package]]
name = "same-file"
version = "1.0.6"
@ -5982,6 +6011,17 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "x25519-dalek"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f"
dependencies = [
"curve25519-dalek 3.2.0",
"rand_core 0.5.1",
"zeroize",
]
[[package]]
name = "x25519-dalek"
version = "2.0.0-rc.2"
@ -6000,6 +6040,19 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47f9da296a88b6bc150b896d17770a62d4dc6f63ecf0ed10a9c08a1cb3d12f24"
[[package]]
name = "xsalsa20poly1305"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02a6dad357567f81cd78ee75f7c61f1b30bb2fe4390be8fb7c69e2ac8dffb6c7"
dependencies = [
"aead",
"poly1305",
"salsa20",
"subtle",
"zeroize",
]
[[package]]
name = "xxhash-rust"
version = "0.8.6"

@ -12,31 +12,131 @@
<script lang="ts">
// this line is needed to have the SDK working when compiling for a single file bundle (pnpm filebuild)
// import * as api from "ng-sdk-js";
import Router from "svelte-spa-router";
import { onMount, tick } from "svelte";
import { push, default as Router } from "svelte-spa-router";
import { onMount, tick, onDestroy } from "svelte";
import {
wallets,
active_wallet,
opened_wallets,
active_session,
} from "./store";
import Home from "./routes/Home.svelte";
import Test from "./routes/Test.svelte";
import Grid from "./routes/Grid.svelte";
import URI from "./routes/URI.svelte";
import NotFound from "./routes/NotFound.svelte";
import WalletCreate from "./routes/WalletCreate.svelte";
import WalletLogin from "./routes/WalletLogin.svelte";
import UserRegistered from "./routes/UserRegistered.svelte";
import Install from "./routes/Install.svelte";
import ng from "./api";
ng.test();
const routes = new Map();
routes.set("/", Home);
routes.set("/test", Test);
routes.set("/grid", Grid);
routes.set("/wallet/login", WalletLogin);
routes.set("/wallet/create", WalletCreate);
routes.set("/user/registered", UserRegistered);
if (import.meta.env.NG_APP_WEB) routes.set("/install", Install);
routes.set(/^\/ng(.*)/i, URI);
routes.set("*", NotFound);
let unsubscribe = () => {};
let wallet_channel;
onMount(async () => {
let tauri_platform = import.meta.env.TAURI_PLATFORM;
if (!tauri_platform) {
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(
Object.fromEntries((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 },
location.href
);
}
for (let opened of Object.keys($opened_wallets)) {
wallet_channel.postMessage(
{
cmd: "opened",
wallet: { wallet: $opened_wallets[opened], id: opened },
},
location.href
);
}
break;
case "opened":
if (!$opened_wallets[event.data.wallet.id]) {
opened_wallets.update((w) => {
w[event.data.wallet.id] = event.data.wallet.wallet;
return w;
});
}
break;
case "closed":
opened_wallets.update((w) => {
delete w[event.data.walletid];
return w;
});
if ($active_wallet && $active_wallet.id == event.data.walletid) {
active_session.set(undefined);
active_wallet.set(undefined);
}
break;
}
};
unsubscribe = active_wallet.subscribe((value) => {
if (value) {
if (value.wallet) {
wallet_channel.postMessage(
{ cmd: "opened", wallet: value },
location.href
);
} else {
wallet_channel.postMessage(
{ cmd: "closed", walletid: value.id },
location.href
);
active_wallet.set(undefined);
active_session.set(undefined);
}
} else {
//push("#/wallet/login");
}
});
}
});
onDestroy(unsubscribe);
</script>
<main class="">
<!-- <p>
{JSON.stringify(Object.keys($wallets))}
{JSON.stringify($active_wallet)}
{JSON.stringify(Object.keys($opened_wallets))}
{JSON.stringify($active_session)}
</p> -->
<Router {routes} />
</main>

@ -96,6 +96,8 @@ const handler = {
return await tauri.invoke(path[0],{params})
} else if (path[0].starts_with("get_local_bootstrap")) {
return false;
} else if (path[0].starts_with("get_local_url")) {
return false;
}
else {
let arg = {};
@ -112,6 +114,8 @@ 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 APP_ACCOUNT_REGISTERED_SUFFIX = "/#/user/registered";
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";

@ -12,20 +12,21 @@
<script>
import { Button } from "flowbite-svelte";
import { link } from "svelte-spa-router";
// @ts-ignore
import Logo from "../assets/nextgraph.svg?component";
import { has_wallets } from "../store";
import { onMount } from "svelte";
export let display_login_create = false;
</script>
<main class="container3">
{#if !$has_wallets || display_login_create}
<main class="container3">
<div class="row">
<Logo class="logo block h-40" alt="NextGraph Logo" />
</div>
<h1 class="text-2xl mb-10">Welcome to NextGraph</h1>
{#if display_login_create}
<p class="max-w-sm">
We could not find a wallet saved on this device.<br /> If you already have
a wallet, select "Log in", otherwise, select "Create Wallet" here below
@ -80,5 +81,5 @@
</button>
</a>
</div>
{/if}
</main>
</main>
{/if}

@ -0,0 +1,421 @@
<!--
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { Alert } from "flowbite-svelte";
import { onMount, createEventDispatcher, tick } from "svelte";
import ng from "../api";
import { emoji_cat, emojis, load_svg } from "../wallet_emojis";
export let wallet;
const dispatch = createEventDispatcher();
onMount(async () => {
await load_svg();
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() {
shuffle = await ng.wallet_gen_shuffle_for_pazzle_opening(pazzle_length);
emojis2 = [];
for (const [idx, cat_idx] of shuffle.category_indices.entries()) {
let cat = emojis[emoji_cat[cat_idx]];
let items = [];
for (const id of shuffle.emoji_indices[idx]) {
items.push(cat[id]);
}
emojis2.push(items);
}
emojis2 = emojis2;
display = 0;
selection = [];
error = undefined;
step = "pazzle";
}
let emojis2 = [];
let shuffle;
let step = "load";
let pazzle_length = 9;
let display = 0;
let selection = [];
let pin_code = [];
let ordered = [];
let last_one = {};
let shuffle_pin;
let error;
function order() {
step = "order";
ordered = [];
last_one = {};
for (let i = 0; i < pazzle_length; i++) {
last_one[i] = true;
}
}
async function start_pin() {
pin_code = [];
console.log(ordered);
shuffle_pin = await ng.wallet_gen_shuffle_for_pin();
step = "pin";
console.log(shuffle_pin);
}
function select(val) {
//console.log(emojis2[display][val]);
let cat_idx = shuffle.category_indices[display];
let cat = emojis[emoji_cat[cat_idx]];
let idx = shuffle.emoji_indices[display][val];
console.log(cat_idx, emoji_cat[cat_idx], idx, cat[idx].code);
selection.push({ cat: cat_idx, index: idx });
console.log(selection);
if (display == pazzle_length - 1) {
order();
} else {
display = display + 1;
}
}
async function finish() {
step = "opening";
let pazzle = [];
for (const emoji of ordered) {
pazzle.push((emoji.cat << 4) + emoji.index);
}
console.log(pazzle);
// open the wallet
try {
let secret_wallet = await ng.wallet_open_wallet_with_pazzle(
wallet,
pazzle,
pin_code
);
step = "end";
console.log(secret_wallet);
dispatch("opened", {
wallet: secret_wallet,
id: secret_wallet.V0.wallet_id,
});
} catch (e) {
console.error(e);
error = e;
step = "end";
dispatch("error", { error: e });
}
// display the result
}
function cancel() {
dispatch("cancel");
}
async function pin(val) {
console.log(val);
pin_code.push(val);
if (pin_code.length == 4) {
await finish();
}
}
async function select_order(val, pos) {
delete last_one[pos];
console.log(last_one);
console.log(val);
ordered.push(val);
val.sel = ordered.length;
selection = selection;
if (ordered.length == pazzle_length - 1) {
let last = selection[Object.keys(last_one)[0]];
ordered.push(last);
last.sel = ordered.length;
selection = selection;
console.log(last);
await start_pin();
}
}
</script>
{#if step == "load"}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-primary-700">
Loading...
<svg
class="animate-spin mt-10 h-14 w-14 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
</div>
{:else if step == "pazzle"}
<div class="h-screen aspect-[3/5] pazzleline max-w-[500px] min-w-[200px]">
{#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}
<div
role="button"
tabindex="0"
class="w-full aspect-square emoji"
on:click={() => select(row * 3 + i)}
on:keypress={() => select(row * 3 + i)}
>
<svelte:component this={emoji.svg?.default} />
</div>
{/each}
</div>
{/each}
</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]">
{#each [0, 1, 2] as row}
<div class="columns-3 gap-0">
{#each selection.slice(0 + row * 3, 3 + row * 3) || [] as emoji, i}
{#if !emoji.sel}
<div
role="button"
tabindex="0"
class="w-full aspect-square emoji"
on:click={() => select_order(emoji, row * 3 + i)}
on:keypress={() => select_order(emoji, row * 3 + i)}
>
<svelte:component
this={emojis[emoji_cat[emoji.cat]][emoji.index].svg?.default}
/>
</div>
{:else}
<div class="w-full aspect-square opacity-25 select-none sel-emoji">
<svelte:component
this={emojis[emoji_cat[emoji.cat]][emoji.index].svg?.default}
/>
<span class="sel drop-shadow-[2px_2px_2px_rgba(255,255,255,1)]"
>{emoji.sel}</span
>
</div>
{/if}
{/each}
</div>
{/each}
</div>
{:else if step == "pin"}
<div class=" max-w-6xl lg:px-8 mx-auto px-4">
<p class="max-w-xl md:mx-auto lg:max-w-2xl">
<span class="text-xl">Enter your PIN code</span>
</p>
<div class="w-[325px] mx-auto">
{#each [0, 1, 2] as row}
<div class="">
{#each shuffle_pin.slice(0 + row * 3, 3 + row * 3) as num}
<button
tabindex="0"
class="m-1 select-none align-bottom text-7xl w-[100px] h-[100px] p-0"
on:click={async () => await pin(num)}
>
<span>{num}</span>
</button>
{/each}
</div>
{/each}
<button
tabindex="0"
class="m-1 select-none mx-auto align-bottom text-7xl w-[100px] h-[100px] p-0"
on:click={async () => await pin(shuffle_pin[9])}
>
<span>{shuffle_pin[9]}</span>
</button>
</div>
</div>
{:else if step == "opening"}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-primary-700">
Opening your wallet...<br />
Please wait
<svg
class="animate-spin mt-10 h-14 w-14 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
</div>
{:else if step == "end"}
{#if error}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
An error occurred !
<svg
fill="none"
class="animate-bounce mt-10 h-10 w-10 mx-auto"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
<Alert color="red" class="mt-5">
{error}
</Alert>
<button class="mt-10 select-none" on:click={init}> Try again </button>
<button class="mt-10 ml-5 select-none" on:click={cancel}> Cancel </button>
</div>
{:else}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-green-800">
Your wallet is opened!
<svg
class="my-10 h-16 w-16 mx-auto"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 12.75L11.25 15 15 9.75M21 12c0 1.268-.63 2.39-1.593 3.068a3.745 3.745 0 01-1.043 3.296 3.745 3.745 0 01-3.296 1.043A3.745 3.745 0 0112 21c-1.268 0-2.39-.63-3.068-1.593a3.746 3.746 0 01-3.296-1.043 3.745 3.745 0 01-1.043-3.296A3.745 3.745 0 013 12c0-1.268.63-2.39 1.593-3.068a3.745 3.745 0 011.043-3.296 3.746 3.746 0 013.296-1.043A3.746 3.746 0 0112 3c1.268 0 2.39.63 3.068 1.593a3.746 3.746 0 013.296 1.043 3.746 3.746 0 011.043 3.296A3.745 3.745 0 0121 12z"
/>
</svg>
</div>
{/if}
{/if}
<style>
.pazzleline {
margin-right: auto;
margin-left: auto;
}
.pin {
cursor: pointer;
text-align: center;
}
.sel {
position: relative;
top: -56%;
font-size: 100px;
font-weight: 700;
}
.sel-emoji {
overflow: hidden;
}
.emoji {
cursor: pointer;
/* padding: 0;
margin: 0;
border: 0;
box-shadow: none; */
}
</style>

@ -1,287 +0,0 @@
<!--
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { onMount } from "svelte";
import ng from "../api";
import { emoji_cat, emojis, load_svg } from "../wallet_emojis";
onMount(async () => {
await load_svg();
shuffle = await ng.wallet_gen_shuffle_for_pazzle_opening(pazzle_length);
for (const [idx, cat_idx] of shuffle.category_indices.entries()) {
let cat = emojis[emoji_cat[cat_idx]];
let items = [];
for (const id of shuffle.emoji_indices[idx]) {
items.push(cat[id]);
}
emojis2.push(items);
}
emojis2 = emojis2;
//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();
});
let wallet;
let emojis2 = [];
let shuffle;
let step = "pazzle";
let pazzle_length = 9;
let display = 0;
let selection = [];
let pin_code = [];
let ordered = [];
let last_one = {};
let shuffle_pin;
function order() {
step = "order";
last_one = {};
for (let i = 0; i < pazzle_length; i++) {
last_one[i] = true;
}
}
async function start_pin() {
console.log(ordered);
shuffle_pin = await ng.wallet_gen_shuffle_for_pin();
step = "pin";
console.log(shuffle_pin);
}
function select(val) {
//console.log(emojis2[display][val]);
let cat_idx = shuffle.category_indices[display];
let cat = emojis[emoji_cat[cat_idx]];
let idx = shuffle.emoji_indices[display][val];
console.log(cat_idx, emoji_cat[cat_idx], idx, cat[idx].code);
selection.push({ cat: cat_idx, index: idx });
console.log(selection);
if (display == pazzle_length - 1) {
order();
} else {
display = display + 1;
}
}
async function finish() {
step = "end";
let pazzle = [];
for (const emoji of ordered) {
pazzle.push((emoji.cat << 4) + emoji.index);
}
console.log(pazzle);
// open the wallet
try {
let secret_wallet = await ng.wallet_open_wallet_with_pazzle(
wallet,
pazzle,
pin_code
);
console.log(secret_wallet);
} catch (e) {
console.error(e);
}
// display the result
}
async function pin(val) {
console.log(val);
pin_code.push(val);
if (pin_code.length == 4) {
await finish();
}
}
async function select_order(val, pos) {
delete last_one[pos];
console.log(last_one);
console.log(val);
ordered.push(val);
val.sel = ordered.length;
selection = selection;
if (ordered.length == pazzle_length - 1) {
let last = selection[Object.keys(last_one)[0]];
ordered.push(last);
last.sel = ordered.length;
selection = selection;
console.log(last);
await start_pin();
}
}
</script>
{#if step == "pazzle"}
<div class="h-screen aspect-[3/5] pazzleline max-w-[500px] min-w-[200px]">
{#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}
<div
role="button"
tabindex="0"
class="w-full aspect-square emoji"
on:click={() => select(row * 3 + i)}
on:keypress={() => select(row * 3 + i)}
>
<svelte:component this={emoji.svg?.default} />
</div>
{/each}
</div>
{/each}
</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]">
{#each [0, 1, 2] as row}
<div class="columns-3 gap-0">
{#each selection.slice(0 + row * 3, 3 + row * 3) || [] as emoji, i}
{#if !emoji.sel}
<div
role="button"
tabindex="0"
class="w-full aspect-square emoji"
on:click={() => select_order(emoji, row * 3 + i)}
on:keypress={() => select_order(emoji, row * 3 + i)}
>
<svelte:component
this={emojis[emoji_cat[emoji.cat]][emoji.index].svg?.default}
/>
</div>
{:else}
<div class="w-full aspect-square opacity-25 select-none sel-emoji">
<svelte:component
this={emojis[emoji_cat[emoji.cat]][emoji.index].svg?.default}
/>
<span class="sel drop-shadow-[2px_2px_2px_rgba(255,255,255,1)]"
>{emoji.sel}</span
>
</div>
{/if}
{/each}
</div>
{/each}
</div>
{:else if step == "pin"}
<div class="aspect-[5/2] pazzleline max-w-[800px] min-w-[200px] mt-20">
{#each [0, 1] as row}
<div class="columns-5 gap-0">
{#each shuffle_pin.slice(0 + row * 5, 5 + row * 5) as num, i}
<div
role="button"
tabindex="0"
class="w-full aspect-square pin align-bottom text-9xl"
on:click={async () => await pin(num)}
on:keypress={async () => await pin(num)}
>
<span>{num}</span>
</div>
{/each}
</div>
{/each}
</div>
{:else if step == "end"}{/if}
<style>
.pazzleline {
margin-right: auto;
margin-left: auto;
}
.pin {
cursor: pointer;
text-align: center;
}
.sel {
position: relative;
top: -56%;
font-size: 100px;
left: 30%;
font-weight: 700;
}
.sel-emoji {
overflow: hidden;
}
.emoji {
cursor: pointer;
/* padding: 0;
margin: 0;
border: 0;
box-shadow: none; */
}
</style>

@ -13,25 +13,34 @@
import { Button } from "flowbite-svelte";
import { link } from "svelte-spa-router";
import Home from "../lib/Home.svelte";
import { onMount } from "svelte";
import { push } from "svelte-spa-router";
import { onMount, onDestroy } from "svelte";
import {
wallets,
active_wallet,
opened_wallets,
active_session,
has_wallets,
derived,
} from "../store";
let display_login_create = false;
async function bootstrap() {
let bs;
try {
bs = localStorage.getItem("bootstrap");
} catch (e) {}
if (bs) {
} else {
// probe localhost and LAN
// if nothing found, displays login/create account
console.log("no wallet found");
display_login_create = true;
let unsubscribe;
onMount(() => {
const combined = derived([active_wallet, has_wallets], ([$s1, $s2]) => [
$s1,
$s2,
]);
unsubscribe = combined.subscribe((value) => {
console.log(value);
if (!value[0] && value[1]) {
push("#/wallet/login");
}
}
onMount(() => bootstrap());
});
});
onDestroy(() => {
unsubscribe();
});
</script>
<Home {display_login_create} />
<Home />

@ -12,23 +12,16 @@
<script>
import { Button, Alert } from "flowbite-svelte";
import { link } from "svelte-spa-router";
import { has_wallets } from "../store";
// @ts-ignore
import Logo from "../assets/nextgraph.svg?component";
import { onMount } from "svelte";
let top;
let display_note_on_local_wallets = false;
async function bootstrap() {
scrollToTop();
let bs;
try {
bs = localStorage.getItem("bootstrap");
} catch (e) {}
if (bs) {
display_note_on_local_wallets = true;
}
}
function scrollToTop() {
@ -50,7 +43,7 @@
mobile, tablet, laptop and desktop.<br /> The app supports iOS, Android, Linux,
macOS, Windows and all other platforms that can run a modern web browser.
</p>
{#if display_note_on_local_wallets}
{#if $has_wallets}
<Alert color="yellow" class="mt-5 block">
A wallet is saved in this browser. If it is yours,<br /> once the
installation of the app will be finished,<br /> choose the option "Login"

@ -0,0 +1,125 @@
<!--
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script>
import { Button, Alert, Dropzone, Toggle } from "flowbite-svelte";
import { link, querystring } from "svelte-spa-router";
// @ts-ignore
import Logo from "../assets/nextgraph.svg?component";
import { onMount, tick } from "svelte";
import {
NG_EU_BSP,
NG_NET_BSP,
NG_EU_BSP_REGISTER,
NG_EU_BSP_REGISTERED,
APP_ACCOUNT_REGISTERED_SUFFIX,
default as ng,
} from "../api";
const param = new URLSearchParams($querystring);
let tauri_platform = import.meta.env.TAURI_PLATFORM;
let mobile = tauri_platform == "android" || tauri_platform == "ios";
let error = param.get("e");
let invite = param.get("i");
let invitation;
let user = param.get("u");
onMount(async () => {
if (invite) {
invitation = await ng.decode_invitation(invite);
}
});
</script>
<main class="container3">
<div class="row">
<a href="#/">
<Logo class="logo block h-40" alt="NextGraph Logo" />
</a>
</div>
{#if error}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
<svg
class="animate-bounce mt-10 h-16 w-16 mx-auto"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
{#if error == "AlreadyExists"}
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
The user is already registered with the selected broker.<br /> Try logging
in instead
</p>
<a use:link href="/">
<button
tabindex="-1"
class="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"
>
Login
</button>
</a>
{:else}
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
An error occurred:<br />{error}
</p>
<a use:link href="/">
<button
tabindex="-1"
class="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"
>
Go back to homepage
</button>
</a>
{/if}
</div>
{:else if invite && user}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-green-800">
<svg
class="mt-10 h-16 w-16 mx-auto"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 12.75L11.25 15 15 9.75M21 12c0 1.268-.63 2.39-1.593 3.068a3.745 3.745 0 01-1.043 3.296 3.745 3.745 0 01-3.296 1.043A3.745 3.745 0 0112 21c-1.268 0-2.39-.63-3.068-1.593a3.746 3.746 0 01-3.296-1.043 3.745 3.745 0 01-1.043-3.296A3.745 3.745 0 013 12c0-1.268.63-2.39 1.593-3.068a3.745 3.745 0 011.043-3.296 3.746 3.746 0 013.296-1.043A3.746 3.746 0 0112 3c1.268 0 2.39.63 3.068 1.593a3.746 3.746 0 013.296 1.043 3.746 3.746 0 011.043 3.296A3.745 3.745 0 0121 12z"
/>
</svg>
<p class="max-w-xl md:mx-auto lg:max-w-2xl">
You have been successfully <br />registered {#if invitation?.V0?.name}
to {invitation?.V0?.name}{/if}
</p>
</div>
{/if}
</main>
<style>
</style>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,270 @@
<!--
// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
-->
<script lang="ts">
import { onMount, onDestroy } from "svelte";
import { link, push } from "svelte-spa-router";
import Login from "../lib/Login.svelte";
import ng from "../api";
// @ts-ignore
import Logo from "../assets/nextgraph.svg?component";
import {
wallets,
active_wallet,
opened_wallets,
active_session,
set_active_session,
has_wallets,
} from "../store";
let wallet;
let selected; //= "8Hg1Rf7us3LFhs7HCbxCQOWJoV-OUyALbTuSaKp7D-M";
let step;
let wallets_unsub;
let opened_wallets_unsub;
let active_wallet_unsub;
function convert_img_to_url(buffer) {
var blob = new Blob([buffer], {
type: "image/jpeg",
});
var imageUrl = URL.createObjectURL(blob);
return imageUrl;
}
onMount(async () => {
step = "open";
wallets_unsub = wallets.subscribe((value) => {
wallet = selected && $wallets[selected]?.wallet;
console.log("wallet found locally", wallet);
});
opened_wallets_unsub = opened_wallets.subscribe(async (value) => {
if (!$active_wallet && selected && value[selected]) {
active_wallet.set({ wallet: value[selected], id: selected });
}
});
active_wallet_unsub = active_wallet.subscribe(async (value) => {
if (value && value.wallet) {
if (!$active_session) {
let session = await ng.get_local_session(
value.id,
value.wallet.V0.wallet_privkey,
value.wallet.V0.personal_site
);
console.log(session);
if (session) {
set_active_session(session);
loggedin();
}
}
}
});
});
function loggedin() {
step = "loggedin";
push("#/");
}
onDestroy(() => {
if (wallets_unsub) wallets_unsub();
if (opened_wallets_unsub) opened_wallets_unsub();
if (active_wallet_unsub) active_wallet_unsub();
});
async function gotError(event) {
console.error(event.detail);
}
async function gotWallet(event) {
console.log(event.detail);
active_wallet.set(event.detail);
// wallet
// id
}
function cancelLogin(event) {
selected = undefined;
wallet = undefined;
}
function select(id) {
selected = id;
if ($opened_wallets[selected]) {
active_wallet.set({ wallet: $opened_wallets[selected], id: selected });
} else {
wallet = $wallets[selected]?.wallet;
}
}
</script>
{#if wallet}
<Login
{wallet}
on:error={gotError}
on:opened={gotWallet}
on:cancel={cancelLogin}
/>
{:else if !$active_wallet && !selected}
<main class="">
<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>
<div class="flex flex-wrap justify-center gap-5 mb-20">
{#each Object.entries($wallets) as wallet_entry}
<div
class="wallet-box"
role="button"
tabindex="0"
title={wallet_entry[0]}
on:click={() => {
select(wallet_entry[0]);
}}
on:keypress={() => {
select(wallet_entry[0]);
}}
>
<span class="securitytxt"
>{wallet_entry[1].wallet.V0.content.security_txt}
{wallet_entry[0]}</span
>
<img
alt={wallet_entry[1].wallet.V0.content.security_txt}
class="securityimg"
src={convert_img_to_url(
wallet_entry[1].wallet.V0.content.security_img
)}
/>
</div>
{/each}
<div class="wallet-box">
{#if $has_wallets}<p class="mt-1">Log in with another wallet</p>
{:else}<p class="mt-1">Import your wallet</p>
{/if}
<button
class=" mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/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-100/55 mb-2"
>
<svg
class="w-8 h-8 mr-2 -ml-1"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 8.25H7.5a2.25 2.25 0 00-2.25 2.25v9a2.25 2.25 0 002.25 2.25h9a2.25 2.25 0 002.25-2.25v-9a2.25 2.25 0 00-2.25-2.25H15M9 12l3 3m0 0l3-3m-3 3V2.25"
/>
</svg>
Import a Wallet File
</button>
<button
class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/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-100/55 mb-2"
>
<svg
class="w-8 h-8 mr-2 -ml-1"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 013.75 9.375v-4.5zM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 01-1.125-1.125v-4.5zM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0113.5 9.375v-4.5z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6.75 6.75h.75v.75h-.75v-.75zM6.75 16.5h.75v.75h-.75v-.75zM16.5 6.75h.75v.75h-.75v-.75zM13.5 13.5h.75v.75h-.75v-.75zM13.5 19.5h.75v.75h-.75v-.75zM19.5 13.5h.75v.75h-.75v-.75zM19.5 19.5h.75v.75h-.75v-.75zM16.5 16.5h.75v.75h-.75v-.75z"
/>
</svg>
Import with QRcode
</button>
<button
class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/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-100/55 mb-2"
>
<svg
class="w-8 h-8 mr-2 -ml-1"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"
/>
</svg>
Enter a Wallet Link
</button>
<a href="/wallet/create" use:link>
<button
tabindex="-1"
class="mt-1 text-white bg-primary-700 hover:bg-primary-700/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-700/55 mb-2"
>
<svg
class="w-8 h-8 mr-2 -ml-1"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19 7.5v3m0 0v3m0-3h3m-3 0h-3m-2.25-4.125a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zM4 19.235v-.11a6.375 6.375 0 0112.75 0v.109A12.318 12.318 0 0110.374 21c-2.331 0-4.512-.645-6.374-1.766z"
/>
</svg>
Create a new wallet
</button>
</a>
</div>
</div>
</main>
{:else if step == "security"}{:else if step == "qrcode"}{:else if step == "drop"}{:else if step == "cloud"}{:else if step == "loggedin"}you
are logged in{/if}
<style>
.wallet-box {
width: 300px;
height: 300px;
background-color: white;
position: relative;
cursor: pointer;
}
button {
min-width: 250px;
}
.securitytxt {
z-index: 100;
width: 300px;
position: absolute;
left: 0;
padding: 5px;
background-color: #ffffff70;
overflow-wrap: break-word;
}
.wallet-box:focus .securitytxt {
background-color: #ffffffff;
}
.securityimg {
position: absolute;
left: 0;
top: 0;
}
</style>

@ -1,33 +1,113 @@
import { writable } from "svelte/store";
import { writable, readonly, derived } from "svelte/store";
import ng from "./api";
let all_branches = {};
export const opened_wallets = writable({});
/// { wallet:, id: }
export const active_wallet = writable(undefined);
export const wallets = writable({});
export const has_wallets = derived(wallets,($wallets) => Object.keys($wallets).length);
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);
};
export { writable, readonly, derived };
const close_active_wallet = function() {
active_session.set(undefined);
active_wallet.update((w) => {
delete w.wallet;
});
}
const branch_commits = (nura, sub) => {
// console.log("branch_commits")
// const { subscribe, set, update } = writable([]); // create the underlying writable store
// let unsub = () => {};
// return {
// load: async () => {
// console.log("load")
// unsub = await ng.doc_sync_branch(nura, async (commit) => {
// console.log(commit);
// update( (old) => {old.unshift(commit); return old;} )
// });
// },
// subscribe: (run, invalid) => {
// console.log("sub")
// let upper_unsub = subscribe(run, invalid);
// return () => {
// upper_unsub();
// unsub();
// }
// }
// // set: (value) => {
// // localStorage.setItem(key, toString(value)); // save also to local storage as a string
// // return set(value);
// // },
// // update,
// };
const { subscribe, set, update } = writable([]); // create the underlying writable store
let unsub = () => {};
return {
load: async () => {
let already_subscribed = all_branches[nura];
if (!already_subscribed) return;
if (already_subscribed.load) {
await already_subscribed.load();
already_subscribed.load = undefined;
}
},
subscribe: (run, invalid) => {
let already_subscribed = all_branches[nura];
if (!already_subscribed) {
const { subscribe, set, update } = writable([]); // create the underlying writable store
let count = 0;
let unsub = () => {};
already_subscribed = {
load: async () => {
unsub = await ng.doc_sync_branch(nura, async (commit) => {
console.log(commit);
update( (old) => {old.unshift(commit); return old;} )
});
},
subscribe: (run, invalid) => {
let upper_unsub = subscribe(run, invalid);
increase: () => {
count += 1;
return readonly({subscribe});
},
decrease: () => {
count -= 1;
if (count == 0) {
unsub();
delete all_branches[nura];
}
},
}
all_branches[nura] = already_subscribed;
}
let new_store = already_subscribed.increase();
let read_unsub = new_store.subscribe(run, invalid);
return () => {
upper_unsub();
unsub();
read_unsub();
already_subscribed.decrease();
}
}
}
// set: (value) => {
// localStorage.setItem(key, toString(value)); // save also to local storage as a string
// return set(value);
// },
// update,
};
};
export default branch_commits;

@ -63,7 +63,7 @@ body {
}
#app {
max-width: 1280px;
/*max-width: 1280px;*/
margin: 0 auto;
padding: 0rem;
text-align: center;

@ -29,6 +29,9 @@ 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"
# [target.'cfg(target_arch = "wasm32")'.dependencies.getrandom]
# version = "0.2.7"

@ -3,7 +3,7 @@ export function client_details() {
}
export function client_details2(obj,version) {
console.log("version",version)
//console.log("version",version)
obj.browser.appVersion = navigator?.appVersion;
obj.browser.arch = navigator?.platform;
obj.browser.vendor = navigator?.vendor;
@ -11,3 +11,45 @@ export function client_details2(obj,version) {
obj.engine.sdk = version;
return JSON.stringify(obj);
}
export function session_save(key,value) {
try {
sessionStorage.setItem(key, value);
} catch(e) {
console.error(e);
return e.message;
}
}
export function session_get(key) {
try {
return sessionStorage.getItem(key);
} catch(e) {
console.error(e);
}
}
export function local_save(key,value) {
try {
localStorage.setItem(key, value);
} catch(e) {
console.error(e);
return e.message;
}
}
export function local_get(key) {
try {
return localStorage.getItem(key);
} catch(e) {
console.error(e);
}
}

@ -120,3 +120,19 @@ module.exports.client_details = function () {
}
});
};
module.exports.session_save = function(key,value) {
}
module.exports.session_get = function(key) {
}
module.exports.local_save = function(key,value) {
}
module.exports.local_get = function(key) {
}

@ -22,15 +22,20 @@ use p2p_client_ws::remote_ws_wasm::ConnectionWebSocket;
use p2p_net::broker::*;
use p2p_net::connection::{ClientConfig, StartConfig};
use p2p_net::types::{
BootstrapContentV0, ClientInfo, ClientInfoV0, ClientType, CreateAccountBSP, DirectPeerId, IP,
BootstrapContent, BootstrapContentV0, ClientId, ClientInfo, ClientInfoV0, ClientType,
CreateAccountBSP, DirectPeerId, UserId, IP,
};
use p2p_net::utils::{
decode_invitation_string, retrieve_local_bootstrap, retrieve_local_url, spawn_and_log_error,
Receiver, ResultSend, Sender,
};
use p2p_net::utils::{retrieve_local_bootstrap, spawn_and_log_error, Receiver, ResultSend, Sender};
use p2p_net::WS_PORT;
use p2p_repo::log::*;
use p2p_repo::types::*;
use p2p_repo::utils::generate_keypair;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::collections::HashMap;
use std::net::IpAddr;
use std::str::FromStr;
use std::sync::Arc;
@ -49,6 +54,28 @@ pub async fn get_local_bootstrap(location: String, invite: JsValue) -> JsValue {
}
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn decode_invitation(invite: String) -> JsValue {
let res = decode_invitation_string(invite);
if res.is_some() {
serde_wasm_bindgen::to_value(&res.unwrap()).unwrap()
} else {
JsValue::FALSE
}
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn get_local_url(location: String) -> JsValue {
let res = retrieve_local_url(location).await;
if res.is_some() {
serde_wasm_bindgen::to_value(&res.unwrap()).unwrap()
} else {
JsValue::FALSE
}
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn get_local_bootstrap_with_public(location: String, invite: JsValue) -> JsValue {
@ -91,6 +118,190 @@ pub fn wallet_open_wallet_with_pazzle(
}
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn wallet_update(js_wallet_id: JsValue, js_operations: JsValue) -> Result<JsValue, JsValue> {
let wallet = serde_wasm_bindgen::from_value::<WalletId>(js_wallet_id)
.map_err(|_| "Deserialization error of WalletId")?;
let operations = serde_wasm_bindgen::from_value::<Vec<WalletOperation>>(js_operations)
.map_err(|_| "Deserialization error of operations")?;
unimplemented!();
// match res {
// Ok(r) => Ok(serde_wasm_bindgen::to_value(&r).unwrap()),
// Err(e) => Err(e.to_string().into()),
// }
}
#[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() {
let map_ser = base64_url::decode(&wallets_string.unwrap()).unwrap();
let wallets: LocalWalletStorage = serde_bare::from_slice(&map_ser).unwrap();
let LocalWalletStorage::V0(v0) = wallets;
Ok(v0)
} else {
Err(())
}
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn get_wallets_from_localstorage() -> JsValue {
let res = get_local_wallets_v0();
if res.is_ok() {
return serde_wasm_bindgen::to_value(&res.unwrap()).unwrap();
}
JsValue::UNDEFINED
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn get_local_session(id: String, key_js: JsValue, user_js: JsValue) -> JsValue {
let res = session_get(format!("ng_wallet@{}", id));
if res.is_some() {
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 {
// 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);
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 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(),
};
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);
let r = local_save("ng_wallets".to_string(), encoded);
if r.is_some() {
return Err(r.unwrap());
}
Ok(sws)
}
#[cfg(not(wasmpack_target = "nodejs"))]
#[wasm_bindgen(module = "/js/browser.js")]
extern "C" {
fn session_save(key: String, value: String) -> Option<String>;
fn session_get(key: String) -> Option<String>;
fn local_save(key: String, value: String) -> Option<String>;
fn local_get(key: String) -> Option<String>;
}
#[cfg(wasmpack_target = "nodejs")]
#[wasm_bindgen(module = "/js/node.js")]
extern "C" {
fn session_save(key: String, value: String) -> Option<String>;
fn session_get(key: String) -> Option<String>;
fn local_save(key: String, value: String) -> Option<String>;
fn local_get(key: String) -> Option<String>;
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn wallet_create_wallet(js_params: JsValue) -> Result<JsValue, JsValue> {
@ -99,7 +310,10 @@ pub async fn wallet_create_wallet(js_params: JsValue) -> Result<JsValue, JsValue
params.result_with_wallet_file = true;
let res = create_wallet_v0(params).await;
match res {
Ok(r) => Ok(serde_wasm_bindgen::to_value(&r).unwrap()),
Ok(r) => {
let session = save_wallet_locally(&r)?;
Ok(serde_wasm_bindgen::to_value(&(r, session)).unwrap())
}
Err(e) => Err(e.to_string().into()),
}
}
@ -133,7 +347,7 @@ extern "C" {
#[cfg(wasmpack_target = "nodejs")]
#[wasm_bindgen]
pub fn client_info() -> ClientInfoV0 {
pub fn client_info() -> JsValue {
let res = ClientInfoV0 {
client_type: ClientType::NodeService,
details: client_details(),
@ -141,8 +355,8 @@ pub fn client_info() -> ClientInfoV0 {
timestamp_install: 0,
timestamp_updated: 0,
};
res
//serde_wasm_bindgen::to_value(&res).unwrap()
//res
serde_wasm_bindgen::to_value(&res).unwrap()
}
#[cfg(target_arch = "wasm32")]
@ -177,8 +391,7 @@ extern "C" {
}
#[cfg(all(not(wasmpack_target = "nodejs"), target_arch = "wasm32"))]
#[wasm_bindgen]
pub fn client_info() -> ClientInfoV0 {
pub fn client_info_() -> ClientInfoV0 {
let ua = client_details();
let bowser = Bowser::parse(ua);
@ -197,6 +410,13 @@ pub fn client_info() -> ClientInfoV0 {
//serde_wasm_bindgen::to_value(&res).unwrap()
}
#[cfg(all(not(wasmpack_target = "nodejs"), target_arch = "wasm32"))]
#[wasm_bindgen]
pub fn client_info() -> JsValue {
let res = client_info_();
serde_wasm_bindgen::to_value(&res).unwrap()
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn test() {
@ -336,7 +556,8 @@ pub async fn start() {
user_priv,
client,
client_priv,
info: ClientInfo::V0(client_info()),
info: ClientInfo::V0(client_info_()),
registration: None,
}),
)
.await;

@ -104,6 +104,9 @@ pub enum SaveToNGOne {
pub struct EncryptedWalletV0 {
pub wallet_privkey: PrivKey,
#[zeroize(skip)]
pub wallet_id: String,
#[serde(with = "serde_bytes")]
pub pazzle: Vec<u8>,
@ -210,7 +213,7 @@ pub struct WalletContentV0 {
/// Wallet Log V0
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct WalletLogV0 {
pub log: Vec<(u128, WalletOperationV0)>,
pub log: Vec<(u128, WalletOperation)>,
}
/// Wallet Log
@ -228,11 +231,11 @@ impl WalletLog {
impl WalletLogV0 {
pub fn new(create_op: WalletOpCreateV0) -> Self {
let mut wallet = WalletLogV0 { log: vec![] };
wallet.add(WalletOperationV0::CreateWalletV0(create_op));
wallet.add(WalletOperation::CreateWalletV0(create_op));
wallet
}
pub fn add(&mut self, op: WalletOperationV0) {
pub fn add(&mut self, op: WalletOperation) {
let duration = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
@ -244,49 +247,49 @@ impl WalletLogV0 {
pub fn reduce(&self) -> Result<EncryptedWalletV0, NgWalletError> {
if self.log.len() < 1 {
Err(NgWalletError::NoCreateWalletPresent)
} else if let (_, WalletOperationV0::CreateWalletV0(create_op)) = &self.log[0] {
} else if let (_, WalletOperation::CreateWalletV0(create_op)) = &self.log[0] {
let mut wallet: EncryptedWalletV0 = create_op.into();
for op in &self.log {
match &op.1 {
WalletOperationV0::CreateWalletV0(_) => { /* intentionally left blank. this op is already reduced */
WalletOperation::CreateWalletV0(_) => { /* intentionally left blank. this op is already reduced */
}
WalletOperationV0::AddSiteV0(o) => {
WalletOperation::AddSiteV0(o) => {
if self.is_first_and_not_deleted_afterwards(op, "RemoveSiteV0") {
wallet.add_site(o.clone());
}
}
WalletOperationV0::RemoveSiteV0(_) => {}
WalletOperationV0::AddBrokerServerV0(o) => {
WalletOperation::RemoveSiteV0(_) => {}
WalletOperation::AddBrokerServerV0(o) => {
if self.is_last_and_not_deleted_afterwards(op, "RemoveBrokerServerV0") {
wallet.add_brokers(vec![BrokerInfoV0::ServerV0(o.clone())]);
}
}
WalletOperationV0::RemoveBrokerServerV0(_) => {}
WalletOperationV0::SetSaveToNGOneV0(o) => {
WalletOperation::RemoveBrokerServerV0(_) => {}
WalletOperation::SetSaveToNGOneV0(o) => {
if self.is_last_occurrence(op.0, &op.1) != 0 {
wallet.save_to_ng_one = o.clone();
}
}
WalletOperationV0::SetBrokerCoreV0(o) => {
WalletOperation::SetBrokerCoreV0(o) => {
if self.is_last_occurrence(op.0, &op.1) != 0 {
wallet.add_brokers(vec![BrokerInfoV0::CoreV0(o.clone())]);
}
}
WalletOperationV0::SetClientV0(o) => {
WalletOperation::SetClientV0(o) => {
if self.is_last_occurrence(op.0, &op.1) != 0 {
wallet.add_client(o.clone());
}
}
WalletOperationV0::AddOverlayCoreOverrideV0((overlay, cores)) => {
WalletOperation::AddOverlayCoreOverrideV0((overlay, cores)) => {
if self
.is_last_and_not_deleted_afterwards(op, "RemoveOverlayCoreOverrideV0")
{
wallet.add_overlay_core_overrides(overlay, cores);
}
}
WalletOperationV0::RemoveOverlayCoreOverrideV0(_) => {}
WalletOperationV0::AddSiteCoreV0((site, core)) => {
WalletOperation::RemoveOverlayCoreOverrideV0(_) => {}
WalletOperation::AddSiteCoreV0((site, core)) => {
if self.is_first_and_not_deleted_afterwards(op, "RemoveSiteCoreV0") {
let _ = wallet.sites.get_mut(&site).and_then(|site| {
site.cores.push(*core);
@ -294,8 +297,8 @@ impl WalletLogV0 {
});
}
}
WalletOperationV0::RemoveSiteCoreV0(_) => {}
WalletOperationV0::AddSiteBootstrapV0((site, server)) => {
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| {
site.bootstraps.push(*server);
@ -303,14 +306,14 @@ impl WalletLogV0 {
});
}
}
WalletOperationV0::RemoveSiteBootstrapV0(_) => {}
WalletOperationV0::AddThirdPartyDataV0((key, value)) => {
WalletOperation::RemoveSiteBootstrapV0(_) => {}
WalletOperation::AddThirdPartyDataV0((key, value)) => {
if self.is_last_and_not_deleted_afterwards(op, "RemoveThirdPartyDataV0") {
let _ = wallet.third_parties.insert(key.to_string(), value.to_vec());
}
}
WalletOperationV0::RemoveThirdPartyDataV0(_) => {}
WalletOperationV0::SetSiteRBDRefV0((site, store_type, rbdr)) => {
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| {
match store_type {
@ -328,7 +331,7 @@ impl WalletLogV0 {
});
}
}
WalletOperationV0::SetSiteRepoSecretV0((site, store_type, secret)) => {
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| {
match store_type {
@ -357,7 +360,7 @@ impl WalletLogV0 {
pub fn is_first_and_not_deleted_afterwards(
&self,
item: &(u128, WalletOperationV0),
item: &(u128, WalletOperation),
delete_type: &str,
) -> bool {
let hash = self.is_first_occurrence(item.0, &item.1);
@ -375,7 +378,7 @@ impl WalletLogV0 {
pub fn is_last_and_not_deleted_afterwards(
&self,
item: &(u128, WalletOperationV0),
item: &(u128, WalletOperation),
delete_type: &str,
) -> bool {
let hash = self.is_last_occurrence(item.0, &item.1);
@ -391,7 +394,7 @@ impl WalletLogV0 {
false
}
pub fn is_first_occurrence(&self, timestamp: u128, searched_op: &WalletOperationV0) -> u64 {
pub fn is_first_occurrence(&self, timestamp: u128, searched_op: &WalletOperation) -> u64 {
let searched_hash = searched_op.hash();
//let mut timestamp = u128::MAX;
//let mut found = searched_op;
@ -406,7 +409,7 @@ impl WalletLogV0 {
searched_hash.0
}
pub fn is_last_occurrence(&self, timestamp: u128, searched_op: &WalletOperationV0) -> u64 {
pub fn is_last_occurrence(&self, timestamp: u128, searched_op: &WalletOperation) -> u64 {
let searched_hash = searched_op.hash();
//let mut timestamp = 0u128;
//let mut found = searched_op;
@ -426,7 +429,7 @@ impl WalletLogV0 {
searched_type: &str,
searched_hash: u64,
after: u128,
) -> Option<(u128, &WalletOperationV0)> {
) -> Option<(u128, &WalletOperation)> {
let mut timestamp = u128::MAX;
let mut found = None;
for op in &self.log {
@ -446,7 +449,7 @@ impl WalletLogV0 {
/// WalletOperation
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum WalletOperationV0 {
pub enum WalletOperation {
CreateWalletV0(WalletOpCreateV0),
AddSiteV0(SiteV0),
RemoveSiteV0(PrivKey),
@ -468,7 +471,7 @@ pub enum WalletOperationV0 {
}
use std::collections::hash_map::DefaultHasher;
impl WalletOperationV0 {
impl WalletOperation {
pub fn hash(&self) -> (u64, &str) {
let mut s = DefaultHasher::new();
match self {
@ -579,6 +582,7 @@ impl From<&WalletOpCreateV0> for EncryptedWalletV0 {
fn from(op: &WalletOpCreateV0) -> Self {
let mut wallet = EncryptedWalletV0 {
wallet_privkey: op.wallet_privkey.clone(),
wallet_id: op.wallet_privkey.to_pub().to_string(),
pazzle: op.pazzle.clone(),
mnemonic: op.mnemonic.clone(),
pin: op.pin.clone(),

@ -17,6 +17,7 @@ env_logger = "0.10"
stores-lmdb = { path = "../stores-lmdb" }
p2p-repo = { path = "../p2p-repo", features = ["server_log_output"] }
p2p-net = { path = "../p2p-net" }
p2p-client-ws = { path = "../p2p-client-ws" }
ng-wallet = { path = "../ng-wallet" }
serde = { version = "1.0.142", features = ["derive"] }
serde_bare = "0.5.0"

@ -11,6 +11,9 @@ extern crate anyhow;
mod types;
use p2p_client_ws::remote_ws::ConnectionWebSocket;
use p2p_net::actors::add_user::*;
use p2p_net::broker::BROKER;
use p2p_repo::store::StorageError;
use warp::reply::Response;
use warp::{Filter, Reply};
@ -18,12 +21,18 @@ use warp::{Filter, Reply};
use rust_embed::RustEmbed;
use serde_bare::{from_slice, to_vec};
use serde_json::json;
use std::convert::Infallible;
use std::net::IpAddr;
use std::str::FromStr;
use std::sync::Arc;
use std::{env, fs};
use crate::types::*;
use ng_wallet::types::*;
use p2p_net::types::{CreateAccountBSP, APP_NG_ONE_URL, NG_ONE_URL};
use p2p_net::types::{
BindAddress, CreateAccountBSP, Invitation, InvitationV0, APP_ACCOUNT_REGISTERED_SUFFIX,
APP_NG_ONE_URL, NG_ONE_URL,
};
use p2p_repo::log::*;
use p2p_repo::types::*;
use p2p_repo::utils::{generate_keypair, sign, verify};
@ -32,27 +41,110 @@ use p2p_repo::utils::{generate_keypair, sign, verify};
#[folder = "web/dist"]
struct Static;
struct Server {}
struct Server {
admin_key: PrivKey,
local_peer_key: PrivKey,
ip: IpAddr,
port: u16,
peer_id: PubKey,
domain: String,
}
impl Server {
fn register_(&self, ca: String) -> Result<(), NgHttpError> {
async fn register_(&self, ca: String) -> Result<String, Option<String>> {
log_debug!("registering {}", ca);
let cabsp: CreateAccountBSP = ca.try_into().map_err(|_| NgHttpError::InvalidParams)?;
let mut cabsp: CreateAccountBSP = ca.try_into().map_err(|_| None)?;
log_debug!("{:?}", cabsp);
Ok(())
// if needed, proceed with payment and verify it here. once validated, add the user
let local_peer_pubk = self.local_peer_key.to_pub();
let res = BROKER
.write()
.await
.admin(
Box::new(ConnectionWebSocket {}),
self.local_peer_key.clone(),
local_peer_pubk,
self.peer_id,
self.admin_key.to_pub(),
self.admin_key.clone(),
BindAddress {
port: self.port,
ip: (&self.ip).into(),
},
AddUser::V0(AddUserV0 {
user: cabsp.user(),
is_admin: false,
}),
)
.await;
let mut redirect_url = cabsp
.redirect_url()
.clone()
.unwrap_or(format!("{}{}", self.domain, APP_ACCOUNT_REGISTERED_SUFFIX));
match &res {
Err(e) => {
log_err!("error while registering: {e} {:?}", cabsp);
Err(Some(format!("{}?e={}", redirect_url, e)))
}
Ok(_) => {
log_info!("User added successfully {}", cabsp.user());
let mut return_invitation = if cabsp.invitation().is_some() {
InvitationV0 {
bootstrap: cabsp.invitation().as_ref().unwrap().bootstrap.clone(),
name: cabsp.invitation().as_ref().unwrap().name.clone(),
code: None,
url: None,
}
} else {
InvitationV0::empty(Some(self.domain.clone()))
};
return_invitation.append_bootstraps(cabsp.additional_bootstrap());
Ok(format!(
"{}?i={}&u={}",
redirect_url,
Invitation::V0(return_invitation),
cabsp.user()
))
}
}
}
pub fn register(&self, ca: String) -> Response {
match self.register_(ca) {
Ok(_) => warp::http::StatusCode::CREATED.into_response(),
Err(e) => e.into_response(),
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 (mut parts, body) = response.into_parts();
parts.status = warp::http::StatusCode::OK;
let response = Response::from_parts(parts, body);
Ok(response)
}
Err(redirect_url) => {
if redirect_url.is_some() {
let response = Response::new(redirect_url.unwrap().into());
let (mut parts, body) = response.into_parts();
parts.status = warp::http::StatusCode::BAD_REQUEST;
let response = Response::from_parts(parts, body);
Ok(response)
} else {
Ok(warp::http::StatusCode::NOT_ACCEPTABLE.into_response())
}
}
}
}
}
fn with_server(
server: Arc<Server>,
) -> impl Filter<Extract = (Arc<Server>,), Error = std::convert::Infallible> + Clone {
warp::any().map(move || Arc::clone(&server))
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
if std::env::var("RUST_LOG").is_err() {
@ -60,13 +152,26 @@ async fn main() -> anyhow::Result<()> {
}
env_logger::init();
let server = Arc::new(Server {});
let domain =
env::var("NG_ACCOUNT_DOMAIN").map_err(|_| anyhow!("NG_ACCOUNT_DOMAIN must be set"))?;
let admin_user =
env::var("NG_ACCOUNT_ADMIN").map_err(|_| anyhow!("NG_ACCOUNT_ADMIN must be set"))?;
let admin_user = env::var("NG_ACCOUNT_ADMIN")
.map_err(|_| anyhow!("NG_ACCOUNT_ADMIN must be set (with private key)"))?;
let admin_key: PrivKey = admin_user.as_str().try_into().map_err(|_| {
anyhow!(
"NG_ACCOUNT_ADMIN is invalid. It should be a base64-url encoded serde serialization of a [u8; 32] of the private key for an admin user. cannot start"
)
})?;
let local_peer_privkey = env::var("NG_ACCOUNT_LOCAL_PEER_KEY")
.map_err(|_| anyhow!("NG_ACCOUNT_LOCAL_PEER_KEY must be set"))?;
let local_peer_key: PrivKey = local_peer_privkey.as_str().try_into().map_err(|_| {
anyhow!(
"NG_ACCOUNT_LOCAL_PEER_KEY is invalid. It should be a base64-url encoded serde serialization of a [u8; 32] of the private key for the peerId. cannot start"
)
})?;
// format is IP,PORT,PEERID
let server_address =
@ -78,15 +183,39 @@ async fn main() -> anyhow::Result<()> {
"NG_ACCOUNT_SERVER is invalid. format is IP,PORT,PEER_ID"
));
}
let ip: IP = addr[0].into();
let ip = IpAddr::from_str(addr[0]).map_err(|_| {
anyhow!("NG_ACCOUNT_SERVER is invalid. format is IP,PORT,PEER_ID. The first part is not an IP address. cannot start")
})?;
log::info!("{}", domain);
let port = match addr[1].parse::<u16>() {
Err(_) => {
return Err(anyhow!("NG_ACCOUNT_SERVER is invalid. format is IP,PORT,PEER_ID. The port is invalid. It should be a number. cannot start"));
}
Ok(val) => val,
};
let peer_id: PubKey = addr[2].try_into().map_err(|_| {
anyhow!(
"NG_ACCOUNT_SERVER is invalid. format is IP,PORT,PEER_ID.
The PEER_ID is invalid. It should be a base64-url encoded serde serialization of a [u8; 32]. cannot start"
)
})?;
log::info!("domain {}", domain);
let server = Arc::new(Server {
admin_key,
local_peer_key,
ip,
port,
peer_id,
domain,
});
// GET /api/v1/register/ca with the same ?ca= query param => 201 CREATED
let server_for_move = Arc::clone(&server);
let register_api = warp::get()
.and(with_server(server))
.and(warp::path!("register" / String))
.map(move |ca| server_for_move.register(ca));
.and_then(Server::register);
let api_v1 = warp::path!("api" / "v1" / ..).and(register_api);

@ -12,7 +12,8 @@
"dependencies": {
"flowbite": "^1.6.5",
"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": {
"@sveltejs/vite-plugin-svelte": "^2.0.4",

@ -2,6 +2,7 @@ lockfileVersion: 5.4
specifiers:
'@sveltejs/vite-plugin-svelte': ^2.0.4
'@tauri-apps/api': 2.0.0-alpha.4
autoprefixer: ^10.4.14
cross-env: ^7.0.3
flowbite: ^1.6.5
@ -16,6 +17,7 @@ specifiers:
vite-plugin-svelte-svg: ^2.2.1
dependencies:
'@tauri-apps/api': 2.0.0-alpha.4
flowbite: 1.6.6
flowbite-svelte: 0.37.5_svelte@3.59.1
svelte-spa-router: 3.3.0
@ -332,6 +334,11 @@ packages:
- supports-color
dev: true
/@tauri-apps/api/2.0.0-alpha.4:
resolution: {integrity: sha512-gWe5fFHbwFM+dmdDPtlDvVDVtoMneGRM+S8mECevWhKpXYxId0yxznE56YGAvPSJXC3vgsXw16mOmkTnEVKnaw==}
engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
dev: false
/@trysound/sax/0.2.0:
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
engines: {node: '>=10.13.0'}

@ -11,14 +11,18 @@
<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";
import { tauri, event } from "@tauri-apps/api";
import { onMount } from "svelte";
let domain = import.meta.env.NG_ACCOUNT_DOMAIN;
const params = new URLSearchParams($querystring);
let ca = params.get("ca");
const param = new URLSearchParams($querystring);
let ca = param.get("ca");
let go_back = true;
let top;
const api_url = import.meta.env.PROD
@ -29,10 +33,37 @@
const opts = {
method: "get",
};
try {
const response = await fetch(api_url + "register/" + ca, opts);
const result = await response.text();
console.log("Result:", response.status, result); // 400 is error, 201 ok
console.log("Result:", response.status, result); // 400 is error with redirect, 200 ok, 406 is error without known redirect
if (response.status == 406) {
close();
} else if (response.status == 400) {
close(result);
error = "We are redirecting you...";
go_back = false;
} else {
success(result);
}
} catch (e) {
error = e.message;
}
}
function close(url) {
// TODO tauri error code
if (url) {
window.location.href = url;
} else {
window.history.go(-1);
}
}
function success(url) {
window.location.href = url;
// TODO tauri success code
}
async function bootstrap() {}
@ -44,7 +75,7 @@
await register();
};
const refuse = (event) => {
window.history.go(-1);
close();
};
</script>
@ -60,11 +91,34 @@
{/if}
</div>
{#if error}
<div class=" max-w-6xl lg:px-8 mx-auto px-4">
<p class="max-w-xl md:mx-auto lg:max-w-2xl">
An error occurred while registering on this broker :<br />
{error}
<div class=" max-w-6xl lg:px-8 mx-auto px-4 text-red-800">
<svg
class="animate-bounce mt-10 h-16 w-16 mx-auto"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-5">
An error occurred while registering on this broker:<br />{error}
</p>
{#if go_back}
<button
on:click|once={close}
class="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"
>
Go back
</button>
{/if}
</div>
{:else}
{#if ca}
@ -218,10 +272,9 @@
</svg>
<span
>Registration is free of charge. And it would be very nice of you
if you wanted to donate a small amount for the fees we have to pay
for the servers. Here is the donation link: <a
target="_blank"
href="https://nextgraph.org/donate"
if you wanted to donate a small amount to help us cover the fees
we have to pay for operating the servers. Here is the donation
link: <a target="_blank" href="https://nextgraph.org/donate"
>https://nextgraph.org/donate</a
>
</span>

@ -17,8 +17,8 @@
import { onMount } from "svelte";
const params = new URLSearchParams($querystring);
let ca = params.get("ca");
const param = new URLSearchParams($querystring);
let ca = param.get("ca");
let domain = import.meta.env.NG_ACCOUNT_DOMAIN;

@ -17,7 +17,7 @@
const api_url = import.meta.env.PROD
? "api/v1/"
: "http://localhost:3030/api/v1/";
: "http://localhost:3031/api/v1/";
async function bootstrap() {}

@ -139,6 +139,10 @@ async fn main() -> Result<(), ProtocolError> {
.about("add a user to the server, so it can connect to it")
.arg(arg!([USER_ID] "userId of the user to add. should be a base64-url encoded serde serialization of its pubkey [u8; 32]").required(true))
.arg(arg!(-a --admin "make this user admin as well").required(false)))
.subcommand(
Command::new("del-user")
.about("removes a user from the broker")
.arg(arg!([USER_ID] "userId of the user to remove. should be a base64-url encoded serde serialization of its pubkey [u8; 32]").required(true)))
.subcommand(
Command::new("list-users")
.about("list all users registered in the broker")
@ -146,7 +150,7 @@ async fn main() -> Result<(), ProtocolError> {
.subcommand(
Command::new("add-invitation")
.about("add an invitation to register on the server")
.arg(arg!([EXPIRES] "offset (from now) of time after which the invitation should expire. Format example: 1w 1d 1m. default unit is second").conflicts_with("forever"))
.arg(arg!([EXPIRES] "offset (from now) of time after which the invitation should expire. Format example: 1w 1d 1m. default unit is second. see https://crates.io/crates/duration-str for format").conflicts_with("forever"))
.arg(arg!(-a --admin "user registered with this invitation will have admin permissions").required(false))
.arg(arg!(-i --multi "many users can use this invitation to register themselves, until the invitation code is deleted by an admin").required(false).conflicts_with("admin").conflicts_with("unique"))
.arg(arg!(-u --unique "this invitation can be used only once. this is the default").required(false).conflicts_with("admin"))
@ -440,6 +444,30 @@ async fn main() -> Result<(), ProtocolError> {
}
return res.map(|_| ());
}
Some(("del-user", sub2_matches)) => {
log_debug!("add-user");
let res = do_admin_call(
keys[1],
config_v0,
DelUser::V0(DelUserV0 {
user: sub2_matches
.get_one::<String>("USER_ID")
.unwrap()
.as_str()
.try_into()
.map_err(|_| {
log_err!("supplied USER_ID is invalid");
ProtocolError::InvalidValue
})?,
}),
)
.await;
match &res {
Err(e) => log_err!("An error occurred: {e}"),
Ok(_) => println!("User removed successfully"),
}
return res.map(|_| ());
}
Some(("list-users", sub2_matches)) => {
log_debug!("list-users");
let admins = sub2_matches.get_flag("admin");

@ -25,7 +25,7 @@
async function bootstrap() {
let bs;
try {
bs = localStorage.getItem("bootstrap");
bs = localStorage.getItem("ng_wallets");
} catch (e) {}
if (bs) {
} else {

@ -12,7 +12,9 @@
<script>
import { Button, Alert } from "flowbite-svelte";
import { link } from "svelte-spa-router";
// @ts-ignore
import EULogo from "../assets/EU.svg?component";
// @ts-ignore
import Logo from "../assets/nextgraph.svg?component";
import { onMount } from "svelte";
@ -28,7 +30,7 @@
scrollToTop();
let bs;
try {
bs = localStorage.getItem("bootstrap");
bs = localStorage.getItem("ng_wallets");
} catch (e) {}
if (bs) {
display_note_on_local_wallets = true;
@ -133,13 +135,7 @@
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<defs />
<g
id="Page-1"
stroke-width="1"
fill="none"
fill-rule="evenodd"
sketch:type="MSPage"
>
<g id="Page-1" stroke-width="1" fill="none" fill-rule="evenodd">
<g
id="Laptop"
transform="translate(1.000000, 1.000000)"

@ -14,6 +14,7 @@ use std::hash::Hash;
use std::hash::Hasher;
use std::time::SystemTime;
use p2p_net::errors::ProtocolError;
use p2p_net::types::*;
use p2p_repo::kcv_store::KCVStore;
use p2p_repo::store::*;
@ -144,6 +145,17 @@ impl<'a> Invitation<'a> {
self.id
}
pub fn get_type(&self) -> Result<u8, ProtocolError> {
let type_ser = self
.store
.get(Self::PREFIX, &to_vec(&self.id)?, Some(Self::TYPE))?;
let t: (u8, u32, Option<String>) = from_slice(&type_ser)?;
if t.1 < now_timestamp() {
return Err(ProtocolError::Expired);
}
Ok(t.0)
}
pub fn is_expired(&self) -> Result<bool, StorageError> {
let expire_ser = self
.store
@ -176,30 +188,5 @@ mod test {
use crate::broker_store::account::Account;
#[test]
pub fn test_account() {
let path_str = "test-env";
let root = Builder::new().prefix(path_str).tempdir().unwrap();
let key: [u8; 32] = [0; 32];
fs::create_dir_all(root.path()).unwrap();
println!("{}", root.path().to_str().unwrap());
let mut store = LmdbKCVStore::open(root.path(), key);
let user_id = PubKey::Ed25519PubKey([1; 32]);
let account = Account::create(&user_id, true, &store).unwrap();
println!("account created {}", account.id());
let account2 = Account::open(&user_id, &store).unwrap();
println!("account opened {}", account2.id());
// let client_id = PubKey::Ed25519PubKey([56; 32]);
// let client_id_not_added = PubKey::Ed25519PubKey([57; 32]);
// account2.add_client(&client_id).unwrap();
// assert!(account2.is_admin().unwrap());
// account.has_client(&client_id).unwrap();
// assert!(account.has_client(&client_id_not_added).is_err());
}
pub fn test_invitation() {}
}

@ -111,6 +111,12 @@ impl BrokerStorage for LmdbBrokerStorage {
Account::create(&user_id, is_admin, &self.accounts_storage)?;
Ok(())
}
fn del_user(&self, user_id: PubKey) -> Result<(), ProtocolError> {
log_debug!("del_user {user_id}");
let acc = Account::open(&user_id, &self.accounts_storage)?;
acc.del()?;
Ok(())
}
fn list_users(&self, admins: bool) -> Result<Vec<PubKey>, ProtocolError> {
log_debug!("list_users that are admin == {admins}");
Ok(Account::get_all_users(admins, &self.accounts_storage)?)
@ -139,4 +145,15 @@ impl BrokerStorage for LmdbBrokerStorage {
Invitation::create(invite_code, expiry, memo, &self.accounts_storage)?;
Ok(())
}
fn get_invitation_type(&self, invite_code: [u8; 32]) -> Result<u8, ProtocolError> {
log_debug!("get_invitation_type {:?}", invite_code);
let inv = Invitation::open(&invite_code, &self.accounts_storage)?;
inv.get_type()
}
fn remove_invitation(&self, invite_code: [u8; 32]) -> Result<(), ProtocolError> {
log_debug!("remove_invitation {:?}", invite_code);
let inv = Invitation::open(&invite_code, &self.accounts_storage)?;
inv.del()?;
Ok(())
}
}

@ -341,6 +341,7 @@ mod test {
client,
client_priv,
info: ClientInfo::new(ClientType::Cli, "".into(), "".into()),
registration: None,
}),
)
.await;

@ -64,6 +64,12 @@ impl From<DelUser> for ProtocolMessage {
}
}
impl From<DelUser> for AdminRequestContentV0 {
fn from(msg: DelUser) -> AdminRequestContentV0 {
AdminRequestContentV0::DelUser(msg)
}
}
impl Actor<'_, DelUser, AdminResponse> {}
#[async_trait::async_trait]
@ -73,14 +79,11 @@ impl EActor for Actor<'_, DelUser, AdminResponse> {
msg: ProtocolMessage,
fsm: Arc<Mutex<NoiseFSM>>,
) -> Result<(), ProtocolError> {
//let req = DelUser::try_from(msg)?;
let res = AdminResponseV0 {
id: 0,
result: 0,
content: AdminResponseContentV0::EmptyResponse,
padding: vec![],
};
fsm.lock().await.send(res.into()).await?;
let req = DelUser::try_from(msg)?;
let broker = BROKER.read().await;
let res = broker.get_storage()?.del_user(req.user());
let response: AdminResponseV0 = res.into();
fsm.lock().await.send(response.into()).await?;
Ok(())
}
}

@ -182,7 +182,70 @@ impl<'a> Broker<'a> {
}
}
Authorization::ExtMessage => Err(ProtocolError::AccessDenied),
Authorization::Client(user) => 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();
if storage.get_user(user_and_registration.0).is_ok() {
return Ok(());
}
if let Some(ServerConfig {
registration: reg, ..
}) = &self.config
{
return match reg {
RegistrationConfig::Closed => return Err(ProtocolError::AccessDenied),
RegistrationConfig::Invitation => {
// registration is only possible with an invitation code
if user_and_registration.1.unwrap().is_none() {
Err(ProtocolError::InvitationRequired)
} else {
let mut is_admin = false;
let code = user_and_registration.1.unwrap().unwrap();
let inv_type = storage.get_invitation_type(code)?;
if inv_type == 2u8 {
// admin
is_admin = true;
storage.remove_invitation(code)?;
} else if inv_type == 1u8 {
storage.remove_invitation(code)?;
}
self.storage
.as_ref()
.unwrap()
.add_user(user_and_registration.0, is_admin)?;
Ok(())
}
}
RegistrationConfig::Open => {
// registration is open (no need for invitation. anybody can register)
let mut is_admin = false;
if user_and_registration.1.unwrap().is_some() {
// but if there is an invitation code and it says the user should be admin, then we take that into account
let code = user_and_registration.1.unwrap().unwrap();
let inv_type = storage.get_invitation_type(code)?;
if inv_type == 2u8 {
// admin
is_admin = true;
storage.remove_invitation(code)?;
} else if inv_type == 1u8 {
storage.remove_invitation(code)?;
}
}
self.storage
.as_ref()
.unwrap()
.add_user(user_and_registration.0, is_admin)?;
Ok(())
}
};
} else {
return Err(ProtocolError::BrokerError);
}
}
// if user doesn't want to register, we accept everything, as perms will be checked late ron, once the overlayId is known
Ok(())
}
Authorization::Core => Err(ProtocolError::AccessDenied),
Authorization::Admin(admin_user) => {
if listener.config.accepts_client() {
@ -543,16 +606,25 @@ impl<'a> Broker<'a> {
.ok_or(ProtocolError::AccessDenied)?;
// authorize
if client.is_none() {
let is_core = if client.is_none() {
// it is a Core connection
if !listener.config.is_core() {
return Err(ProtocolError::AccessDenied);
}
true
} else {
if !listener.config.accepts_client() {
return Err(ProtocolError::AccessDenied);
}
}
let client = client.unwrap();
self.authorize(
&(local_bind_address, remote_bind_address),
Authorization::Client((client.user, client.registration)),
)?;
// TODO add client to storage
false
};
let mut connection = self
.anonymous_connections
@ -561,9 +633,7 @@ impl<'a> Broker<'a> {
connection.reset_shutdown(remote_peer_id).await;
let ip = remote_bind_address.ip;
let connected = if let Some(client_auth) = client {
// TODO add client to storage
let connected = if !is_core {
PeerConnection::Client(connection)
} else {
let dc = DirectConnection {

@ -15,6 +15,7 @@ use p2p_repo::{kcv_store::KCVStore, types::PubKey};
pub trait BrokerStorage: 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>;
fn list_users(&self, admins: bool) -> Result<Vec<PubKey>, ProtocolError>;
fn list_invitations(
&self,
@ -28,4 +29,6 @@ pub trait BrokerStorage: Send + Sync + std::fmt::Debug {
expiry: u32,
memo: &Option<String>,
) -> Result<(), ProtocolError>;
fn get_invitation_type(&self, invite: [u8; 32]) -> Result<u8, ProtocolError>;
fn remove_invitation(&self, invite: [u8; 32]) -> Result<(), ProtocolError>;
}

@ -164,6 +164,7 @@ pub struct ClientConfig {
pub client: PubKey,
pub client_priv: PrivKey,
pub info: ClientInfo,
pub registration: Option<Option<[u8; 32]>>,
}
#[derive(Debug, Clone)]
@ -702,6 +703,7 @@ impl NoiseFSM {
/// Nonce from ServerHello
nonce: hello.nonce().clone(),
info: info.clone(),
registration: client_config.registration,
};
let ser = serde_bare::to_vec(&content)?;
let sig =

@ -58,6 +58,7 @@ pub enum ProtocolError {
InvalidSignature,
SerializationError,
AccessDenied,
InvitationRequired,
OverlayNotJoined,
OverlayNotFound,
BrokerError,
@ -70,6 +71,7 @@ pub enum ProtocolError {
ConnectionError,
Timeout,
Expired,
PeerAlreadyConnected,
OtherError,

@ -230,6 +230,8 @@ pub struct BrokerServerV0 {
pub peer_id: PubKey,
}
pub const APP_ACCOUNT_REGISTERED_SUFFIX: &str = "/#/user/registered";
pub const NG_ONE_URL: &str = "https://nextgraph.one";
pub const APP_NG_ONE_URL: &str = "https://app.nextgraph.one";
@ -246,7 +248,7 @@ fn local_ws_url(port: &u16) -> String {
format!("ws://localhost:{}", if *port == 0 { 80 } else { *port })
}
fn local_http_url(port: &u16) -> String {
pub fn local_http_url(port: &u16) -> String {
format!("http://localhost:{}", if *port == 0 { 80 } else { *port })
}
@ -294,6 +296,18 @@ impl BrokerServerV0 {
})
}
pub fn first_ipv4_http(&self) -> Option<String> {
self.server_type.find_first_ipv4().map_or(None, |bindaddr| {
Some(format!("http://{}:{}", bindaddr.ip, bindaddr.port))
})
}
pub fn first_ipv6_http(&self) -> Option<String> {
self.server_type.find_first_ipv6().map_or(None, |bindaddr| {
Some(format!("http://{}:{}", bindaddr.ip, bindaddr.port))
})
}
fn first_ipv6_or_ipv4(
ipv4: bool,
ipv6: bool,
@ -520,6 +534,14 @@ pub enum BootstrapContent {
V0(BootstrapContentV0),
}
impl BootstrapContent {
pub fn servers(&self) -> &Vec<BrokerServerV0> {
match self {
Self::V0(v0) => &v0.servers,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum InvitationCode {
Unique(SymKey),
@ -566,6 +588,14 @@ impl InvitationV0 {
BootstrapContent::V0(v0) => self.bootstrap = v0,
}
}
pub fn empty(name: Option<String>) -> Self {
InvitationV0 {
bootstrap: BootstrapContentV0 { servers: vec![] },
code: None,
name,
url: None,
}
}
pub fn new(
bootstrap_content: BootstrapContent,
code: Option<SymKey>,
@ -581,6 +611,12 @@ impl InvitationV0 {
},
}
}
pub fn append_bootstraps(&mut self, add: &mut Option<BootstrapContentV0>) {
if add.is_some() {
let add = add.as_mut().unwrap();
self.bootstrap.servers.append(&mut add.servers);
}
}
}
impl Invitation {
@ -676,6 +712,14 @@ impl Invitation {
}
}
impl fmt::Display for Invitation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ser = serde_bare::to_vec(&self).unwrap();
let string = base64_url::encode(&ser);
write!(f, "{}", string)
}
}
impl TryFrom<String> for Invitation {
type Error = NgError;
fn try_from(value: String) -> Result<Self, NgError> {
@ -729,6 +773,26 @@ impl CreateAccountBSP {
}
Some(base64_url::encode(&payload_ser.unwrap()))
}
pub fn user(&self) -> PubKey {
match self {
Self::V0(v0) => v0.user,
}
}
pub fn redirect_url(&self) -> &Option<String> {
match self {
Self::V0(v0) => &v0.redirect_url,
}
}
pub fn invitation(&self) -> &Option<InvitationV0> {
match self {
Self::V0(v0) => &v0.invitation,
}
}
pub fn additional_bootstrap(&mut self) -> &mut Option<BootstrapContentV0> {
match self {
Self::V0(v0) => &mut v0.additional_bootstrap,
}
}
}
/// Create an account at a Broker Service Provider (BSP). Version 0
@ -2668,7 +2732,7 @@ pub enum Authorization {
Discover,
ExtMessage,
Core,
Client(PubKey),
Client((PubKey, Option<Option<[u8; 32]>>)),
OverlayJoin(PubKey),
Admin(PubKey),
}
@ -2815,6 +2879,8 @@ pub struct ClientAuthContentV0 {
pub info: ClientInfoV0,
pub registration: Option<Option<[u8; 32]>>,
/// Nonce from ServerHello
#[serde(with = "serde_bytes")]
pub nonce: Vec<u8>,
@ -2826,7 +2892,7 @@ pub struct ClientAuthV0 {
/// Authentication data
pub content: ClientAuthContentV0,
/// Signature by client key
/// Signature by user key
pub sig: Sig,
}
@ -2862,6 +2928,11 @@ impl ClientAuth {
ClientAuth::V0(o) => &o.content.nonce,
}
}
pub fn registration(&self) -> Option<Option<[u8; 32]>> {
match self {
ClientAuth::V0(o) => o.content.registration,
}
}
}
impl From<ClientAuth> for ProtocolMessage {

@ -9,8 +9,7 @@
* according to those terms.
*/
use crate::types::BootstrapContent;
use crate::types::Invitation;
use crate::types::*;
use crate::NG_BOOTSTRAP_LOCAL_PATH;
use async_std::task;
use ed25519_dalek::*;
@ -22,6 +21,8 @@ use p2p_repo::errors::NgError;
use p2p_repo::types::PubKey;
use p2p_repo::{log::*, types::PrivKey};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use url::Host;
use url::Url;
#[cfg(target_arch = "wasm32")]
pub fn spawn_and_log_error<F>(fut: F) -> task::JoinHandle<()>
@ -58,6 +59,79 @@ const APP_PREFIX: &str = "http://localhost:14400";
#[cfg(not(debug_assertions))]
const APP_PREFIX: &str = "";
pub fn decode_invitation_string(string: String) -> Option<Invitation> {
Invitation::try_from(string).ok()
}
pub fn check_is_local_url(bootstrap: &BrokerServerV0, location: &String) -> Option<String> {
if location.starts_with(APP_NG_ONE_URL) {
match &bootstrap.server_type {
BrokerServerTypeV0::BoxPublic(_) | BrokerServerTypeV0::BoxPublicDyn(_) => {
return Some(APP_NG_ONE_WS_URL.to_string());
}
_ => {}
}
} else if let BrokerServerTypeV0::Domain(domain) = &bootstrap.server_type {
let url = format!("https://{}", domain);
if location.starts_with(&url) {
return Some(url);
}
} else {
// localhost
if location.starts_with(LOCAL_URLS[0])
|| location.starts_with(LOCAL_URLS[1])
|| location.starts_with(LOCAL_URLS[2])
{
if let BrokerServerTypeV0::Localhost(port) = bootstrap.server_type {
return Some(local_http_url(&port));
}
}
// a private address
else if location.starts_with("http://") {
let url = Url::parse(location).unwrap();
match url.host() {
Some(Host::Ipv4(ip)) => {
if is_ipv4_private(&ip) {
let res = bootstrap.first_ipv4_http();
if res.is_some() {
return res;
}
}
}
Some(Host::Ipv6(ip)) => {
if is_ipv6_private(&ip) {
let res = bootstrap.first_ipv6_http();
if res.is_some() {
return res;
}
}
}
_ => {}
}
}
}
None
}
pub async fn retrieve_local_url(location: String) -> Option<String> {
let bootstraps: BootstrapContent = {
let resp = reqwest::get(format!("{}{}", APP_PREFIX, NG_BOOTSTRAP_LOCAL_PATH)).await;
if resp.is_ok() {
let resp = resp.unwrap().json::<BootstrapContent>().await;
resp.unwrap()
} else {
return None;
}
};
for bootstrap in bootstraps.servers() {
let res = check_is_local_url(bootstrap, &location);
if res.is_some() {
return res;
}
}
None
}
pub async fn retrieve_local_bootstrap(
location_string: String,
invite_string: Option<String>,
@ -93,11 +167,7 @@ pub async fn retrieve_local_bootstrap(
if res.is_some() {
for server in res.as_ref().unwrap().get_servers() {
if must_be_public && server.is_public_server()
|| !must_be_public
&& server
.get_ws_url(Some(location_string.clone()))
.await
.is_some()
|| !must_be_public && check_is_local_url(server, &location_string).is_some()
{
return res;
}

@ -177,6 +177,7 @@ pub fn timestamp_after(duration: Duration) -> Timestamp {
}
/// displays the NextGraph Timestamp in UTC.
#[cfg(not(target_arch = "wasm32"))]
pub fn display_timestamp(ts: &Timestamp) -> String {
let st = SystemTime::UNIX_EPOCH
+ Duration::from_secs(EPOCH_AS_UNIX_TIMESTAMP)

@ -171,11 +171,16 @@ impl<'a> WriteTransaction for LmdbTransaction<'a> {
/// Delete a property from the store.
fn del(&mut self, prefix: u8, key: &Vec<u8>, suffix: Option<u8>) -> Result<(), StorageError> {
let property = LmdbKCVStore::compute_property(prefix, key, suffix);
self.store
let res = self
.store
.main_store
.delete_all(self.writer.as_mut().unwrap(), property)
.map_err(|e| StorageError::BackendError)?;
.delete_all(self.writer.as_mut().unwrap(), property);
if res.is_err() {
if let StoreError::KeyValuePairNotFound = res.unwrap_err() {
return Ok(());
}
return Err(StorageError::BackendError);
}
Ok(())
}
@ -367,7 +372,6 @@ impl KCVStore for LmdbKCVStore {
writer: Some(writer),
};
let res = method(&mut transaction);
if res.is_ok() {
transaction.commit();
}

Loading…
Cancel
Save