Niko PLP 1 year ago
parent 7045a717bb
commit 59f63aef17
  1. 47
      Cargo.lock
  2. 1
      Cargo.toml
  3. 6
      ng-app/README.md
  4. 1
      ng-app/package.json
  5. 1
      ng-app/src-tauri/Cargo.toml
  6. 10
      ng-app/src-tauri/src/lib.rs
  7. 47
      ng-app/src/api.ts
  8. 10
      ng-app/src/routes/Grid.svelte
  9. 10
      ng-app/src/routes/Install.svelte
  10. 445
      ng-app/src/routes/WalletCreate.svelte
  11. 4
      ng-app/src/styles.css
  12. 23
      ng-sdk-js/README.md
  13. 2
      ng-sdk-js/app-node/index.js
  14. 2233
      ng-sdk-js/js/bowser.js
  15. 15
      ng-sdk-js/js/browser.js
  16. 122
      ng-sdk-js/js/node.js
  17. 91
      ng-sdk-js/src/lib.rs
  18. 54
      ng-wallet/src/lib.rs
  19. 64
      ng-wallet/src/types.rs
  20. 28
      ngaccount/Cargo.toml
  21. 37
      ngaccount/README.md
  22. 82
      ngaccount/src/main.rs
  23. 30
      ngaccount/src/types.rs
  24. 24
      ngaccount/web/.gitignore
  25. 3
      ngaccount/web/.vscode/extensions.json
  26. 61
      ngaccount/web/index.html
  27. 32
      ngaccount/web/jsconfig.json
  28. 27
      ngaccount/web/package.json
  29. 1310
      ngaccount/web/pnpm-lock.yaml
  30. 13
      ngaccount/web/postcss.config.cjs
  31. 2
      ngaccount/web/public/robots.txt
  32. 1
      ngaccount/web/public/vite.svg
  33. 26
      ngaccount/web/src/App.svelte
  34. 4
      ngaccount/web/src/app.postcss
  35. 4
      ngaccount/web/src/assets/EU.svg
  36. 16
      ngaccount/web/src/assets/nextgraph.svg
  37. 9
      ngaccount/web/src/main.js
  38. 27
      ngaccount/web/src/routes/Home.svelte
  39. 20
      ngaccount/web/src/routes/NotFound.svelte
  40. 2
      ngaccount/web/src/vite-env.d.ts
  41. 7
      ngaccount/web/svelte.config.js
  42. 23
      ngaccount/web/tailwind.config.cjs
  43. 36
      ngaccount/web/vite.config.js
  44. 8
      ngd/src/cli.rs
  45. 9
      ngd/src/main.rs
  46. 13
      ngone/README.md
  47. 5
      ngone/src/main.rs
  48. 6
      ngone/web/src/routes/WalletCreate.svelte
  49. 170
      p2p-broker/src/broker_store/account.rs
  50. 157
      p2p-broker/src/broker_store/invitation.rs
  51. 2
      p2p-broker/src/broker_store/mod.rs
  52. 4
      p2p-broker/src/broker_store/overlay.rs
  53. 2
      p2p-broker/src/broker_store/topic.rs
  54. 2
      p2p-broker/src/lib.rs
  55. 83
      p2p-broker/src/server_ws.rs
  56. 37
      p2p-broker/src/storage.rs
  57. 10
      p2p-broker/src/types.rs
  58. 3
      p2p-net/Cargo.toml
  59. 7
      p2p-net/src/broker.rs
  60. 17
      p2p-net/src/broker_storage.rs
  61. 4
      p2p-net/src/lib.rs
  62. 287
      p2p-net/src/types.rs
  63. 56
      p2p-net/src/utils.rs
  64. 2
      p2p-repo/src/errors.rs
  65. 2
      p2p-repo/src/kcv_store.rs
  66. 8
      pnpm-lock.yaml
  67. 5
      stores-lmdb/src/kcv_store.rs

47
Cargo.lock generated

@ -2736,6 +2736,7 @@ dependencies = [
"serde_json",
"tauri",
"tauri-build",
"tauri-plugin-window",
]
[[package]]
@ -2787,6 +2788,30 @@ dependencies = [
"zeroize",
]
[[package]]
name = "ngaccount"
version = "0.1.0"
dependencies = [
"base64-url",
"bytes",
"env_logger",
"log",
"ng-wallet",
"p2p-net",
"p2p-repo",
"rust-embed",
"serde",
"serde-big-array",
"serde_bare",
"serde_bytes",
"serde_json",
"slice_as_array",
"stores-lmdb",
"tokio",
"warp",
"warp-embed",
]
[[package]]
name = "ngcli"
version = "0.1.0"
@ -3024,6 +3049,15 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-src"
version = "111.26.0+1.1.1u"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efc62c9f12b22b8f5208c23a7200a442b2e5999f8bdf80233852122b5a4f6f37"
dependencies = [
"cc",
]
[[package]]
name = "openssl-sys"
version = "0.9.90"
@ -3032,6 +3066,7 @@ checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6"
dependencies = [
"cc",
"libc",
"openssl-src",
"pkg-config",
"vcpkg",
]
@ -3129,6 +3164,7 @@ dependencies = [
"unique_id",
"url",
"wasm-bindgen",
"web-time",
]
[[package]]
@ -4629,6 +4665,17 @@ dependencies = [
"tauri-utils",
]
[[package]]
name = "tauri-plugin-window"
version = "2.0.0-alpha.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "209fef1a00a981949e2440924b4be267c7639daeba51b29179004fa1c6d74900"
dependencies = [
"serde",
"tauri",
"thiserror",
]
[[package]]
name = "tauri-runtime"
version = "0.13.0-alpha.6"

@ -9,6 +9,7 @@ members = [
"ngcli",
"ngd",
"ngone",
"ngaccount",
"ng-sdk-js",
"ng-app/src-tauri",
"ng-wallet"

@ -101,6 +101,12 @@ rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-andro
- follow the steps for Android in the [Prerquisites guide of Tauri](https://next--tauri.netlify.app/next/guides/getting-started/prerequisites/)
Until I find out how to do this properly, if you are compiling the android app from a macos station, you need to override an env var. this is due to reqwest needing SSL support, and on linux and android it compiles it from source. apparently the compiler (cc-rs) doesn't know that when cross compiling to android targets, the tool ranlib is called llvm-ranlib (and not [target]-ranlib)
```
export RANLIB=/Users/[user]/Library/Android/sdk/ndk/[yourNDKversion]/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-ranlib
```
to launch the dev app :
```

@ -17,6 +17,7 @@
"dependencies": {
"@popperjs/core": "^2.11.8",
"@tauri-apps/api": "2.0.0-alpha.4",
"@tauri-apps/plugin-window": "2.0.0-alpha.0",
"async-proxy": "^0.4.1",
"classnames": "^2.3.2",
"flowbite": "^1.6.5",

@ -24,6 +24,7 @@ p2p-repo = { path = "../../p2p-repo" }
p2p-net = { path = "../../p2p-net" }
ng-wallet = { path = "../../ng-wallet" }
async-std = { version = "1.12.0", features = ["attributes","unstable"] }
tauri-plugin-window = "2.0.0-alpha"
[features]
# this feature is used for production builds or when `devPath` points to the filesystem

@ -13,7 +13,7 @@ use p2p_net::broker::*;
use p2p_net::utils::{spawn_and_log_error, Receiver, ResultSend};
use p2p_repo::log::*;
use p2p_repo::types::*;
use tauri::{App, Manager};
use tauri::{App, Manager, Window};
#[cfg(mobile)]
mod mobile;
@ -78,6 +78,7 @@ async fn wallet_create_wallet(mut params: CreateWalletV0) -> Result<CreateWallet
#[tauri::command(rename_all = "snake_case")]
async fn doc_sync_branch(nuri: &str, stream_id: &str, app: tauri::AppHandle) -> Result<(), ()> {
log_info!("doc_sync_branch {} {}", nuri, stream_id);
let main_window = app.get_window("main").unwrap();
let mut reader;
{
@ -91,10 +92,10 @@ async fn doc_sync_branch(nuri: &str, stream_id: &str, app: tauri::AppHandle) ->
async fn inner_task(
mut reader: Receiver<Commit>,
stream_id: String,
app: tauri::AppHandle,
main_window: tauri::Window,
) -> ResultSend<()> {
while let Some(commit) = reader.next().await {
app.emit_all(&stream_id, commit).unwrap();
main_window.emit(&stream_id, commit).unwrap();
}
BROKER.write().await.tauri_stream_cancel(stream_id);
@ -103,7 +104,7 @@ async fn doc_sync_branch(nuri: &str, stream_id: &str, app: tauri::AppHandle) ->
Ok(())
}
spawn_and_log_error(inner_task(reader, stream_id.to_string(), app));
spawn_and_log_error(inner_task(reader, stream_id.to_string(), main_window));
Ok(())
}
@ -173,6 +174,7 @@ impl AppBuilder {
Ok(())
})
.plugin(tauri_plugin_window::init())
.invoke_handler(tauri::generate_handler![
test,
doc_sync_branch,

@ -8,6 +8,8 @@
// according to those terms.
import {createAsyncProxy} from "async-proxy";
import { writable } from "svelte/store";
import { Bowser } from "../../ng-sdk-js/js/bowser.js";
import {version} from '../package.json';
const mapping = {
@ -26,18 +28,51 @@ const handler = {
if (import.meta.env.NG_APP_WEB) {
let sdk = await import("ng-sdk-js")
if (path[0] === "client_info") {
let client_info = await Reflect.apply(sdk[path], caller, args);
client_info.version=version;
//console.log(client_info);
return client_info;
} else {
return Reflect.apply(sdk[path], caller, args)
}
} else {
let tauri = await import("@tauri-apps/api/tauri");
if (path[0] === "client_info") {
if (path[0] === "doc_sync_branch") {
let tauri_platform = import.meta.env.TAURI_PLATFORM;
let client_type;
switch (tauri_platform) {
case 'macos': client_type = "NativeMacOS";break;
case 'linux': client_type = "NativeLinux";break;
case 'windows': client_type = "NativeWindows";break;
case 'android': client_type = "NativeAndroid";break;
case 'ios': client_type = "NativeIos";break;
}
let info = Bowser.parse(window.navigator.userAgent);
info.platform.arch = import.meta.env.TAURI_ARCH;
info.platform.tauri = {
family: import.meta.env.TAURI_FAMILY,
os_version: import.meta.env.TAURI_PLATFORM_VERSION,
type: import.meta.env.TAURI_PLATFORM_TYPE,
debug: import.meta.env.TAURI_DEBUG,
target: import.meta.env.TAURI_TARGET_TRIPLE
};
info.browser.ua = window.navigator.userAgent;
let res = {
// TODO: install timestamp
ClientInfoV0 : { client_type, details: JSON.stringify(info), version, timestamp_install:0, timestamp_updated:0 }
};
//console.log(res);
return res;
} else if (path[0] === "doc_sync_branch") {
let stream_id = (lastStreamId += 1).toString();
console.log("stream_id",stream_id);
let { listen } = await import("@tauri-apps/api/event");
let { appWindow } = await import("@tauri-apps/plugin-window");
let nuri = args[0];
let callback = args[1];
let unlisten = await listen(stream_id, (event) => {
let unlisten = await appWindow.listen(stream_id, (event) => {
callback(event.payload).then(()=> {})
})
await tauri.invoke("doc_sync_branch",{nuri, stream_id});
@ -58,6 +93,8 @@ const handler = {
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] === "get_local_bootstrap") {
return false;
}
else {
let arg = {};
@ -70,4 +107,8 @@ const handler = {
const api = createAsyncProxy({}, handler);
export const NG_EU_BSP = "https://nextgraph.eu";
export const NG_NET_BSP = "https://nextgraph.net";
export default api;

@ -55,16 +55,10 @@
security_txt: " know yourself ",
pin: [5, 2, 9, 1],
pazzle_length: 9,
send_bootstrap: undefined,
send_bootstrap: false,
send_wallet: false,
result_with_wallet_file: true,
peer_id: {
Ed25519PubKey: [
119, 251, 253, 29, 135, 199, 254, 50, 134, 67, 1, 208, 117, 196, 167,
107, 2, 113, 98, 243, 49, 90, 7, 0, 157, 58, 14, 187, 14, 3, 116, 86,
],
},
nonce: 0,
local_save: false,
};
try {

@ -78,7 +78,7 @@
</a>
</div>
<div class="row mt-5">
<a href="#">
<a href="https://nextgraph.org/download/#android">
<button
tabindex="-1"
class="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 mr-2 mb-2"
@ -119,7 +119,7 @@
</div>
<div class="row mt-5">
<a href="#">
<a href="https://nextgraph.org/download/#macos">
<button
tabindex="-1"
class="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 mr-2 mb-2"
@ -140,7 +140,7 @@
</div>
<div class="row mt-5">
<a href="#">
<a href="https://nextgraph.org/download/#linux">
<button
tabindex="-1"
class="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 mr-2 mb-2"
@ -161,7 +161,7 @@
</div>
<div class="row mt-5">
<a href="#">
<a href="https://nextgraph.org/download/#windows">
<button
tabindex="-1"
class="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 mr-2 mb-2"
@ -182,7 +182,7 @@
</div>
<div class="row mt-5 mb-12">
<a href="https://docs.nextgraph.org/en/self-hosted">
<a href="https://nextgraph.org/self-host">
<button
tabindex="-1"
class="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 mr-2 mb-2"

@ -11,17 +11,19 @@
<script>
import { Button, Alert, Dropzone, Toggle } from "flowbite-svelte";
import { link } from "svelte-spa-router";
import { link, querystring } from "svelte-spa-router";
import EULogo from "../assets/EU.svg?component";
import Logo from "../assets/nextgraph.svg?component";
import ng from "../api";
import { NG_EU_BSP, NG_NET_BSP, default as ng } from "../api";
import { display_pazzle } from "../wallet_emojis";
import { onMount, tick } from "svelte";
let mobile =
import.meta.env.TAURI_PLATFORM == "android" ||
import.meta.env.TAURI_PLATFORM == "ios";
const params = new URLSearchParams($querystring);
let tauri_platform = import.meta.env.TAURI_PLATFORM;
let mobile = tauri_platform == "android" || tauri_platform == "ios";
const onFileSelected = (image) => {
animate_bounce = false;
@ -92,6 +94,7 @@
let download_name;
let cloud_link;
let animateDownload = true;
let invitation;
function scrollToTop() {
top.scrollIntoView();
@ -115,9 +118,18 @@
? "api/v1/"
: "http://localhost:3030/api/v1/";
let display_note_on_local_wallets = true;
let display_note_on_local_wallets = false;
async function bootstrap() {
console.log(await ng.client_info());
invitation = await ng.get_local_bootstrap(location.href, params.get("i"));
console.log(invitation);
// TODO: implement this error screen and link button
if (!invitation && params.get("i")) {
console.error(
"got an invitation for another broker. click on the link below to be redirected to the right broker"
);
}
scrollToTop();
let bs;
try {
@ -152,15 +164,8 @@
security_txt,
pin,
pazzle_length: 9,
send_bootstrap: undefined, //options.cloud || options.bootstrap ? : undefined,
send_bootstrap: false, //options.cloud || options.bootstrap ? : undefined,
send_wallet: options.cloud,
peer_id: {
Ed25519PubKey: [
119, 251, 253, 29, 135, 199, 254, 50, 134, 67, 1, 208, 117, 196, 167,
107, 2, 113, 98, 243, 49, 90, 7, 0, 157, 58, 14, 187, 14, 3, 116, 86,
],
},
nonce: 0,
local_save: options.trusted, // this is only used for tauri apps
result_with_wallet_file: false, // this will be automatically changed to true for browser app
};
@ -203,7 +208,16 @@
console.log("Result:", result);
}
onMount(() => bootstrap());
onMount(async () => await bootstrap());
const selectEU = (event) => {
if (!tauri_platform) {
window.open(NG_EU_BSP + "/#/wallet/create", "_blank").focus();
}
};
const selectNET = (event) => {};
const enterINVITE = (event) => {};
const enterQRcode = (event) => {};
</script>
<main class="container3" bind:this={top}>
@ -215,14 +229,14 @@
{#if intro}
<div class=" max-w-6xl lg:px-8 mx-auto px-4">
<p class="max-w-xl md:mx-auto lg:max-w-2xl">
A <b>NextGraph Wallet</b> is unique to each individual. It stores your
A <b>NextGraph Wallet</b> is unique to each person. It stores your
credentials and authorizations to access documents. <br /><br />If you
already have a wallet, you should not create a new one, instead,
<a href="/wallet/login" use:link
>login here with your existing wallet.</a
>
If you never created a NextGraph Wallet before, please follow the instructions
below in order to create your personal wallet.
below in order to create your unique personal wallet.
</p>
</div>
{#if display_note_on_local_wallets}
@ -277,8 +291,8 @@
/>
</svg>
<span
>In it, we store all the permissions to access documents you have
been granted with, or that you have created yourself.</span
>In your wallet, we store all the permissions to access documents
you have been granted with, or that you have created yourself.</span
>
</li>
<li class="flex space-x-3">
@ -422,10 +436,10 @@
</svg>
<span
>For the same reason, we won't be able to help you if you forget
your pazzle or PIN code. There is no "password recovery" option in
this case. You can note your pazzle down on a piece of paper until
you remember it, but don't forget to destroy this note after a
while.</span
your pazzle or PIN code, or if you loose the wallet file. There is
no "password recovery" option in this case. You can note your
pazzle down on a piece of paper until you remember it, but don't
forget to destroy this note after a while.</span
>
</li>
</ul>
@ -435,7 +449,7 @@
<button
on:click|once={create_wallet}
role="button"
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 mr-2 mb-2"
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"
>
<svg
class="w-8 h-8 mr-2 -ml-1"
@ -455,14 +469,374 @@
Ok, I create my wallet now !
</button>
</div>
{:else if !invitation}
<div class=" max-w-6xl lg:px-8 mx-auto px-4">
<p class="max-w-xl md:mx-auto lg:max-w-2xl">
NextGraph is based on an efficient decentralized P2P network, and in
order to join this network and start using the app, you need to first
select a <b>broker&nbsp;server</b>.
</p>
</div>
<div class="px-4 pt-3 mx-auto max-w-6xl lg:px-8 lg:pt-10 dark:bg-slate-800">
<div class="max-w-xl md:mx-auto sm:text-center lg:max-w-2xl">
<h2 class="pb-5 text-xl">
What is a broker? <span class="text-sm">Please read</span>
</h2>
<ul class="mb-8 space-y-4 text-left text-gray-500 dark:text-gray-400">
<li class="flex space-x-3">
<svg
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
/>
</svg>
<span>
The broker helps you keep all your data in <b>sync</b>, as it is
connected to the internet 24/7 and keeps a copy of the updates for
you. This way, even if the devices of the other participants are
offline, you can still see their changes</span
>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
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="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z"
/>
</svg>
<span>
All your data is secure and <b>end-to-end encrypted</b>, and the
broker cannot see the content of the documents as it does not have
the keys to decrypt them.</span
>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
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.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"
/>
</svg>
<span>
The broker helps you enforce your <b>privacy</b> as it hides your internet
address (IP) from other users you share documents with.</span
>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
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.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>
It will be possible in the future to use NextGraph without any
broker and to have direct connections between peers, but this will
imply a less smooth experience.</span
>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
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="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5"
/>
</svg>
<span>
At anytime you can decide to switch to another broker service
provider or host it yourself. Your data is totally <b>portable</b>
and can freely move to another broker.</span
>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
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="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"
/>
</svg>
<span>
Very soon we will offer you the opportunity to host your own
broker at <b>home</b> or <b>office</b>. Instead of using a "broker
service provider", you will own a small device that you connect
behind your internet router. It is called NG Box and will be
available soon.</span
>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-green-500 dark:text-green-400"
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="M5.25 14.25h13.5m-13.5 0a3 3 0 01-3-3m3 3a3 3 0 100 6h13.5a3 3 0 100-6m-16.5-3a3 3 0 013-3h13.5a3 3 0 013 3m-19.5 0a4.5 4.5 0 01.9-2.7L5.737 5.1a3.375 3.375 0 012.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 01.9 2.7m0 0a3 3 0 01-3 3m0 3h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008zm-3 6h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008z"
/>
</svg>
<span>
Large organizations and companies have the opportunity to host a
broker <b>on-premise</b> or in the cloud, as the software is open
source. Individuals can also <b>self-host</b> a broker on any VPS server
or at home.</span
>
</li>
</ul>
<h2 class="mt-3 text-xl">Please choose one broker among the list</h2>
</div>
</div>
<div class="row mt-5">
{#if !tauri_platform}
<a href="https://nextgraph.eu/#/wallet/create">
<button
tabindex="-1"
class="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"
>
<EULogo class="mr-4 block h-10 w-10" alt="European Union flag" />
For European Union citizens
</button>
</a>
{:else}
<button
on:click|once={selectEU}
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"
>
<EULogo class="mr-4 block h-10 w-10" alt="European Union flag" />
For European Union citizens
</button>
{/if}
</div>
<div class="row mt-5">
{#if !tauri_platform}
<a href="https://nextgraph.net/#/wallet/create">
<button
tabindex="-1"
class="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
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
class="mr-4 block h-10 w-10"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 013 12c0-1.605.42-3.113 1.157-4.418"
/>
</svg>
For the rest of the world
</button>
</a>
{:else}
<button
on:click|once={selectNET}
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
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
class="mr-4 block h-10 w-10"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 013 12c0-1.605.42-3.113 1.157-4.418"
/>
</svg>
For the rest of the world
</button>
{/if}
</div>
<div class="row mt-5">
<button
on:click|once={enterINVITE}
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
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
class="mr-4 block h-10 w-10"
>
<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 an invitation link
</button>
</div>
{#if mobile}
<div class="row mt-5">
<button
on:click|once={enterQRcode}
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
class="mr-4 block h-10 w-10"
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>
Scan an invitation QRcode
</button>
</div>
{/if}
<div class="row mt-5">
<a href="https://nextgraph.org/self-host">
<button
tabindex="-1"
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
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
class="mr-4 block h-10 w-10"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M5.25 14.25h13.5m-13.5 0a3 3 0 01-3-3m3 3a3 3 0 100 6h13.5a3 3 0 100-6m-16.5-3a3 3 0 013-3h13.5a3 3 0 013 3m-19.5 0a4.5 4.5 0 01.9-2.7L5.737 5.1a3.375 3.375 0 012.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 01.9 2.7m0 0a3 3 0 01-3 3m0 3h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008zm-3 6h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008z"
/>
</svg>
Self-hosted broker
</button>
</a>
</div>
<div class="row mt-5 mb-12">
<a href="https://nextgraph.org/ng-box/">
<button
tabindex="-1"
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
xmlns="http://www.w3.org/2000/svg"
version="1.1"
viewBox="0 0 225 225"
class="mr-4 block h-10 w-10"
stroke="currentColor"
stroke-width="12"
fill="none"
>
<path
d="M 88.332599,179.77884 C 72.858008,177.42608 59.581081,170.564 48.8817,159.38898 36.800075,146.77026 30.396139,130.74266 30.396139,113.12381 c 0,-8.81477 1.466462,-16.772273 4.503812,-24.439156 3.697755,-9.333883 8.658122,-16.726264 15.988284,-23.827148 4.07992,-3.952299 5.699054,-5.267377 9.730928,-7.903581 10.263753,-6.710853 20.852276,-10.247623 32.861256,-10.976317 17.083161,-1.036581 33.737521,4.410501 47.100151,15.404873 1.30009,1.069669 2.35446,2.035155 2.34305,2.145524 -0.0114,0.110369 -3.32807,3.135042 -7.37038,6.721489 -4.04229,3.586437 -8.6667,7.731233 -10.27646,9.210635 -1.60975,1.479412 -3.05439,2.689839 -3.21032,2.689839 -0.15591,0 -1.2075,-0.642795 -2.33686,-1.428431 -6.49544,-4.518567 -13.79659,-6.747116 -22.104843,-6.747116 -10.982241,0 -20.054641,3.741852 -27.727158,11.435891 -5.517107,5.532575 -9.233107,12.555305 -10.782595,20.377588 -0.596045,3.00901 -0.594915,11.67153 0.0017,14.67182 3.195984,16.0665 15.801761,28.55358 31.607491,31.30987 3.592183,0.62643 10.334745,0.61437 13.792675,-0.0247 12.10383,-2.2368 22.30712,-9.80603 27.83192,-20.64689 0.66747,-1.30971 1.08703,-2.48825 0.93235,-2.61898 -0.1547,-0.13073 -5.9299,-1.01605 -12.83381,-1.96739 -8.43575,-1.16241 -12.87296,-1.9096 -13.52955,-2.27826 -1.31171,-0.73647 -2.44642,-2.49122 -2.44642,-3.78325 0,-1.012 1.74837,-13.68832 2.1486,-15.57814 0.25598,-1.20873 2.0923,-3.01339 3.3151,-3.25795 0.53677,-0.10735 7.61424,0.73799 15.7688,1.88346 8.13723,1.14303 14.89071,1.97925 15.00772,1.85826 0.11702,-0.12098 0.96445,-5.648553 1.88315,-12.283473 0.95557,-6.900944 1.90122,-12.59548 2.20977,-13.306594 0.29667,-0.683692 0.95765,-1.595052 1.46889,-2.025218 1.77972,-1.497534 2.7114,-1.539742 10.52745,-0.476938 8.31229,1.130266 9.2373,1.347581 10.59333,2.488613 1.41776,1.192951 1.96085,2.424677 1.94866,4.419342 -0.006,0.950347 -0.79507,7.156475 -1.75393,13.791395 -0.95885,6.634933 -1.70069,12.111623 -1.64854,12.170443 0.0522,0.0588 6.18174,0.95872 13.62132,1.99978 9.57969,1.34053 13.80866,2.0595 14.49353,2.46406 1.3199,0.77969 2.13943,2.28402 2.1135,3.87957 -0.0399,2.45278 -2.08103,15.63263 -2.5664,16.57122 -0.57073,1.10369 -2.24485,2.197 -3.38232,2.20889 -0.44831,0.004 -6.79249,-0.82755 -14.09817,-1.84941 -7.3057,-1.02186 -13.34942,-1.79161 -13.43049,-1.71053 -0.0811,0.0811 -1.02469,6.33285 -2.09694,13.89286 -1.24218,8.75802 -2.1547,14.1778 -2.51495,14.93697 -0.62565,1.31846 -2.38302,2.64205 -3.91461,2.94836 -0.8254,0.16509 -9.4024,-0.80047 -11.73007,-1.32049 -0.47193,-0.10544 -1.63157,0.58011 -3.8898,2.29957 -9.71515,7.39729 -20.99725,11.99799 -33.08692,13.49241 -3.79574,0.46921 -13.565667,0.37348 -17.125664,-0.16779 z"
/>
<rect
ry="37.596001"
y="10.583322"
x="14.363095"
height="204.86308"
width="195.79167"
/>
</svg>
NG Box (owned or invited)
</button>
</a>
</div>
{:else if pin.length < 4}
<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">Let's start by choosing a PIN code</span>
<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 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)
</Alert>
</p>
<p class="text-left mt-5">Here are the rules for the PIN :</p>
@ -610,7 +984,7 @@
<button
on:click|once={save_security}
bind:this={validate_button}
class="animate-bounce mt-10 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 mr-2 mb-2"
class="animate-bounce mt-10 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
class="w-8 h-8 mr-2 -ml-1"
@ -695,8 +1069,8 @@
with your security and privacy.<br /><br />
Remember that in any case, once your wallet will be created, you will download
a file that you should keep privately somewhere on your device, USB key or
harddisk. This is the default way you can use and keep your wallet. Now let's
look at some options that can make your life a bit easier.
hard-disk. This is the default way you can use and keep your wallet. Now
let's look at some options that can make your life a bit easier.
</p>
<p class="max-w-xl md:mx-auto lg:max-w-2xl text-left">
<span class="text-xl">Do you trust this device? </span> <br />
@ -704,8 +1078,8 @@
family or workplace, and you would like to login again from this device in
the future, then you can save your wallet on this device. To the contrary,
if this device is public and shared by strangers, do not save your wallet
here. {#if !import.meta.env.TAURI_PLATFORM}By selecting this option, you
agree to save some cookies on your browser.{/if}<br />
here. {#if !tauri_platform}By selecting this option, you agree to save
some cookies on your browser.{/if}<br />
<Toggle class="mt-3" bind:checked={options.trusted}
>Save your wallet on this device?</Toggle
>
@ -755,7 +1129,7 @@
{/if}
<button
on:click|once={do_wallet}
class="mt-10 mb-8 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 mr-2"
class="mt-10 mb-8 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"
>
<svg
class="w-8 h-8 mr-2 -ml-1"
@ -827,7 +1201,7 @@
tabindex="-1"
class:animate-bounce={animateDownload}
on:click={() => (animateDownload = false)}
class="mt-10 mb-8 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 mr-2"
class="mt-10 mb-8 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"
>
<svg
class="w-8 h-8 mr-2 -ml-1"
@ -896,3 +1270,6 @@
</div>
{/if}
</main>
<style>
</style>

@ -29,6 +29,10 @@ div[role="alert"] div {
display: block;
}
.choice-button {
min-width: 340px;
}
.row {
display: flex;
justify-content: center;

@ -37,17 +37,10 @@ npm i ng-sdk-js
## For contributors
```
wasm-pack build --target bundler
cd pkg
// if you have access to npm registry and want to publish the package
// npm publish --access=public
wasm-pack build --dev --target bundler
cd ..
wasm-pack build -t nodejs -d pkg-node
wasm-pack build --dev -t nodejs -d pkg-node
node prepare-node.js
cd pkg-node
// if you have access to npm registry and want to publish the package
// npm publish --access=public
```
For testing in vanilla JS
@ -65,6 +58,18 @@ Or automated testing with headless chrome:
wasm-pack test --chrome --headless
```
## Production built
```
wasm-pack build --target bundler
wasm-pack build -t nodejs -d pkg-node
node prepare-node.js
cd pkg
npm publish --access=public
cd ../pkg-node
npm publish --access=public
```
### Plain JS web app
```

@ -14,6 +14,6 @@ global.WebSocket = WebSocket;
const test = require("./test")
console.log("FROM INDEX");
ng.test();
test.random();
//test.random();
console.log(ng.start());

File diff suppressed because it is too large Load Diff

@ -1,3 +1,14 @@
export function random(max) {
return Math.round(Math.random() * max)
import {version} from '../../../package.json';
export function client_details() {
return window.navigator.userAgent;
}
export function client_details2(obj) {
obj.browser.appVersion = navigator?.appVersion;
obj.browser.arch = navigator?.platform;
obj.browser.vendor = navigator?.vendor;
obj.browser.ua = window.navigator.userAgent;
obj.engine.sdk = version;
return JSON.stringify(obj);
}

@ -1,4 +1,122 @@
module.exports.random = function (max) {
return 0
const macNameMap = new Map([
[23, ['Sonoma', '14']],
[22, ['Ventura', '13']],
[21, ['Monterey', '12']],
[20, ['Big Sur', '11']],
[19, ['Catalina', '10.15']],
[18, ['Mojave', '10.14']],
[17, ['High Sierra', '10.13']],
[16, ['Sierra', '10.12']],
[15, ['El Capitan', '10.11']],
[14, ['Yosemite', '10.10']],
[13, ['Mavericks', '10.9']],
[12, ['Mountain Lion', '10.8']],
[11, ['Lion', '10.7']],
[10, ['Snow Leopard', '10.6']],
[9, ['Leopard', '10.5']],
[8, ['Tiger', '10.4']],
[7, ['Panther', '10.3']],
[6, ['Jaguar', '10.2']],
[5, ['Puma', '10.1']],
]);
function macosRelease(release) {
let split = (release).split('.');
rel = Number(split[0]);
let [name, version] = macNameMap.get(rel) || ['Unknown', release];
if (name!='Unknown') {
if (split.length>1) version += '.'+split[1];
//if (split.length>2 && split[2]) version += '.'+split[2];
}
return {
name: "macOS",
versionName: name,
version,
release
};
}
const winNames = new Map([
['10.0.2', '11'], // It's unclear whether future Windows 11 versions will use this version scheme: https://github.com/sindresorhus/windows-release/pull/26/files#r744945281
['10.0.22', 'Server 2022'],
['10.0', '10 or Server 2016/2019'],
['6.3', '8.1 or Server 2012 R2'],
['6.2', '8 or Server 2012'],
['6.1', '7 or Server 2008 R2'],
['6.0', 'Vista or Server 2008'],
['5.2', 'Server 2003'],
['5.1', 'XP'],
['5.0', '2000'],
['4.90', 'ME'],
['4.10', '98'],
['4.03', '95'],
['4.00', '95'],
['3.00', 'NT'],
]);
function windowsRelease(release) {
const version = /(\d+\.\d+)(?:\.(\d+))?/.exec(release);
let ver = version[1] || '';
const build = version[2] || '';
if (ver.startsWith('3.')) {
ver = '3.00';
}
if (ver === '10.0' && build.startsWith('20348')) {
// Windows Server 2022
ver = '10.0.22';
} else if (ver === '10.0' && build.startsWith('2')) {
// Windows 11
ver = '10.0.2';
}
return {
name: "Windows",
versionName: winNames.get(ver),
version: build,
release
};
}
function osName(platform, release) {
if (platform === 'darwin') {
return release? macosRelease(release) : {name: "macOS"};
}
if (platform === 'linux') {
id = release ? release.replace(/^(\d+\.\d+).*/, '$1') : '';
return {name:'Linux', version: id || release, release};
}
if (platform === 'win32') {
return release ? windowsRelease(release) : {name: "Windows"};
}
return {name:platform, version:release};
}
module.exports.version = function () {
return require('../../../package.json').version;
}
module.exports.client_details = function () {
const process = require('process');
const osnode = require('os');
let os = osName(osnode.platform(),osnode.release());
if (osnode.version) os.uname = osnode.version();
os.type = osnode.type();
return JSON.stringify({
platform: { type: "server", arch: osnode.machine? osnode.machine() : process.arch },
os,
engine: {
name: "nodejs",
version: process.version,
arch : process.arch,
versions: process.versions
}
});
};

@ -21,12 +21,13 @@ use ng_wallet::*;
use p2p_client_ws::remote_ws_wasm::ConnectionWebSocket;
use p2p_net::broker::*;
use p2p_net::connection::{ClientConfig, StartConfig};
use p2p_net::types::{DirectPeerId, IP};
use p2p_net::utils::{spawn_and_log_error, Receiver, ResultSend, Sender};
use p2p_net::types::{BootstrapContentV0, ClientInfoV0, ClientType, DirectPeerId, IP};
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::net::IpAddr;
use std::str::FromStr;
@ -35,6 +36,17 @@ use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_futures::{future_to_promise, JsFuture};
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn get_local_bootstrap(location: String, invite: JsValue) -> JsValue {
let res = retrieve_local_bootstrap(location, invite.as_string()).await;
if res.is_some() {
serde_wasm_bindgen::to_value(&res.unwrap()).unwrap()
} else {
JsValue::FALSE
}
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn wallet_gen_shuffle_for_pazzle_opening(pazzle_length: u8) -> JsValue {
@ -88,14 +100,8 @@ pub fn test_create_wallet() -> JsValue {
" know yourself ".to_string(),
pin,
9,
None,
false,
PubKey::Ed25519PubKey([
119, 251, 253, 29, 135, 199, 254, 50, 134, 67, 1, 208, 117, 196, 167, 107, 2, 113, 98,
243, 49, 90, 7, 0, 157, 58, 14, 187, 14, 3, 116, 86,
]),
0,
ClientV0::dummy(),
false,
);
serde_wasm_bindgen::to_value(&r).unwrap()
}
@ -103,19 +109,77 @@ pub fn test_create_wallet() -> JsValue {
#[cfg(wasmpack_target = "nodejs")]
#[wasm_bindgen(module = "/js/node.js")]
extern "C" {
fn random(max: usize) -> usize;
fn client_details() -> String;
}
#[cfg(wasmpack_target = "nodejs")]
#[wasm_bindgen(module = "/js/node.js")]
extern "C" {
fn version() -> String;
}
#[cfg(wasmpack_target = "nodejs")]
#[wasm_bindgen]
pub fn client_info() -> JsValue {
let res = ClientInfoV0 {
client_type: ClientType::NodeService,
details: client_details(),
version: version(),
timestamp_install: 0,
timestamp_updated: 0,
};
serde_wasm_bindgen::to_value(&res).unwrap()
}
#[cfg(not(wasmpack_target = "nodejs"))]
#[wasm_bindgen(module = "/js/browser.js")]
extern "C" {
fn random(max: usize) -> usize;
fn client_details() -> String;
}
#[cfg(not(wasmpack_target = "nodejs"))]
#[wasm_bindgen(module = "/js/bowser.js")]
extern "C" {
type Bowser;
#[wasm_bindgen(static_method_of = Bowser)]
fn parse(UA: String) -> JsValue;
}
#[cfg(not(wasmpack_target = "nodejs"))]
#[wasm_bindgen(module = "/js/browser.js")]
extern "C" {
fn client_details2(val: JsValue) -> String;
}
#[cfg(all(not(wasmpack_target = "nodejs"), target_arch = "wasm32"))]
#[wasm_bindgen]
pub fn client_info() -> JsValue {
let ua = client_details();
let bowser = Bowser::parse(ua);
//log_info!("{:?}", bowser);
let details_string = client_details2(bowser);
let res = ClientInfoV0 {
client_type: ClientType::Web,
details: details_string,
version: "".to_string(),
timestamp_install: 0,
timestamp_updated: 0,
};
serde_wasm_bindgen::to_value(&res).unwrap()
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn test() {
log_info!("test is {}", BROKER.read().await.test());
//let client_info = client_info();
//log_info!("{:?}", client_info);
//let b = Bowser::parse(ua);
//log_info!("{:?}", b);
}
#[cfg(target_arch = "wasm32")]
@ -220,11 +284,6 @@ pub async fn probe() {
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn start() {
log_info!("random {}", random(10));
// let mut random_buf = [0u8; 32];
// getrandom::getrandom(&mut random_buf).unwrap();
async fn inner_task() -> ResultSend<()> {
let server_key: PubKey = "X0nh-gOTGKSx0yL0LYJviOWRNacyqIzjQW_LKdK6opU".try_into()?;
log_debug!("server_key:{}", server_key);

@ -356,11 +356,6 @@ pub async fn create_wallet_v0(
return Err(NgWalletError::InvalidPazzleLength);
}
// cannot submit wallet if we don't submit also the bootstrap
if params.send_bootstrap.is_none() && params.send_wallet {
return Err(NgWalletError::SubmissionError);
}
// check validity of PIN
// shouldn't start with 0
@ -462,16 +457,30 @@ pub async fn create_wallet_v0(
//.ok_or(NgWalletError::InternalError)?
//.clone(),
let user = site.site_key.to_pub();
// Creating a new client
let client = ClientV0::new(user);
let create_op = WalletOpCreateV0 {
wallet_privkey: wallet_privkey.clone(),
pazzle: pazzle.clone(),
mnemonic,
pin: params.pin,
personal_site: site,
brokers: vec![], //TODO add the broker here
client: params.client.clone(),
save_to_ng_one: if params.send_wallet {
SaveToNGOne::Wallet
} else if params.send_bootstrap {
SaveToNGOne::Bootstrap
} else {
SaveToNGOne::No
},
client: client.clone(),
};
//Creating a new peerId for this Client and User
let peer = generate_keypair();
let wallet_log = WalletLog::new_v0(create_op);
let mut master_key = [0u8; 32];
@ -509,14 +518,7 @@ pub async fn create_wallet_v0(
let timestamp = now_timestamp();
let encrypted = enc_wallet_log(
&wallet_log,
&master_key,
params.peer_id,
params.nonce,
timestamp,
wallet_id,
)?;
let encrypted = enc_wallet_log(&wallet_log, &master_key, peer.1, 0, timestamp, wallet_id)?;
master_key.zeroize();
let wallet_content = WalletContentV0 {
@ -529,8 +531,8 @@ pub async fn create_wallet_v0(
enc_master_key_mnemonic,
master_nonce: 0,
timestamp,
peer_id: params.peer_id,
nonce: params.nonce,
peer_id: peer.1,
nonce: 0,
encrypted,
};
@ -557,9 +559,6 @@ pub async fn create_wallet_v0(
// sig,
// });
// TODO send bootstrap (if)
// TODO send wallet (if)
log_info!(
"creating of wallet took: {} ms",
creating_pazzle.elapsed().as_millis()
@ -575,13 +574,18 @@ pub async fn create_wallet_v0(
pazzle,
mnemonic: mnemonic.clone(),
wallet_name: base64_url::encode(&wallet_id.slice()),
peer_id: peer.1,
peer_key: peer.0,
nonce: 0,
client,
user,
})
}
#[cfg(test)]
mod test {
use super::*;
use p2p_repo::utils::generate_keypair;
use p2p_net::types::BootstrapContentV0;
use std::fs::File;
use std::io::BufReader;
use std::io::Read;
@ -626,14 +630,8 @@ mod test {
" know yourself ".to_string(),
pin,
9,
None,
false,
PubKey::Ed25519PubKey([
119, 251, 253, 29, 135, 199, 254, 50, 134, 67, 1, 208, 117, 196, 167, 107, 2, 113,
98, 243, 49, 90, 7, 0, 157, 58, 14, 187, 14, 3, 116, 86,
]),
0,
ClientV0::dummy(),
false,
))
.await
.expect("create_wallet_v0");

@ -81,6 +81,22 @@ impl ClientV0 {
auto_open: vec![],
}
}
pub fn new(user: PubKey) -> Self {
ClientV0 {
priv_key: PrivKey::random_ed(),
storage_master_key: SymKey::random(),
auto_open: vec![Identity::IndividualSite(user)],
}
}
}
/// Save to nextgraph.one
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum SaveToNGOne {
No,
Bootstrap,
Wallet,
}
/// EncryptedWallet block Version 0
@ -95,6 +111,9 @@ pub struct EncryptedWalletV0 {
pub pin: [u8; 4],
#[zeroize(skip)]
pub save_to_ng_one: SaveToNGOne,
#[zeroize(skip)]
pub personal_site: PubKey,
@ -244,6 +263,11 @@ impl WalletLogV0 {
}
}
WalletOperationV0::RemoveBrokerServerV0(_) => {}
WalletOperationV0::SetSaveToNGOneV0(o) => {
if self.is_last_occurrence(op.0, &op.1) != 0 {
wallet.save_to_ng_one = o.clone();
}
}
WalletOperationV0::SetBrokerCoreV0(o) => {
if self.is_last_occurrence(op.0, &op.1) != 0 {
wallet.add_brokers(vec![BrokerInfoV0::CoreV0(o.clone())]);
@ -428,6 +452,7 @@ pub enum WalletOperationV0 {
RemoveSiteV0(PrivKey),
AddBrokerServerV0(BrokerServerV0),
RemoveBrokerServerV0(BrokerServerV0),
SetSaveToNGOneV0(SaveToNGOne),
SetBrokerCoreV0(BrokerCoreV0),
SetClientV0(ClientV0),
AddOverlayCoreOverrideV0((OverlayId, Vec<PubKey>)),
@ -464,6 +489,7 @@ impl WalletOperationV0 {
t.hash(&mut s);
(s.finish(), "RemoveBrokerServerV0")
}
Self::SetSaveToNGOneV0(t) => (0, "SetSaveToNGOneV0"),
Self::SetBrokerCoreV0(t) => {
t.peer_id.hash(&mut s);
(s.finish(), "SetBrokerCoreV0")
@ -537,12 +563,14 @@ pub struct WalletOpCreateV0 {
pub pin: [u8; 4],
#[zeroize(skip)]
pub personal_site: SiteV0,
pub save_to_ng_one: SaveToNGOne,
// list of brokers and their connection details
#[zeroize(skip)]
pub brokers: Vec<BrokerInfoV0>,
pub personal_site: SiteV0,
// list of brokers and their connection details
//#[zeroize(skip)]
//pub brokers: Vec<BrokerInfoV0>,
#[zeroize(skip)]
pub client: ClientV0,
}
@ -554,6 +582,7 @@ impl From<&WalletOpCreateV0> for EncryptedWalletV0 {
pazzle: op.pazzle.clone(),
mnemonic: op.mnemonic.clone(),
pin: op.pin.clone(),
save_to_ng_one: op.save_to_ng_one.clone(),
personal_site: op.personal_site.site_key.to_pub(),
sites: HashMap::new(),
brokers: HashMap::new(),
@ -562,7 +591,7 @@ impl From<&WalletOpCreateV0> for EncryptedWalletV0 {
third_parties: HashMap::new(),
};
wallet.add_site(op.personal_site.clone());
wallet.add_brokers(op.brokers.clone());
//wallet.add_brokers(op.brokers.clone());
wallet.add_client(op.client.clone());
wallet
}
@ -620,6 +649,8 @@ impl BrokerInfoV0 {
/// ReducedEncryptedWallet block Version 0
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ReducedEncryptedWalletV0 {
pub save_to_ng_one: SaveToNGOne,
// main Site (Personal)
pub personal_site: ReducedSiteV0,
@ -721,19 +752,13 @@ pub struct CreateWalletV0 {
pub pin: [u8; 4],
pub pazzle_length: u8,
#[zeroize(skip)]
pub send_bootstrap: Option<Bootstrap>,
pub send_bootstrap: bool,
#[zeroize(skip)]
pub send_wallet: bool,
#[zeroize(skip)]
pub result_with_wallet_file: bool,
#[zeroize(skip)]
pub local_save: bool,
#[zeroize(skip)]
pub peer_id: PubKey,
#[zeroize(skip)]
pub nonce: u64,
#[zeroize(skip)]
pub client: ClientV0,
}
impl CreateWalletV0 {
@ -742,11 +767,8 @@ impl CreateWalletV0 {
security_txt: String,
pin: [u8; 4],
pazzle_length: u8,
send_bootstrap: Option<Bootstrap>,
send_bootstrap: bool,
send_wallet: bool,
peer_id: PubKey,
nonce: u64,
client: ClientV0,
) -> Self {
CreateWalletV0 {
result_with_wallet_file: false,
@ -757,9 +779,6 @@ impl CreateWalletV0 {
pazzle_length,
send_bootstrap,
send_wallet,
peer_id,
nonce,
client,
}
}
}
@ -775,6 +794,15 @@ pub struct CreateWalletResultV0 {
pub mnemonic: [u16; 12],
#[zeroize(skip)]
pub wallet_name: String,
#[zeroize(skip)]
pub peer_id: PubKey,
pub peer_key: PrivKey,
#[zeroize(skip)]
pub nonce: u64,
#[zeroize(skip)]
pub client: ClientV0,
#[zeroize(skip)]
pub user: PubKey,
}
#[derive(Debug, Eq, PartialEq, Clone)]

@ -0,0 +1,28 @@
[package]
name = "ngaccount"
version = "0.1.0"
edition = "2021"
license = "MIT/Apache-2.0"
authors = ["Niko PLP <niko@nextgraph.org>"]
description = "account manager for NextGraph broker service provider"
repository = "https://git.nextgraph.org/NextGraph/nextgraph-rs"
[dependencies]
tokio = { version = "1.27", features = ["full"] }
warp = "0.3"
warp-embed = "0.4"
rust-embed = "6"
log = "0.4"
env_logger = "0.10"
stores-lmdb = { path = "../stores-lmdb" }
p2p-repo = { path = "../p2p-repo", features = ["server_log_output"] }
p2p-net = { path = "../p2p-net" }
ng-wallet = { path = "../ng-wallet" }
serde = { version = "1.0.142", features = ["derive"] }
serde_bare = "0.5.0"
serde_bytes = "0.11.7"
serde-big-array = "0.5.1"
base64-url = "2.0.0"
slice_as_array = "1.1.0"
serde_json = "1.0.96"
bytes = "1.0"

@ -0,0 +1,37 @@
# broker service provider account manager (ngaccount)
This server is used internally by NextGraph to handle the creation of accounts at our broker service provider servers. You probably don't need this server in your infrastructure, even if you decide to self-host a broker under your own domain name.
## Install
```
cd web
npm install -g pnpm
pnpm --ignore-workspace install
```
## Dev
```
cd web
pnpm run dev
// in another terminal
cd ../
cargo watch -c -w src -x run
// then open http://localhost:5173/
```
## Build
```
cd web
pnpm run build
cd ..
cargo build --release
```
## run
```
../target/release/ngaccount
```

@ -0,0 +1,82 @@
// 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.
#[macro_use]
extern crate slice_as_array;
mod types;
use p2p_repo::store::StorageError;
use warp::reply::Response;
use warp::{Filter, Reply};
use rust_embed::RustEmbed;
use serde_bare::{from_slice, to_vec};
use serde_json::json;
use std::sync::Arc;
use std::{env, fs};
use crate::types::*;
use ng_wallet::types::*;
use p2p_net::types::{APP_NG_ONE_URL, NG_ONE_URL};
use p2p_repo::log::*;
use p2p_repo::types::*;
use p2p_repo::utils::{generate_keypair, sign, verify};
#[derive(RustEmbed)]
#[folder = "web/dist"]
struct Static;
struct Server {}
impl Server {}
#[tokio::main]
async fn main() {
if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "info"); //trace
}
env_logger::init();
// let (wallet_key, wallet_id) = generate_keypair();
// let content = BootstrapContentV0 { servers: vec![] };
// let ser = serde_bare::to_vec(&content).unwrap();
// let sig = sign(wallet_key, wallet_id, &ser).unwrap();
// let bootstrap = Bootstrap::V0(BootstrapV0 {
// id: wallet_id,
// content,
// sig,
// });
let server = Arc::new(Server {});
let static_files = warp::get().and(warp_embed::embed(&Static)).boxed();
let mut cors = warp::cors()
.allow_methods(vec!["GET", "POST"])
.allow_headers(vec!["Content-Type"]);
#[cfg(not(debug_assertions))]
{
cors = cors
.allow_origin(NG_ONE_URL)
.allow_origin(APP_NG_ONE_URL)
.allow_origin("https://nextgraph.eu")
.allow_origin("https://nextgraph.net");
}
#[cfg(debug_assertions)]
{
log_debug!("CORS: any origin");
cors = cors.allow_any_origin();
}
log::info!("Starting server on http://localhost:3030");
warp::serve(static_files.with(cors))
.run(([127, 0, 0, 1], 3031))
.await;
}

@ -0,0 +1,30 @@
// 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.
use warp::{reply::Response, Reply};
pub enum NgHttpError {
InvalidParams,
NotFound,
AlreadyExists,
InternalError,
}
impl Reply for NgHttpError {
fn into_response(self) -> Response {
match (self) {
NgHttpError::NotFound => warp::http::StatusCode::NOT_FOUND.into_response(),
NgHttpError::InvalidParams => warp::http::StatusCode::BAD_REQUEST.into_response(),
NgHttpError::AlreadyExists => warp::http::StatusCode::CONFLICT.into_response(),
NgHttpError::InternalError => {
warp::http::StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
}
}
}

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

@ -0,0 +1,3 @@
{
"recommendations": ["svelte.svelte-vscode"]
}

@ -0,0 +1,61 @@
<!--
// 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.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,
PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMjUg
MjI1Ij48Y2lyY2xlIGN4PSIxMDkuODgxIiBjeT0iMTEyLjkwNSIgcj0iMTA2Ljk4IiBzdHlsZT0i
ZmlsbDogcmdiKDI1NSwgMjU1LCAyNTUpOyBzdHJva2U6IG5vbmU7IHN0cm9rZS13aWR0aDogMC4y
NjgzNzU7Ij48L2NpcmNsZT48cGF0aCBkPSJNOTguMzQzIDE5MC4yNjFjLTE3Ljk0LTIuNzI3LTMz
LjMzMS0xMC42ODMtNDUuNzM1LTIzLjYzOC0xNC4wMDYtMTQuNjI5LTIxLjQzLTMzLjIxLTIxLjQz
LTUzLjYzNSAwLTEwLjIxOCAxLjctMTkuNDQ0IDUuMjIxLTI4LjMzMiA0LjI4Ny0xMC44MiAxMC4w
MzgtMTkuMzkgMTguNTM1LTI3LjYyMiA0LjczLTQuNTgyIDYuNjA3LTYuMTA3IDExLjI4MS05LjE2
MyAxMS45LTcuNzggMjQuMTc0LTExLjg4IDM4LjA5Ni0xMi43MjUgMTkuODA1LTEuMjAxIDM5LjEx
MiA1LjExMyA1NC42MDMgMTcuODYgMS41MDcgMS4yNCAyLjczIDIuMzU4IDIuNzE2IDIuNDg2LS4w
MTMuMTI4LTMuODU4IDMuNjM1LTguNTQ0IDcuNzkzLTQuNjg2IDQuMTU3LTEwLjA0NyA4Ljk2Mi0x
MS45MTQgMTAuNjc3LTEuODY2IDEuNzE1LTMuNTQgMy4xMTktMy43MjEgMy4xMTktLjE4MSAwLTEu
NC0uNzQ2LTIuNzEtMS42NTYtNy41My01LjIzOS0xNS45OTQtNy44MjItMjUuNjI1LTcuODIyLTEy
LjczMiAwLTIzLjI1IDQuMzM4LTMyLjE0NCAxMy4yNTctNi4zOTYgNi40MTQtMTAuNzA0IDE0LjU1
Ni0xMi41IDIzLjYyNC0uNjkxIDMuNDg4LS42OSAxMy41My4wMDIgMTcuMDA5IDMuNzA1IDE4LjYy
NiAxOC4zMTggMzMuMTAyIDM2LjY0MiAzNi4yOTcgNC4xNjQuNzI2IDExLjk4LjcxMiAxNS45OS0u
MDI4IDE0LjAzMi0yLjU5NCAyNS44Ni0xMS4zNjggMzIuMjY1LTIzLjkzNi43NzQtMS41MTkgMS4y
Ni0yLjg4NSAxLjA4LTMuMDM2LS4xNzgtLjE1Mi02Ljg3NC0xLjE3OC0xNC44NzctMi4yODEtOS43
OC0xLjM0OC0xNC45MjQtMi4yMTQtMTUuNjg1LTIuNjQxLTEuNTItLjg1NC0yLjgzNi0yLjg4OC0y
LjgzNi00LjM4NiAwLTEuMTczIDIuMDI3LTE1Ljg2OSAyLjQ5LTE4LjA2LjI5OC0xLjQwMSAyLjQy
Ni0zLjQ5MyAzLjg0NC0zLjc3Ny42MjItLjEyNCA4LjgyNy44NTYgMTguMjggMi4xODQgOS40MzQg
MS4zMjUgMTcuMjYzIDIuMjk0IDE3LjM5OSAyLjE1NC4xMzYtLjE0IDEuMTE4LTYuNTQ4IDIuMTgz
LTE0LjI0IDEuMTA4LTggMi4yMDQtMTQuNjAyIDIuNTYyLTE1LjQyNi4zNDQtLjc5MyAxLjExLTEu
ODUgMS43MDMtMi4zNDggMi4wNjMtMS43MzYgMy4xNDMtMS43ODUgMTIuMjA0LS41NTMgOS42MzYg
MS4zMSAxMC43MDkgMS41NjIgMTIuMjggMi44ODUgMS42NDQgMS4zODMgMi4yNzQgMi44MSAyLjI2
IDUuMTIzLS4wMDcgMS4xMDItLjkyMiA4LjI5Ny0yLjAzMyAxNS45ODktMS4xMTIgNy42OTEtMS45
NzIgMTQuMDQtMS45MTIgMTQuMTA5LjA2MS4wNjggNy4xNjcgMS4xMTEgMTUuNzkyIDIuMzE4IDEx
LjEwNSAxLjU1NCAxNi4wMDggMi4zODcgMTYuODAyIDIuODU2IDEuNTMuOTA0IDIuNDggMi42NDgg
Mi40NSA0LjQ5OC0uMDQ2IDIuODQ0LTIuNDEzIDE4LjEyMy0yLjk3NSAxOS4yMS0uNjYyIDEuMjgt
Mi42MDMgMi41NDgtMy45MjEgMi41NjItLjUyLjAwNS03Ljg3NS0uOTYtMTYuMzQ0LTIuMTQ0LTgu
NDctMS4xODUtMTUuNDc2LTIuMDc3LTE1LjU3LTEuOTgzLS4wOTQuMDk0LTEuMTg4IDcuMzQxLTIu
NDMxIDE2LjEwNi0xLjQ0IDEwLjE1My0yLjQ5OCAxNi40MzYtMi45MTYgMTcuMzE2LS43MjUgMS41
MjgtMi43NjIgMy4wNjMtNC41MzggMy40MTgtLjk1Ny4xOTEtMTAuOS0uOTI4LTEzLjU5OC0xLjUz
LS41NDgtLjEyMy0xLjg5Mi42NzItNC41MSAyLjY2NS0xMS4yNjMgOC41NzYtMjQuMzQyIDEzLjkx
LTM4LjM1NyAxNS42NDItNC40LjU0NC0xNS43MjcuNDMzLTE5Ljg1NC0uMTk1eiIgc3R5bGU9ImZp
bGw6IHJnYig3MywgMTE0LCAxNjUpOyBmaWxsLW9wYWNpdHk6IDE7IHN0cm9rZTogcmdiKDczLCAx
MTQsIDE2NSk7IHN0cm9rZS13aWR0aDogMC4zNzc5NzY7IHN0cm9rZS1vcGFjaXR5OiAxOyI+PC9w
YXRoPjwvc3ZnPg==" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NextGraph</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

@ -0,0 +1,32 @@
{
"compilerOptions": {
"moduleResolution": "bundler",
"target": "ESNext",
"module": "ESNext",
/**
* svelte-preprocess cannot figure out whether you have
* a value or a type, so tell TypeScript to enforce using
* `import type` instead of `import` for Types.
*/
"verbatimModuleSyntax": true,
"isolatedModules": true,
"resolveJsonModule": true,
/**
* To have warnings / errors of the Svelte compiler at the
* correct position, enable source maps by default.
*/
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true,
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable this if you'd like to use dynamic types.
*/
"checkJs": true
},
/**
* Use global.d.ts instead of compilerOptions.types
* to avoid limiting type declarations.
*/
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
}

@ -0,0 +1,27 @@
{
"name": "ng-account-web",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build --base=./",
"preview": "vite preview"
},
"dependencies": {
"flowbite": "^1.6.5",
"flowbite-svelte": "^0.37.1",
"svelte-spa-router": "^3.3.0"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.0.4",
"svelte": "^3.58.0",
"vite": "^4.3.9",
"postcss": "^8.4.23",
"postcss-load-config": "^4.0.1",
"svelte-preprocess": "^5.0.3",
"tailwindcss": "^3.3.1",
"autoprefixer": "^10.4.14",
"vite-plugin-svelte-svg": "^2.2.1"
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,13 @@
const tailwindcss = require("tailwindcss");
const autoprefixer = require("autoprefixer");
const config = {
plugins: [
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
tailwindcss(),
//But others, like autoprefixer, need to run after,
autoprefixer,
],
};
module.exports = config;

@ -0,0 +1,2 @@
User-agent: *
Disallow: /

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -0,0 +1,26 @@
<!--
// 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 Router from "svelte-spa-router";
import { onMount, tick } from "svelte";
import Home from "./routes/Home.svelte";
import NotFound from "./routes/NotFound.svelte";
const routes = new Map();
routes.set("/", Home);
routes.set("*", NotFound);
</script>
<main class="">
<Router {routes} />
</main>

@ -0,0 +1,4 @@
/* Write your global styles here, in PostCSS syntax */
@tailwind base;
@tailwind components;
@tailwind utilities;

@ -0,0 +1,4 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 810 540" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" ><desc>European flag</desc>
<defs><g id="s"><g id="c"><path id="t" d="M0,0v1h0.5z" transform="translate(0,-1)rotate(18)"/><use xlink:href="#t" transform="scale(-1,1)"/></g><g id="a"><use xlink:href="#c" transform="rotate(72)"/><use xlink:href="#c" transform="rotate(144)"/></g><use xlink:href="#a" transform="scale(-1,1)"/></g></defs>
<rect fill="#039" width="810" height="540"/><g fill="#fc0" transform="scale(30)translate(13.5,9)"><use xlink:href="#s" y="-6"/><use xlink:href="#s" y="6"/><g id="l"><use xlink:href="#s" x="-6"/><use xlink:href="#s" transform="rotate(150)translate(0,6)rotate(66)"/><use xlink:href="#s" transform="rotate(120)translate(0,6)rotate(24)"/><use xlink:href="#s" transform="rotate(60)translate(0,6)rotate(12)"/><use xlink:href="#s" transform="rotate(30)translate(0,6)rotate(42)"/></g><use xlink:href="#l" transform="scale(-1,1)"/></g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 225 225"
>
<g>
<circle
r="106.98013"
cy="112.90476"
cx="109.88096"
style="fill:#ffffff;stroke:none;stroke-width:0.268375" />
<path
d="M 98.343352,190.26108 C 80.403778,187.53354 65.011938,179.57839 52.608228,166.62327 38.602093,151.99448 31.178059,133.41381 31.178059,112.98841 c 0,-10.21889 1.700058,-19.44396 5.221234,-28.332119 4.28678,-10.820699 10.037295,-19.39063 18.535095,-27.62263 4.72982,-4.58187 6.60687,-6.10643 11.28099,-9.16256 11.89869,-7.779841 24.173884,-11.879991 38.095802,-12.724761 19.80437,-1.2017 39.11165,5.11306 54.60284,17.858751 1.50718,1.24006 2.72951,2.35934 2.71628,2.48729 -0.0132,0.12795 -3.85821,3.63443 -8.54442,7.79217 -4.6862,4.157729 -10.04724,8.96276 -11.91342,10.677819 -1.86617,1.715071 -3.54094,3.11831 -3.7217,3.11831 -0.18075,0 -1.39985,-0.745188 -2.70911,-1.655969 -7.53011,-5.23834 -15.99428,-7.82188 -25.62597,-7.82188 -12.731628,0 -23.249192,4.3379 -32.143882,13.257541 -6.39594,6.413868 -10.70387,14.555268 -12.50018,23.623578 -0.69099,3.48832 -0.68968,13.53072 0.002,17.00893 3.70508,18.62577 18.31886,33.10194 36.642322,36.29729 4.16439,0.72621 11.98099,0.71223 15.98975,-0.0286 14.03187,-2.59311 25.86047,-11.36806 32.26533,-23.93578 0.77379,-1.51834 1.26018,-2.88461 1.08086,-3.03616 -0.17934,-0.15156 -6.87448,-1.1779 -14.87813,-2.28078 -9.7795,-1.34758 -14.92353,-2.21379 -15.68471,-2.64117 -1.52067,-0.85379 -2.83611,-2.88806 -2.83611,-4.3859 0,-1.1732 2.02687,-15.86876 2.49085,-18.05962 0.29676,-1.40127 2.42559,-3.4934 3.84317,-3.77691 0.62227,-0.12445 8.82712,0.85555 18.28065,2.18348 9.43343,1.32511 17.26269,2.29453 17.39833,2.15427 0.13566,-0.14026 1.11808,-6.54833 2.18313,-14.24014 1.10778,-8.000208 2.20407,-14.60184 2.56177,-15.426229 0.34392,-0.792599 1.11019,-1.849131 1.70287,-2.34782 2.06321,-1.736079 3.1433,-1.785011 12.20439,-0.55291 9.63637,1.310309 10.70873,1.56224 12.28077,2.88503 1.64359,1.382979 2.2732,2.810909 2.25906,5.123309 -0.007,1.10173 -0.92172,8.29645 -2.03332,15.98826 -1.11158,7.69182 -1.97159,14.04091 -1.91113,14.1091 0.0605,0.0682 7.16644,1.11143 15.79109,2.31832 11.10566,1.55407 16.00827,2.38757 16.80223,2.85657 1.53015,0.90389 2.48023,2.64785 2.45017,4.49756 -0.0462,2.84349 -2.41252,18.12279 -2.97521,19.21089 -0.66164,1.27949 -2.60244,2.54696 -3.92109,2.56074 -0.51973,0.005 -7.87449,-0.95937 -16.34391,-2.144 -8.46944,-1.18464 -15.47588,-2.077 -15.56986,-1.98301 -0.094,0.094 -1.18792,7.34163 -2.43097,16.10589 -1.44004,10.15311 -2.49792,16.43621 -2.91556,17.31631 -0.72531,1.52848 -2.76261,3.06291 -4.53817,3.41802 -0.95688,0.19138 -10.90014,-0.92798 -13.59859,-1.53084 -0.5471,-0.12223 -1.89146,0.67252 -4.50941,2.66588 -11.2627,8.57562 -24.34195,13.90917 -38.35741,15.64164 -4.40038,0.54395 -15.72658,0.43298 -19.853658,-0.19451 z"
style="fill:#4972a5;fill-opacity:1;stroke:#4972a5;stroke-width:0.377976;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

@ -0,0 +1,9 @@
import './app.postcss'
import "../../../ng-app/src/styles.css";
import App from './App.svelte'
const app = new App({
target: document.getElementById('app'),
})
export default app

@ -0,0 +1,27 @@
<!--
// 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 } from "flowbite-svelte";
import { link } from "svelte-spa-router";
import { onMount } from "svelte";
const api_url = import.meta.env.PROD
? "api/v1/"
: "http://localhost:3030/api/v1/";
async function bootstrap() {}
onMount(() => bootstrap());
</script>
<div />

@ -0,0 +1,20 @@
<!--
// 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 { Alert } from "flowbite-svelte";
</script>
<div class="p-8">
<Alert color="red">
<span class="font-medium">404</span> Page not found.
</Alert>
</div>

@ -0,0 +1,2 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />

@ -0,0 +1,7 @@
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
export default {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors
preprocess: vitePreprocess(),
}

@ -0,0 +1,23 @@
/** @type {import('tailwindcss').Config}*/
const config = {
content: [
"./src/**/*.{html,js,svelte,ts}",
"../../ng-app/src/**/*.{html,js,svelte,ts}",
"./node_modules/flowbite-svelte/**/*.{html,js,svelte,ts}",
],
theme: {
extend: {
colors: {
primary: { "50": "#eff6ff", "100": "#dbeafe", "200": "#bfdbfe", "300": "#93c5fd", "400": "#60a5fa", "500": "#3b82f6", "600": "#1E88E5", "700": "#4972A5", "800": "#1e40af", "900": "#1e3a8a" }
}
},
},
plugins: [
require('flowbite/plugin')
],
darkMode: 'class',
};
module.exports = config;

@ -0,0 +1,36 @@
import { defineConfig } from 'vite'
import { svelte, vitePreprocess } from '@sveltejs/vite-plugin-svelte'
import sveltePreprocess from "svelte-preprocess";
import svelteSVG from "vite-plugin-svelte-svg";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [svelte({
preprocess: [
vitePreprocess(),
sveltePreprocess({
typescript: false,
postcss: true,
}),
],
}),
svelteSVG({
svgoConfig: {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
// disable plugins
removeViewBox: false,
},
},
},
{
name: 'prefixIds',
}
],
}, // See https://github.com/svg/svgo#configuration
requireSuffix: true, // Set false to accept '.svg' without the '?component'
}),],
})

@ -104,6 +104,14 @@ pub(crate) struct Cli {
#[arg(long)]
pub no_ipv6: bool,
/// Registration of new users is off. default is invitation-only registration
#[arg(long)]
pub registration_off: bool,
/// Registration of new users is open to anybody without restriction. default is invitation-only registration
#[arg(long, conflicts_with("registration_off"))]
pub registration_open: bool,
/// Saves the quick config into a file on disk, that can then be modified for advanced configs
#[arg(long)]
pub save_config: bool,

@ -908,9 +908,18 @@ async fn main_inner() -> Result<(), ()> {
}];
}
let registration = if args.registration_off {
RegistrationConfig::Closed
} else if args.registration_open {
RegistrationConfig::Open
} else {
RegistrationConfig::Invitation
};
config = Some(DaemonConfig::V0(DaemonConfigV0 {
listeners,
overlays_configs: vec![overlays_config],
registration,
}));
if args.print_config {

@ -1,6 +1,6 @@
# nextgraph.one server (ngone)
This server is used internally by NextGraph to redirect users to the right app server from web clients. You probably don't need this server in your infrastructure, even if you decide to self-host a broker under a domain name.
This server is used internally by NextGraph to redirect users to the right app server from web clients. You probably don't need this server in your infrastructure, even if you decide to self-host a broker under your own domain name.
## Install
@ -23,17 +23,6 @@ cargo watch -c -w src -x run
## Build
First you will need to build the single-file release of ng-app.
```
// uncomment line 14 of src/App.svelte: import * as api from "ng-sdk-js";
cd ../ng-app
pnpm filebuild
cd ../ngone
```
then, in ngone:
```
cd web
pnpm run build

@ -25,6 +25,7 @@ use std::{env, fs};
use crate::store::wallet_record::*;
use crate::types::*;
use ng_wallet::types::*;
use p2p_net::types::{APP_NG_ONE_URL, NG_ONE_URL};
use p2p_repo::log::*;
use p2p_repo::types::*;
use p2p_repo::utils::{generate_keypair, sign, verify};
@ -202,8 +203,8 @@ async fn main() {
#[cfg(not(debug_assertions))]
{
cors = cors
.allow_origin("https://nextgraph.one")
.allow_origin("https://app.nextgraph.one")
.allow_origin(NG_ONE_URL)
.allow_origin(APP_NG_ONE_URL)
.allow_origin("https://nextgraph.eu")
.allow_origin("https://nextgraph.net");
}

@ -46,7 +46,7 @@
</div>
<p class="max-w-sm">
A <b>NextGraph Wallet</b> is unique to each individual.<br /> It stores your
A <b>NextGraph Wallet</b> is unique to each person.<br /> It stores your
credentials to access documents. <br />If you already have a wallet, you
should not create a new one, instead,
{#if display_note_on_local_wallets}
@ -190,7 +190,7 @@
</a>
</div>
<div class="row mt-5">
<a href="https://docs.nextgraph.org/en/self-hosted">
<a href="https://nextgraph.org/self-host">
<button
tabindex="-1"
class="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 mr-2 mb-2"
@ -215,7 +215,7 @@
</a>
</div>
<div class="row mt-5 mb-12">
<a href="#">
<a href="https://nextgraph.org/ng-box/">
<button
tabindex="-1"
class="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 mr-2 mb-2"

@ -9,10 +9,15 @@
//! User account
use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;
use std::hash::Hasher;
use std::time::SystemTime;
use p2p_net::types::*;
use p2p_repo::kcv_store::KCVStore;
use p2p_repo::store::*;
use p2p_repo::types::*;
use p2p_repo::types::Timestamp;
use serde_bare::to_vec;
pub struct Account<'a> {
@ -23,13 +28,20 @@ pub struct Account<'a> {
impl<'a> Account<'a> {
const PREFIX: u8 = b"u"[0];
const PREFIX_CLIENT: u8 = b"d"[0];
// propertie's suffixes
const CLIENT: u8 = b"c"[0];
const ADMIN: u8 = b"a"[0];
const OVERLAY: u8 = b"o"[0];
//const OVERLAY: u8 = b"o"[0];
// propertie's client suffixes
const INFO: u8 = b"i"[0];
const LAST_SEEN: u8 = b"l"[0];
const ALL_PROPERTIES: [u8; 3] = [Self::CLIENT, Self::ADMIN, Self::OVERLAY];
const ALL_PROPERTIES: [u8; 2] = [Self::CLIENT, Self::ADMIN];
const ALL_CLIENT_PROPERTIES: [u8; 2] = [Self::INFO, Self::LAST_SEEN];
const SUFFIX_FOR_EXIST_CHECK: u8 = Self::ADMIN;
@ -75,63 +87,104 @@ impl<'a> Account<'a> {
pub fn id(&self) -> UserId {
self.id
}
pub fn add_client(&self, client: &ClientId) -> Result<(), StorageError> {
pub fn add_client(&self, client: &ClientId, info: &ClientInfo) -> Result<(), StorageError> {
if !self.exists() {
return Err(StorageError::BackendError);
}
self.store.put(
Self::PREFIX,
&to_vec(&self.id)?,
Some(Self::CLIENT),
to_vec(client)?,
)
}
pub fn remove_client(&self, client: &ClientId) -> Result<(), StorageError> {
self.store.del_property_value(
let mut s = DefaultHasher::new();
info.hash(&mut s);
let hash = s.finish();
let client_key = (client.clone(), hash);
let client_key_ser = to_vec(&client_key)?;
let info_ser = to_vec(info)?;
self.store.write_transaction(&|tx| {
if tx
.has_property_value(
Self::PREFIX,
&to_vec(&self.id)?,
Some(Self::CLIENT),
to_vec(client)?,
&client_key_ser,
)
}
pub fn has_client(&self, client: &ClientId) -> Result<(), StorageError> {
self.store.has_property_value(
.is_err()
{
tx.put(
Self::PREFIX,
&to_vec(&self.id)?,
Some(Self::CLIENT),
to_vec(client)?,
)
}
pub fn add_overlay(&self, overlay: &OverlayId) -> Result<(), StorageError> {
if !self.exists() {
return Err(StorageError::BackendError);
&client_key_ser,
)?;
}
self.store.put(
Self::PREFIX,
&to_vec(&self.id)?,
Some(Self::OVERLAY),
to_vec(overlay)?,
if tx
.has_property_value(
Self::PREFIX_CLIENT,
&client_key_ser,
Some(Self::INFO),
&info_ser,
)
.is_err()
{
tx.put(
Self::PREFIX_CLIENT,
&client_key_ser,
Some(Self::INFO),
&info_ser,
)?;
}
pub fn remove_overlay(&self, overlay: &OverlayId) -> Result<(), StorageError> {
self.store.del_property_value(
Self::PREFIX,
&to_vec(&self.id)?,
Some(Self::OVERLAY),
to_vec(overlay)?,
)
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
tx.replace(
Self::PREFIX_CLIENT,
&client_key_ser,
Some(Self::LAST_SEEN),
&to_vec(&now)?,
)?;
Ok(())
})
}
pub fn has_overlay(&self, overlay: &OverlayId) -> Result<(), StorageError> {
self.store.has_property_value(
Self::PREFIX,
&to_vec(&self.id)?,
Some(Self::OVERLAY),
to_vec(overlay)?,
)
}
// pub fn has_client(&self, client: &ClientId) -> Result<(), StorageError> {
// self.store.has_property_value(
// Self::PREFIX,
// &to_vec(&self.id)?,
// Some(Self::CLIENT),
// to_vec(client)?,
// )
// }
// pub fn add_overlay(&self, overlay: &OverlayId) -> Result<(), StorageError> {
// if !self.exists() {
// return Err(StorageError::BackendError);
// }
// self.store.put(
// Self::PREFIX,
// &to_vec(&self.id)?,
// Some(Self::OVERLAY),
// to_vec(overlay)?,
// )
// }
// pub fn remove_overlay(&self, overlay: &OverlayId) -> Result<(), StorageError> {
// self.store.del_property_value(
// Self::PREFIX,
// &to_vec(&self.id)?,
// Some(Self::OVERLAY),
// to_vec(overlay)?,
// )
// }
// pub fn has_overlay(&self, overlay: &OverlayId) -> Result<(), StorageError> {
// self.store.has_property_value(
// Self::PREFIX,
// &to_vec(&self.id)?,
// Some(Self::OVERLAY),
// to_vec(overlay)?,
// )
// }
pub fn is_admin(&self) -> Result<bool, StorageError> {
if self
@ -140,7 +193,7 @@ impl<'a> Account<'a> {
Self::PREFIX,
&to_vec(&self.id)?,
Some(Self::ADMIN),
to_vec(&true)?,
&to_vec(&true)?,
)
.is_ok()
{
@ -150,8 +203,15 @@ impl<'a> Account<'a> {
}
pub fn del(&self) -> Result<(), StorageError> {
self.store
.del_all(Self::PREFIX, &to_vec(&self.id)?, &Self::ALL_PROPERTIES)
self.store.write_transaction(&|tx| {
if let Ok(clients) = tx.get_all(Self::PREFIX, &to_vec(&self.id)?, Some(Self::CLIENT)) {
for client in clients {
tx.del_all(Self::PREFIX_CLIENT, &client, &Self::ALL_CLIENT_PROPERTIES)?;
}
}
tx.del_all(Self::PREFIX, &to_vec(&self.id)?, &Self::ALL_PROPERTIES)?;
Ok(())
})
}
}
@ -161,8 +221,8 @@ mod test {
use p2p_repo::store::*;
use p2p_repo::types::*;
use p2p_repo::utils::*;
use stores_lmdb::kcv_store::LmdbKCVStore;
use std::fs;
use stores_lmdb::kcv_store::LmdbKCVStore;
use tempfile::Builder;
use crate::broker_store::account::Account;
@ -184,14 +244,14 @@ mod test {
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]);
// let client_id = PubKey::Ed25519PubKey([56; 32]);
// let client_id_not_added = PubKey::Ed25519PubKey([57; 32]);
account2.add_client(&client_id).unwrap();
// account2.add_client(&client_id).unwrap();
assert!(account2.is_admin().unwrap());
// assert!(account2.is_admin().unwrap());
account.has_client(&client_id).unwrap();
assert!(account.has_client(&client_id_not_added).is_err());
// account.has_client(&client_id).unwrap();
// assert!(account.has_client(&client_id_not_added).is_err());
}
}

@ -0,0 +1,157 @@
// 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.
//! User account
use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;
use std::hash::Hasher;
use std::time::SystemTime;
use p2p_net::types::*;
use p2p_repo::kcv_store::KCVStore;
use p2p_repo::store::*;
use p2p_repo::types::Timestamp;
use p2p_repo::utils::now_timestamp;
use serde_bare::from_slice;
use serde_bare::to_vec;
pub struct Invitation<'a> {
/// User ID
id: [u8; 32],
store: &'a dyn KCVStore,
}
impl<'a> Invitation<'a> {
const PREFIX: u8 = b"i"[0];
// propertie's invitation suffixes
const TYPE: u8 = b"t"[0];
const EXPIRE: u8 = b"e"[0];
const ALL_PROPERTIES: [u8; 2] = [Self::TYPE, Self::EXPIRE];
const SUFFIX_FOR_EXIST_CHECK: u8 = Self::TYPE;
pub fn open(id: &[u8; 32], store: &'a dyn KCVStore) -> Result<Invitation<'a>, StorageError> {
let opening = Invitation {
id: id.clone(),
store,
};
if !opening.exists() {
return Err(StorageError::NotFound);
}
Ok(opening)
}
pub fn create(
id: &InvitationCode,
expiry: u32,
store: &'a dyn KCVStore,
) -> Result<Invitation<'a>, StorageError> {
let (code_type, code) = match id {
InvitationCode::Unique(c) => (0, c.slice()),
InvitationCode::Multi(c) => (1, c.slice()),
InvitationCode::Admin(c) => (2, c.slice()),
};
let acc = Invitation {
id: code.clone(),
store,
};
if acc.exists() {
return Err(StorageError::BackendError);
}
store.write_transaction(&|tx| {
tx.put(
Self::PREFIX,
&to_vec(code)?,
Some(Self::TYPE),
&to_vec(&code_type)?,
)?;
tx.put(
Self::PREFIX,
&to_vec(code)?,
Some(Self::EXPIRE),
&to_vec(&expiry)?,
)?;
Ok(())
})?;
Ok(acc)
}
pub fn exists(&self) -> bool {
self.store
.get(
Self::PREFIX,
&to_vec(&self.id).unwrap(),
Some(Self::SUFFIX_FOR_EXIST_CHECK),
)
.is_ok()
}
pub fn id(&self) -> [u8; 32] {
self.id
}
pub fn is_expired(&self) -> Result<bool, StorageError> {
let expire_ser = self
.store
.get(Self::PREFIX, &to_vec(&self.id)?, Some(Self::EXPIRE))?;
let expire: u32 = from_slice(&expire_ser)?;
if expire < now_timestamp() {
return Ok(true);
}
Ok(false)
}
pub fn del(&self) -> Result<(), StorageError> {
self.store.write_transaction(&|tx| {
tx.del_all(Self::PREFIX, &to_vec(&self.id)?, &Self::ALL_PROPERTIES)?;
Ok(())
})
}
}
#[cfg(test)]
mod test {
use p2p_repo::store::*;
use p2p_repo::types::*;
use p2p_repo::utils::*;
use std::fs;
use stores_lmdb::kcv_store::LmdbKCVStore;
use tempfile::Builder;
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());
}
}

@ -9,3 +9,5 @@ pub mod peer;
pub mod repostoreinfo;
pub mod topic;
pub mod invitation;

@ -139,7 +139,7 @@ impl<'a> Overlay<'a> {
Self::PREFIX,
&to_vec(&self.id)?,
Some(Self::PEER),
to_vec(peer)?,
&to_vec(peer)?,
)
}
@ -168,7 +168,7 @@ impl<'a> Overlay<'a> {
Self::PREFIX,
&to_vec(&self.id)?,
Some(Self::TOPIC),
to_vec(topic)?,
&to_vec(topic)?,
)
}

@ -104,7 +104,7 @@ impl<'a> Topic<'a> {
Self::PREFIX,
&to_vec(&self.id)?,
Some(Self::HEAD),
to_vec(head)?,
&to_vec(head)?,
)
}

@ -7,3 +7,5 @@ pub mod types;
pub mod utils;
pub mod interfaces;
pub mod storage;

@ -12,6 +12,7 @@
//! WebSocket implementation of the Broker
use crate::interfaces::*;
use crate::storage::LmdbBrokerStorage;
use crate::types::*;
use async_std::io::ReadExt;
use async_std::net::{TcpListener, TcpStream};
@ -38,6 +39,7 @@ use p2p_net::types::*;
use p2p_net::utils::get_domain_without_port;
use p2p_net::utils::is_private_ip;
use p2p_net::utils::is_public_ip;
use p2p_net::NG_BOOTSTRAP_LOCAL_URL;
use p2p_repo::log::*;
use p2p_repo::types::SymKey;
use p2p_repo::types::{PrivKey, PubKey};
@ -54,8 +56,7 @@ use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::{thread, time};
use stores_lmdb::kcv_store::LmdbKCVStore;
use stores_lmdb::repo_store::LmdbRepoStore;
use tempfile::Builder;
static LISTENERS_INFO: OnceCell<(HashMap<String, ListenerInfo>, HashMap<BindAddress, String>)> =
@ -90,7 +91,7 @@ fn check_no_origin(origin: Option<&HeaderValue>) -> Result<(), ErrorResponse> {
fn check_origin_is_url(
origin: Option<&HeaderValue>,
domains: Vec<String>,
domains: &Vec<String>,
) -> Result<(), ErrorResponse> {
match origin {
None => Ok(()),
@ -213,6 +214,7 @@ fn upgrade_ws_or_serve_app(
serve_app: bool,
uri: &Uri,
last_etag: Option<&HeaderValue>,
cors: Option<&str>,
) -> Result<(), ErrorResponse> {
if connection.is_some()
&& connection
@ -253,13 +255,16 @@ fn upgrade_ws_or_serve_app(
.body(Some(file.data.to_vec()))
.unwrap();
return Err(res);
} else if uri == "/.ng_bootstrap" {
} else if uri == NG_BOOTSTRAP_LOCAL_URL {
log_debug!("Serving bootstrap");
let res = Response::builder()
.status(StatusCode::OK)
let mut builder = Response::builder().status(StatusCode::OK);
if cors.is_some() {
builder = builder.header("Access-Control-Allow-Origin", cors.unwrap());
}
let res = builder
.header("Content-Type", "text/json")
.header("Cache-Control", "max-age=3600, must-revalidate")
.header("Cache-Control", "max-age=0, must-revalidate")
.body(Some(BOOTSTRAP_STRING.get().unwrap().as_bytes().to_vec()))
.unwrap();
return Err(res);
@ -354,9 +359,10 @@ impl Callback for SecurityCallback {
return upgrade_ws_or_serve_app(
connection,
remote,
listener.config.serve_app,
listener.config.serve_app && !listener.config.refuse_clients,
uri,
last_etag,
None,
);
}
InterfaceType::Loopback => {
@ -372,7 +378,7 @@ impl Callback for SecurityCallback {
// TODO local_urls might need a trailing :port, but it is ok for now as we do starts_with
urls_str = [urls_str, local_urls].concat();
}
check_origin_is_url(origin, urls_str)?;
check_origin_is_url(origin, &urls_str)?;
check_host(host, hosts_str)?;
check_xff_is_public_or_private(xff, listener.config.accept_direct, true)?;
log_debug!(
@ -385,11 +391,18 @@ impl Callback for SecurityCallback {
listener.config.serve_app,
uri,
last_etag,
origin.map(|or| or.to_str().unwrap()).and_then(|val| {
if listener.config.refuse_clients {
None
} else {
Some(val)
}
}),
);
} else if listener.config.accept_forward_for.is_private_domain() {
let (hosts_str, urls_str) =
prepare_domain_url_and_host(&listener.config.accept_forward_for);
check_origin_is_url(origin, urls_str)?;
check_origin_is_url(origin, &urls_str)?;
check_host(host, hosts_str)?;
check_xff_is_public_or_private(xff, false, false)?;
log_debug!("accepted loopback PRIVATE_DOMAIN");
@ -399,12 +412,13 @@ impl Callback for SecurityCallback {
listener.config.serve_app,
uri,
last_etag,
origin.map(|or| or.to_str().unwrap()),
);
} else if listener.config.accept_forward_for == AcceptForwardForV0::No {
check_host(host, local_hosts)?;
check_no_xff(xff)?;
// TODO local_urls might need a trailing :port, but it is ok for now as we do starts_with
check_origin_is_url(origin, local_urls)?;
check_origin_is_url(origin, &local_urls)?;
log_debug!("accepted loopback DIRECT");
return upgrade_ws_or_serve_app(
connection,
@ -412,6 +426,7 @@ impl Callback for SecurityCallback {
listener.config.serve_app,
uri,
last_etag,
origin.map(|or| or.to_str().unwrap()),
);
}
}
@ -444,7 +459,7 @@ impl Callback for SecurityCallback {
]
.concat();
}
check_origin_is_url(origin, urls_str)?;
check_origin_is_url(origin, &urls_str)?;
check_host_in_addrs(host, &addrs)?;
log_debug!("accepted private PUBLIC_STATIC or PUBLIC_DYN with direct {} with refuse_clients {}",listener.config.accept_direct, listener.config.refuse_clients);
return upgrade_ws_or_serve_app(
@ -453,6 +468,7 @@ impl Callback for SecurityCallback {
listener.config.serve_app,
uri,
last_etag,
origin.map(|or| or.to_str().unwrap()),
);
} else if listener.config.accept_forward_for.is_public_domain() {
if !remote.is_private() {
@ -473,7 +489,7 @@ impl Callback for SecurityCallback {
]
.concat();
}
check_origin_is_url(origin, urls_str)?;
check_origin_is_url(origin, &urls_str)?;
check_host(host, hosts_str)?;
log_debug!(
"accepted private PUBLIC_DOMAIN with direct {}",
@ -485,6 +501,13 @@ impl Callback for SecurityCallback {
listener.config.serve_app,
uri,
last_etag,
origin.map(|or| or.to_str().unwrap()).and_then(|val| {
if listener.config.refuse_clients {
None
} else {
Some(val)
}
}),
);
} else if listener.config.accept_forward_for == AcceptForwardForV0::No {
if !remote.is_private() {
@ -494,10 +517,9 @@ impl Callback for SecurityCallback {
check_no_xff(xff)?;
check_host_in_addrs(host, &listener.addrs)?;
check_origin_is_url(
origin,
prepare_urls_from_private_addrs(&listener.addrs, listener.config.port),
)?;
let urls_str =
prepare_urls_from_private_addrs(&listener.addrs, listener.config.port);
check_origin_is_url(origin, &urls_str)?;
log_debug!("accepted private DIRECT");
return upgrade_ws_or_serve_app(
connection,
@ -505,6 +527,7 @@ impl Callback for SecurityCallback {
listener.config.serve_app,
uri,
last_etag,
origin.map(|or| or.to_str().unwrap()),
);
}
}
@ -560,10 +583,10 @@ pub async fn run_server_accept_one(
) -> std::io::Result<()> {
let addrs = format!("{}:{}", addr, port);
let root = tempfile::Builder::new().prefix("ngd").tempdir().unwrap();
let master_key: [u8; 32] = [0; 32];
std::fs::create_dir_all(root.path()).unwrap();
log_debug!("data directory: {}", root.path().to_str().unwrap());
let store = LmdbKCVStore::open(root.path(), master_key);
// let master_key: [u8; 32] = [0; 32];
// std::fs::create_dir_all(root.path()).unwrap();
// log_debug!("data directory: {}", root.path().to_str().unwrap());
// let store = LmdbKCVStore::open(root.path(), master_key);
let socket = TcpListener::bind(addrs.as_str()).await?;
log_debug!("Listening on {}", addrs.as_str());
@ -619,16 +642,6 @@ pub async fn run_server_v0(
return Err(());
}
}
//let root = tempfile::Builder::new().prefix("ngd").tempdir().unwrap();
path.push("storage");
std::fs::create_dir_all(path.clone()).unwrap();
//log::info!("Home directory is {}");
// TODO: open wallet
let master_key: [u8; 32] = [0; 32];
let store = LmdbKCVStore::open(&path, master_key);
let interfaces = get_interface();
let mut listener_infos: HashMap<String, ListenerInfo> = HashMap::new();
@ -748,8 +761,16 @@ pub async fn run_server_v0(
// saving the infos in the broker. This needs to happen before we start listening, as new incoming connections can happen anytime after that.
// and we need those infos for permission checking.
{
//let root = tempfile::Builder::new().prefix("ngd").tempdir().unwrap();
path.push("storage");
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(&mut path, wallet_master_key);
let mut broker = BROKER.write().await;
broker.set_my_peer_id(peer_id);
broker.set_storage(broker_storage);
LISTENERS_INFO
.set(broker.set_listeners(listener_infos))
.unwrap();

@ -0,0 +1,37 @@
/*
* 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.
*/
use std::path::PathBuf;
use crate::types::*;
use p2p_net::broker_storage::*;
use p2p_repo::kcv_store::KCVStore;
use p2p_repo::types::SymKey;
use stores_lmdb::kcv_store::LmdbKCVStore;
use stores_lmdb::repo_store::LmdbRepoStore;
pub struct LmdbBrokerStorage {
wallet_storage: LmdbKCVStore,
}
impl LmdbBrokerStorage {
pub fn open(path: &mut PathBuf, master_key: SymKey) -> Self {
path.push("wallet");
std::fs::create_dir_all(path.clone()).unwrap();
//TODO redo the whole key passing mechanism so it uses zeroize all the way
let wallet_storage = LmdbKCVStore::open(&path, master_key.slice().clone());
LmdbBrokerStorage { wallet_storage }
}
}
impl BrokerStorage for LmdbBrokerStorage {
fn get_user(&self) {}
}

@ -10,6 +10,14 @@ use p2p_net::types::{BrokerOverlayConfigV0, ListenerV0};
use p2p_repo::types::PrivKey;
use serde::{Deserialize, Serialize};
/// Registration config
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum RegistrationConfig {
Closed,
Invitation,
Open,
}
/// DaemonConfig Version 0
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DaemonConfigV0 {
@ -17,6 +25,8 @@ pub struct DaemonConfigV0 {
pub listeners: Vec<ListenerV0>,
pub overlays_configs: Vec<BrokerOverlayConfigV0>,
pub registration: RegistrationConfig,
}
/// Daemon config

@ -25,9 +25,10 @@ noise-protocol = "0.2.0-rc1"
noise-rust-crypto = "0.6.0-rc.1"
ed25519-dalek = "1.0.1"
either = "1.8.1"
reqwest = { version = "0.11.18", features = ["json"] }
reqwest = { version = "0.11.18", features = ["json","native-tls-vendored"] }
url = "2.4.0"
base64-url = "2.0.0"
web-time = "0.2.0"
[target.'cfg(target_arch = "wasm32")'.dependencies.getrandom]
version = "0.2.7"

@ -10,6 +10,7 @@
*/
use crate::actor::*;
use crate::broker_storage::BrokerStorage;
use crate::connection::*;
use crate::errors::*;
use crate::types::*;
@ -75,6 +76,7 @@ pub struct Broker {
shutdown_sender: Sender<ProtocolError>,
closing: bool,
my_peer_id: Option<PubKey>,
storage: Option<Box<dyn BrokerStorage + Send + Sync>>,
test: u32,
tauri_streams: HashMap<String, Sender<Commit>>,
@ -102,6 +104,10 @@ impl Broker {
}
}
pub fn set_storage(&mut self, storage: impl BrokerStorage + 'static) {
self.storage = Some(Box::new(storage));
}
#[cfg(not(target_arch = "wasm32"))]
pub fn set_listeners(
&mut self,
@ -314,6 +320,7 @@ impl Broker {
closing: false,
test: u32::from_be_bytes(random_buf),
my_peer_id: None,
storage: None,
}
}

@ -0,0 +1,17 @@
/*
* 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.
*/
use crate::types::*;
use p2p_repo::kcv_store::KCVStore;
pub trait BrokerStorage: Send + Sync {
fn get_user(&self);
}

@ -19,6 +19,8 @@ pub mod errors;
pub mod broker;
pub mod broker_storage;
pub mod connection;
pub mod actor;
@ -31,6 +33,8 @@ pub mod tests;
pub mod site;
pub static NG_BOOTSTRAP_LOCAL_URL: &str = "/.ng_bootstrap";
#[cfg(debug_assertions)]
pub static WS_PORT: u16 = 14400;

@ -19,6 +19,7 @@ use crate::utils::{
};
use crate::{actor::EActor, actors::*, errors::ProtocolError};
use core::fmt;
use p2p_repo::errors::NgError;
use p2p_repo::types::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
@ -26,6 +27,7 @@ use std::{
any::{Any, TypeId},
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
};
use web_time::SystemTime;
//
// Broker common types
@ -228,6 +230,8 @@ pub struct BrokerServerV0 {
pub peer_id: PubKey,
}
pub const NG_ONE_URL: &str = "https://nextgraph.one";
pub const APP_NG_ONE_URL: &str = "https://app.nextgraph.one";
pub const APP_NG_ONE_WS_URL: &str = "wss://app.nextgraph.one";
@ -242,6 +246,10 @@ fn local_ws_url(port: &u16) -> String {
format!("ws://localhost:{}", if *port == 0 { 80 } else { *port })
}
fn local_http_url(port: &u16) -> String {
format!("http://localhost:{}", if *port == 0 { 80 } else { *port })
}
pub const LOCAL_URLS: [&str; 3] = ["http://localhost", "http://127.0.0.1", "http://[::1]"];
use url::{Host, Url};
@ -316,7 +324,7 @@ impl BrokerServerV0 {
}
Some(format!(
"{}?b={}",
APP_NG_ONE_WS_URL,
APP_NG_ONE_URL,
base64_url::encode(&payload_ser.unwrap())
))
}
@ -369,16 +377,20 @@ impl BrokerServerV0 {
}
}
BrokerServerTypeV0::Domain(domain) => Some(format!("https://{}", domain)),
BrokerServerTypeV0::Localhost(port) => Some(local_ws_url(&port)),
BrokerServerTypeV0::Localhost(port) => Some(local_http_url(&port)),
BrokerServerTypeV0::BoxPrivate(_) => {
if ipv6 {
let v6 = self.first_ipv6().map(|v| v.0);
let v6 = self.server_type.find_first_ipv6().map_or(None, |bindaddr| {
Some(format!("http://{}:{}", bindaddr.ip, bindaddr.port))
});
if v6.is_some() {
return v6;
}
}
if ipv4 {
self.first_ipv4().map(|v| v.0)
self.server_type.find_first_ipv4().map_or(None, |bindaddr| {
Some(format!("http://{}:{}", bindaddr.ip, bindaddr.port))
})
} else {
None
}
@ -387,11 +399,21 @@ impl BrokerServerV0 {
}
}
pub async fn is_public_broker(&self) -> bool {
match &self.server_type {
BrokerServerTypeV0::Localhost(_) => false,
BrokerServerTypeV0::BoxPrivate(_) => false,
BrokerServerTypeV0::BoxPublic(_) => true,
BrokerServerTypeV0::BoxPublicDyn(_) => true,
BrokerServerTypeV0::Domain(_) => true,
}
}
/// on web browser, returns the connection URL and an optional list of BindAddress if a relay is needed
/// filtered by the current location url of the webpage
/// on native apps, returns or the connection URL without optional BindAddress or an empty string with
/// on native apps (do not pass a location), returns or the connection URL without optional BindAddress or an empty string with
/// several BindAddresses to try to connect to with .to_ws_url()
pub async fn get_url(&self, location: Option<String>) -> Option<(String, Vec<BindAddress>)> {
pub async fn get_ws_url(&self, location: Option<String>) -> Option<(String, Vec<BindAddress>)> {
if location.is_some() {
let location = location.unwrap();
if location.starts_with(APP_NG_ONE_URL) {
@ -498,6 +520,173 @@ pub enum BootstrapContent {
V0(BootstrapContentV0),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum InvitationCode {
Unique(SymKey),
Admin(SymKey),
Multi(SymKey),
}
/// Invitation to create an account at a broker. Version 0
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct InvitationV0 {
/// list of servers, in order of preference
pub bootstrap: BootstrapContentV0,
pub code: Option<SymKey>,
/// an optional name to display to the invitee
pub name: Option<String>,
// an optional url to redirect the user to, for accepting ToS and making payment, if any.
pub url: Option<String>,
}
impl Invitation {
pub fn new_v0(
bootstrap: BootstrapContentV0,
name: Option<String>,
url: Option<String>,
) -> Self {
Invitation::V0(InvitationV0 {
bootstrap,
code: Some(SymKey::random()),
name,
url,
})
}
pub fn new_v0_free(
bootstrap: BootstrapContentV0,
name: Option<String>,
url: Option<String>,
) -> Self {
Invitation::V0(InvitationV0 {
bootstrap,
code: None,
name,
url,
})
}
pub fn intersects(&self, invite2: Invitation) -> Invitation {
let Invitation::V0(v0) = self;
let mut new_invite = InvitationV0 {
bootstrap: BootstrapContentV0 { servers: vec![] },
code: v0.code.clone(),
name: v0.name.clone(),
url: v0.url.clone(),
};
for server2 in invite2.get_servers() {
for server1 in &v0.bootstrap.servers {
if *server1 == *server2 {
new_invite.bootstrap.servers.push(server2.clone());
break;
}
}
}
Invitation::V0(new_invite)
}
pub fn get_servers(&self) -> &Vec<BrokerServerV0> {
match self {
Invitation::V0(v0) => &v0.bootstrap.servers,
}
}
/// first URL in the list is the ngone one
pub fn get_urls(&self) -> Vec<String> {
match self {
Invitation::V0(v0) => {
let mut res = vec![];
let ser = serde_bare::to_vec(&self).unwrap();
let url_param = base64_url::encode(&ser);
res.push(format!("{}/#/i/{}", NG_ONE_URL, url_param));
for server in &v0.bootstrap.servers {
match &server.server_type {
BrokerServerTypeV0::Domain(domain) => {
res.push(format!("https://{}/#/i/{}", domain, url_param));
}
BrokerServerTypeV0::BoxPrivate(addrs) => {
for bindaddr in addrs {
res.push(format!(
"http://{}:{}/#/i/{}",
bindaddr.ip, bindaddr.port, url_param
));
}
}
BrokerServerTypeV0::Localhost(port) => {
res.push(format!("{}/#/i/{}", local_http_url(&port), url_param));
}
_ => {}
}
}
res
}
}
}
}
impl TryFrom<String> for Invitation {
type Error = NgError;
fn try_from(value: String) -> Result<Self, NgError> {
let ser = base64_url::decode(&value).map_err(|_| NgError::InvalidInvitation)?;
let invite: Invitation =
serde_bare::from_slice(&ser).map_err(|_| NgError::InvalidInvitation)?;
Ok(invite)
}
}
/// Invitation to create an account at a broker.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Invitation {
V0(InvitationV0),
}
impl From<BootstrapContent> for Invitation {
fn from(value: BootstrapContent) -> Self {
let BootstrapContent::V0(boot) = value;
Invitation::V0(InvitationV0 {
bootstrap: boot,
code: None,
name: None,
url: None,
})
}
}
/// Create an account at a Broker Service Provider (BSP).
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum CreateAccountBSP {
V0(CreateAccountBSPV0),
}
impl TryFrom<String> for CreateAccountBSP {
type Error = NgError;
fn try_from(value: String) -> Result<Self, NgError> {
let ser = base64_url::decode(&value).map_err(|_| NgError::InvalidCreateAccount)?;
let invite: CreateAccountBSP =
serde_bare::from_slice(&ser).map_err(|_| NgError::InvalidCreateAccount)?;
Ok(invite)
}
}
/// Create an account at a Broker Service Provider (BSP). Version 0
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CreateAccountBSPV0 {
pub invitation_code: Option<SymKey>,
/// the user asking to create an account
pub user: PubKey,
/// signature over serialized invitation, with user key
pub sig: Sig,
/// for web access, will redirect after successful signup. if left empty, it means user is on native app.
pub redirect_url: Option<String>,
}
/// ListenerInfo
#[cfg(not(target_arch = "wasm32"))]
#[derive(Clone, Debug, Serialize, Deserialize)]
@ -940,6 +1129,66 @@ pub enum NetAddr {
IPTransport(IPTransportAddr),
}
/**
* info : {
type : WEB | NATIVE-IOS | NATIVE-ANDROID | NATIVE-MACOS | NATIVE-LINUX | NATIVE-WIN
NATIVE-SERVICE | NODE-SERVICE | VERIFIER | CLIENT-BROKER | CLI
vendor : (UA, node version, tauri webview, rust version)
os : operating system string
version : version of client
date_install
date_updated : last update
}
*/
/// Client Type
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum ClientType {
Web,
NativeIos,
NativeAndroid,
NativeMacOS,
NativeLinux,
NativeWin,
NativeService,
NodeService,
Verifier,
ClientBroker,
Cli,
}
/// IP transport address
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct ClientInfoV0 {
pub client_type: ClientType,
pub details: String,
pub version: String,
pub timestamp_install: u64,
pub timestamp_updated: u64,
}
/// Client Info
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum ClientInfo {
V0(ClientInfoV0),
}
impl ClientInfo {
pub fn new(client_type: ClientType, details: String, version: String) -> ClientInfo {
let timestamp_install = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
ClientInfo::V0(ClientInfoV0 {
details,
version,
client_type,
timestamp_install,
timestamp_updated: timestamp_install,
})
}
}
//
// OVERLAY MESSAGES
//
@ -2829,3 +3078,29 @@ pub struct RepoKeysV0 {
pub enum RepoKeys {
V0(RepoKeysV0),
}
#[cfg(test)]
mod test {
use crate::types::{BootstrapContentV0, BrokerServerTypeV0, BrokerServerV0, Invitation};
use p2p_repo::types::PubKey;
#[test]
pub fn invitation() {
let inv = Invitation::new_v0(
BootstrapContentV0 {
servers: vec![BrokerServerV0 {
server_type: BrokerServerTypeV0::Localhost(14400),
peer_id: PubKey::Ed25519PubKey([
95, 73, 225, 250, 3, 147, 24, 164, 177, 211, 34, 244, 45, 130, 111, 136,
229, 145, 53, 167, 50, 168, 140, 227, 65, 111, 203, 41, 210, 186, 162, 149,
]),
}],
},
Some("test invitation".to_string()),
None,
);
println!("{:?}", inv.get_urls());
}
}

@ -9,12 +9,16 @@
* according to those terms.
*/
use crate::types::BootstrapContent;
use crate::types::Invitation;
use crate::NG_BOOTSTRAP_LOCAL_URL;
use async_std::task;
use ed25519_dalek::*;
use futures::{channel::mpsc, select, Future, FutureExt, SinkExt};
use noise_protocol::U8Array;
use noise_protocol::DH;
use noise_rust_crypto::sensitive::Sensitive;
use p2p_repo::errors::NgError;
use p2p_repo::types::PubKey;
use p2p_repo::{log::*, types::PrivKey};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
@ -48,6 +52,58 @@ where
})
}
#[cfg(debug_assertions)]
const APP_PREFIX: &str = "http://localhost:14400";
#[cfg(not(debug_assertions))]
const APP_PREFIX: &str = "";
pub async fn retrieve_local_bootstrap(
location_string: String,
invite_string: Option<String>,
) -> Option<Invitation> {
let invite1: Option<Invitation> = if invite_string.is_some() {
let invitation: Result<Invitation, NgError> = invite_string.clone().unwrap().try_into();
invitation.ok()
} else {
None
};
log_debug!("{}", location_string);
log_debug!("invite_String {:?} invite1{:?}", invite_string, invite1);
let invite2: Option<Invitation> = {
let resp = reqwest::get(format!("{}{}", APP_PREFIX, NG_BOOTSTRAP_LOCAL_URL)).await;
if resp.is_ok() {
let resp = resp.unwrap().json::<BootstrapContent>().await;
resp.ok().map(|v| v.into())
} else {
None
}
};
let res = if invite1.is_none() {
invite2
} else if invite2.is_none() {
invite1
} else {
invite1.map(|i| i.intersects(invite2.unwrap()))
};
if res.is_some() {
for server in res.as_ref().unwrap().get_servers() {
if server
.get_ws_url(Some(location_string.clone()))
.await
.is_some()
{
return res;
}
}
return None;
}
res
}
pub fn sensitive_from_privkey(privkey: PrivKey) -> Sensitive<[u8; 32]> {
// we copy the key here, because otherwise the 2 zeroize would conflict. as the drop of the PrivKey might be called before the one of Sensitive
let mut bits: [u8; 32] = [0u8; 32];

@ -20,6 +20,8 @@ pub enum NgError {
InvalidSignature,
SerializationError,
InvalidKey,
InvalidInvitation,
InvalidCreateAccount,
}
impl Error for NgError {}

@ -66,7 +66,7 @@ pub trait ReadTransaction {
prefix: u8,
key: &Vec<u8>,
suffix: Option<u8>,
value: Vec<u8>,
value: &Vec<u8>,
) -> Result<(), StorageError>;
}

@ -11,6 +11,7 @@ importers:
'@sveltejs/vite-plugin-svelte': ^2.0.0
'@tauri-apps/api': 2.0.0-alpha.4
'@tauri-apps/cli': 2.0.0-alpha.9
'@tauri-apps/plugin-window': 2.0.0-alpha.0
'@tsconfig/svelte': ^3.0.0
'@types/node': ^18.7.10
async-proxy: ^0.4.1
@ -39,6 +40,7 @@ importers:
dependencies:
'@popperjs/core': 2.11.8
'@tauri-apps/api': 2.0.0-alpha.4
'@tauri-apps/plugin-window': 2.0.0-alpha.0
async-proxy: 0.4.1
classnames: 2.3.2
flowbite: 1.6.5
@ -602,6 +604,12 @@ packages:
'@tauri-apps/cli-win32-x64-msvc': 2.0.0-alpha.9
dev: true
/@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'}

@ -90,7 +90,7 @@ impl<'a> ReadTransaction for LmdbTransaction<'a> {
prefix: u8,
key: &Vec<u8>,
suffix: Option<u8>,
value: Vec<u8>,
value: &Vec<u8>,
) -> Result<(), StorageError> {
let property = LmdbKCVStore::compute_property(prefix, key, suffix);
@ -269,7 +269,7 @@ impl ReadTransaction for LmdbKCVStore {
prefix: u8,
key: &Vec<u8>,
suffix: Option<u8>,
value: Vec<u8>,
value: &Vec<u8>,
) -> Result<(), StorageError> {
let property = Self::compute_property(prefix, key, suffix);
let lock = self.environment.read().unwrap();
@ -378,6 +378,7 @@ impl LmdbKCVStore {
let shared_rkv = manager
.get_or_create(path, |path| {
//Rkv::new::<Lmdb>(path) // use this instead to disable encryption
// TODO: fix memory management of the key. it should be zeroized all the way to the LMDB C FFI
Rkv::with_encryption_key_and_mapsize::<Lmdb>(path, key, 2 * 1024 * 1024 * 1024)
})
.unwrap();

Loading…
Cancel
Save