invite_admin

Niko PLP 1 year ago
parent 59f63aef17
commit a1f11f3a6a
  1. 2
      Cargo.lock
  2. 8
      ng-app/src-tauri/src/lib.rs
  3. 10
      ng-app/src/api.ts
  4. 137
      ng-app/src/routes/WalletCreate.svelte
  5. 3
      ng-sdk-js/README.md
  6. 2
      ng-sdk-js/index.html
  7. 5
      ng-sdk-js/js/browser.js
  8. 48
      ng-sdk-js/src/lib.rs
  9. 2
      ngaccount/Cargo.toml
  10. 76
      ngaccount/src/main.rs
  11. 8
      ngaccount/src/types.rs
  12. 6
      ngaccount/web/package.json
  13. 48
      ngaccount/web/pnpm-lock.yaml
  14. 4
      ngaccount/web/src/App.svelte
  15. 265
      ngaccount/web/src/routes/Create.svelte
  16. 186
      ngaccount/web/src/routes/Delete.svelte
  17. 1
      ngaccount/web/vite.config.js
  18. 8
      ngd/src/cli.rs
  19. 24
      ngd/src/main.rs
  20. 2
      p2p-broker/src/broker_store/mod.rs
  21. 101
      p2p-broker/src/broker_store/wallet.rs
  22. 20
      p2p-broker/src/server_ws.rs
  23. 73
      p2p-broker/src/storage.rs
  24. 4
      p2p-broker/src/types.rs
  25. 1
      p2p-client-ws/src/remote_ws.rs
  26. 1
      p2p-client-ws/src/remote_ws_wasm.rs
  27. 92
      p2p-net/src/broker.rs
  28. 42
      p2p-net/src/connection.rs
  29. 1
      p2p-net/src/errors.rs
  30. 2
      p2p-net/src/lib.rs
  31. 49
      p2p-net/src/types.rs
  32. 9
      p2p-net/src/utils.rs
  33. 9
      p2p-repo/src/types.rs

2
Cargo.lock generated

@ -2792,6 +2792,7 @@ dependencies = [
name = "ngaccount" name = "ngaccount"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"base64-url", "base64-url",
"bytes", "bytes",
"env_logger", "env_logger",
@ -2805,7 +2806,6 @@ dependencies = [
"serde_bare", "serde_bare",
"serde_bytes", "serde_bytes",
"serde_json", "serde_json",
"slice_as_array",
"stores-lmdb", "stores-lmdb",
"tokio", "tokio",
"warp", "warp",

@ -10,6 +10,7 @@ use async_std::stream::StreamExt;
use ng_wallet::types::*; use ng_wallet::types::*;
use ng_wallet::*; use ng_wallet::*;
use p2p_net::broker::*; use p2p_net::broker::*;
use p2p_net::types::CreateAccountBSP;
use p2p_net::utils::{spawn_and_log_error, Receiver, ResultSend}; use p2p_net::utils::{spawn_and_log_error, Receiver, ResultSend};
use p2p_repo::log::*; use p2p_repo::log::*;
use p2p_repo::types::*; use p2p_repo::types::*;
@ -75,6 +76,12 @@ async fn wallet_create_wallet(mut params: CreateWalletV0) -> Result<CreateWallet
res res
} }
#[tauri::command(rename_all = "snake_case")]
async fn encode_create_account(payload: CreateAccountBSP) -> Result<String, ()> {
log_info!("{:?}", payload);
payload.encode().ok_or(())
}
#[tauri::command(rename_all = "snake_case")] #[tauri::command(rename_all = "snake_case")]
async fn doc_sync_branch(nuri: &str, stream_id: &str, app: tauri::AppHandle) -> Result<(), ()> { async fn doc_sync_branch(nuri: &str, stream_id: &str, app: tauri::AppHandle) -> Result<(), ()> {
log_info!("doc_sync_branch {} {}", nuri, stream_id); log_info!("doc_sync_branch {} {}", nuri, stream_id);
@ -184,6 +191,7 @@ impl AppBuilder {
wallet_gen_shuffle_for_pin, wallet_gen_shuffle_for_pin,
wallet_open_wallet_with_pazzle, wallet_open_wallet_with_pazzle,
wallet_create_wallet, wallet_create_wallet,
encode_create_account,
]) ])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");

@ -18,6 +18,7 @@ const mapping = {
"wallet_gen_shuffle_for_pin": [], "wallet_gen_shuffle_for_pin": [],
"wallet_open_wallet_with_pazzle": ["wallet","pazzle","pin"], "wallet_open_wallet_with_pazzle": ["wallet","pazzle","pin"],
"wallet_create_wallet": ["params"], "wallet_create_wallet": ["params"],
"encode_create_account": ["payload"],
"test": [ ] "test": [ ]
} }
@ -61,7 +62,7 @@ const handler = {
info.browser.ua = window.navigator.userAgent; info.browser.ua = window.navigator.userAgent;
let res = { let res = {
// TODO: install timestamp // TODO: install timestamp
ClientInfoV0 : { client_type, details: JSON.stringify(info), version, timestamp_install:0, timestamp_updated:0 } V0 : { client_type, details: JSON.stringify(info), version, timestamp_install:0, timestamp_updated:0 }
}; };
//console.log(res); //console.log(res);
return res; return res;
@ -93,7 +94,7 @@ const handler = {
params.result_with_wallet_file = false; params.result_with_wallet_file = false;
params.security_img = Array.from(new Uint8Array(params.security_img)); params.security_img = Array.from(new Uint8Array(params.security_img));
return await tauri.invoke(path[0],{params}) return await tauri.invoke(path[0],{params})
} else if (path[0] === "get_local_bootstrap") { } else if (path[0].starts_with("get_local_bootstrap")) {
return false; return false;
} }
else { else {
@ -108,7 +109,12 @@ const handler = {
const api = createAsyncProxy({}, handler); const api = createAsyncProxy({}, handler);
export const NG_EU_BSP = "https://nextgraph.eu"; export const NG_EU_BSP = "https://nextgraph.eu";
export const NG_EU_BSP_REGISTER = "https://account.nextgraph.eu/#/create";
export const NG_EU_BSP_REGISTERED = "https://nextgraph.eu/#/user/registered";
export const NG_NET_BSP = "https://nextgraph.net"; export const NG_NET_BSP = "https://nextgraph.net";
export const NG_NET_BSP_REGISTER = "https://account.nextgraph.net/#/create";
export const NG_NET_BSP_REGISTERED = "https://nextgraph.net/#/user/registered";
export default api; export default api;

@ -14,7 +14,13 @@
import { link, querystring } from "svelte-spa-router"; import { link, querystring } from "svelte-spa-router";
import EULogo from "../assets/EU.svg?component"; import EULogo from "../assets/EU.svg?component";
import Logo from "../assets/nextgraph.svg?component"; import Logo from "../assets/nextgraph.svg?component";
import { NG_EU_BSP, NG_NET_BSP, default as ng } from "../api"; import {
NG_EU_BSP,
NG_NET_BSP,
NG_EU_BSP_REGISTER,
NG_EU_BSP_REGISTERED,
default as ng,
} from "../api";
import { display_pazzle } from "../wallet_emojis"; import { display_pazzle } from "../wallet_emojis";
import { onMount, tick } from "svelte"; import { onMount, tick } from "svelte";
@ -122,7 +128,10 @@
async function bootstrap() { async function bootstrap() {
console.log(await ng.client_info()); console.log(await ng.client_info());
invitation = await ng.get_local_bootstrap(location.href, params.get("i")); invitation = await ng.get_local_bootstrap_with_public(
location.href,
params.get("i")
);
console.log(invitation); console.log(invitation);
// TODO: implement this error screen and link button // TODO: implement this error screen and link button
if (!invitation && params.get("i")) { if (!invitation && params.get("i")) {
@ -210,14 +219,52 @@
onMount(async () => await bootstrap()); onMount(async () => await bootstrap());
const selectEU = (event) => { ready = {
user: {
Ed25519PubKey: [
141, 114, 111, 29, 59, 133, 182, 172, 177, 211, 238, 224, 62, 208, 206,
18, 226, 219, 118, 229, 184, 76, 204, 29, 194, 228, 248, 186, 15, 113,
125, 119,
],
},
};
const selectEU = async (event) => {
if (!tauri_platform) { if (!tauri_platform) {
window.open(NG_EU_BSP + "/#/wallet/create", "_blank").focus(); let local_invitation = await ng.get_local_bootstrap(location.href);
let additional_bootstrap;
if (local_invitation) {
additional_bootstrap = local_invitation.V0.bootstrap;
}
let create = {
V0: {
additional_bootstrap,
invitation: undefined,
user: ready.user,
redirect_url: NG_EU_BSP_REGISTERED,
},
};
let ca = await ng.encode_create_account(create);
window.location.href = NG_EU_BSP_REGISTER + "?ca=" + ca;
//window.open(), "_self").focus();
} else {
let create = {
V0: {
additional_bootstrap: undefined,
invitation: undefined,
user: ready.user,
redirect_url: NG_EU_BSP_REGISTERED,
},
};
let ca = await ng.encode_create_account(create);
// TODO: open window with registration URL : NG_EU_BSP_REGISTER + "?ca=" + ca;
} }
}; };
const selectNET = (event) => {}; const selectNET = (event) => {};
const enterINVITE = (event) => {}; const enterINVITE = (event) => {};
const enterQRcode = (event) => {}; const enterQRcode = (event) => {};
const displayNGbox = (event) => {};
const displaySelfHost = (event) => {};
</script> </script>
<main class="container3" bind:this={top}> <main class="container3" bind:this={top}>
@ -612,11 +659,12 @@
</svg> </svg>
<span> <span>
Very soon we will offer you the opportunity to host your own Soon we will offer you the opportunity to host your own broker at <b
broker at <b>home</b> or <b>office</b>. Instead of using a "broker >home</b
service provider", you will own a small device that you connect >
behind your internet router. It is called NG Box and will be or <b>office</b>. Instead of using a "broker service provider",
available soon.</span you will own a small device that you connect behind your internet
router. It is called <b>NG Box</b> and will be available soon.</span
> >
</li> </li>
<li class="flex space-x-3"> <li class="flex space-x-3">
@ -637,10 +685,13 @@
</svg> </svg>
<span> <span>
Large organizations and companies have the opportunity to host a Organizations and companies have the opportunity to host a broker <b
broker <b>on-premise</b> or in the cloud, as the software is open >on-premise</b
source. Individuals can also <b>self-host</b> a broker on any VPS server >
or at home.</span or in the <b>cloud</b>, as the software is open source.
Individuals can also
<b>self-host</b> a broker on any VPS server or at home, on their dedicated
hardware.</span
> >
</li> </li>
</ul> </ul>
@ -648,17 +699,6 @@
</div> </div>
</div> </div>
<div class="row mt-5"> <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 <button
on:click|once={selectEU} 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" 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"
@ -666,35 +706,9 @@
<EULogo class="mr-4 block h-10 w-10" alt="European Union flag" /> <EULogo class="mr-4 block h-10 w-10" alt="European Union flag" />
For European Union citizens For European Union citizens
</button> </button>
{/if}
</div> </div>
<div class="row mt-5"> <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 <button
on:click|once={selectNET} 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" 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"
@ -716,7 +730,6 @@
</svg> </svg>
For the rest of the world For the rest of the world
</button> </button>
{/if}
</div> </div>
<div class="row mt-5"> <div class="row mt-5">
@ -774,9 +787,8 @@
</div> </div>
{/if} {/if}
<div class="row mt-5"> <div class="row mt-5">
<a href="https://nextgraph.org/self-host">
<button <button
tabindex="-1" on:click|once={displaySelfHost}
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2" class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
> >
<svg <svg
@ -796,12 +808,10 @@
</svg> </svg>
Self-hosted broker Self-hosted broker
</button> </button>
</a>
</div> </div>
<div class="row mt-5 mb-12"> <div class="row mt-5 mb-12">
<a href="https://nextgraph.org/ng-box/">
<button <button
tabindex="-1" on:click|once={displayNGbox}
class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2" class="choice-button text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:outline-none focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-100/55 mb-2"
> >
<svg <svg
@ -826,7 +836,6 @@
</svg> </svg>
NG Box (owned or invited) NG Box (owned or invited)
</button> </button>
</a>
</div> </div>
{:else if pin.length < 4} {:else if pin.length < 4}
<div class=" max-w-6xl lg:px-8 mx-auto px-4"> <div class=" max-w-6xl lg:px-8 mx-auto px-4">
@ -1091,8 +1100,8 @@
We can keep an encrypted copy of your wallet in our cloud. Only you will We can keep an encrypted copy of your wallet in our cloud. Only you will
be able to download it with a special link. You would have to keep this link be able to download it with a special link. You would have to keep this link
safely though. By selecting this option, you agree to the safely though. By selecting this option, you agree to the
<a target="_blank" href="https://nextgraph.one/tos" <a target="_blank" href="https://nextgraph.one/#/tos"
>Terms and Conditions of our cloud</a >Terms of Service of our cloud</a
>. >.
<br /> <br />
<Toggle class="mt-3" bind:checked={options.cloud} <Toggle class="mt-3" bind:checked={options.cloud}
@ -1116,11 +1125,15 @@
<span class="text-xl" <span class="text-xl"
>Create a link to access your wallet easily? >Create a link to access your wallet easily?
</span> <br /> </span> <br />
When you want to use your wallet on the web or in other apps, we can help When you want to use your wallet on the web or from other devices, we can
you find your wallet by creating a simple link accessible from anywhere. help you find your wallet by creating a simple link accessible from anywhere.
Only you will have access to this link. In order to do so, we will keep Only you will have access to this link. In order to do so, we will keep
your wallet ID and some information about your broker on our cloud servers. your wallet ID and some information about your broker on our cloud servers.
If you prefer to opt out, just uncheck this option. If you prefer to opt out, just uncheck this option. By selecting this option,
you agree to the
<a target="_blank" href="https://nextgraph.one/#/tos"
>Terms of Service of our cloud</a
>.
<br /> <br />
<Toggle class="mt-3" bind:checked={options.bootstrap} <Toggle class="mt-3" bind:checked={options.bootstrap}
>Create a link to my wallet?</Toggle >Create a link to my wallet?</Toggle

@ -46,7 +46,7 @@ node prepare-node.js
For testing in vanilla JS For testing in vanilla JS
``` ```
wasm-pack build --target web -d web wasm-pack build --dev --target web -d web
python3 -m http.server python3 -m http.server
// open http://localhost:8000 // open http://localhost:8000
@ -63,6 +63,7 @@ wasm-pack test --chrome --headless
``` ```
wasm-pack build --target bundler wasm-pack build --target bundler
wasm-pack build -t nodejs -d pkg-node wasm-pack build -t nodejs -d pkg-node
wasm-pack build --target web -d web
node prepare-node.js node prepare-node.js
cd pkg cd pkg
npm publish --access=public npm publish --access=public

@ -25,7 +25,7 @@
start(); start();
test(); test();
}); });
// DON'T DO THE FOLLOW: // DON'T DO THE FOLLOWING:
// it will instantiate twice the SDK, which is not what we want // it will instantiate twice the SDK, which is not what we want
// init().then(() => { // init().then(() => {
// test(); // test();

@ -1,10 +1,9 @@
import {version} from '../../../package.json';
export function client_details() { export function client_details() {
return window.navigator.userAgent; return window.navigator.userAgent;
} }
export function client_details2(obj) { export function client_details2(obj,version) {
console.log("version",version)
obj.browser.appVersion = navigator?.appVersion; obj.browser.appVersion = navigator?.appVersion;
obj.browser.arch = navigator?.platform; obj.browser.arch = navigator?.platform;
obj.browser.vendor = navigator?.vendor; obj.browser.vendor = navigator?.vendor;

@ -21,7 +21,9 @@ use ng_wallet::*;
use p2p_client_ws::remote_ws_wasm::ConnectionWebSocket; use p2p_client_ws::remote_ws_wasm::ConnectionWebSocket;
use p2p_net::broker::*; use p2p_net::broker::*;
use p2p_net::connection::{ClientConfig, StartConfig}; use p2p_net::connection::{ClientConfig, StartConfig};
use p2p_net::types::{BootstrapContentV0, ClientInfoV0, ClientType, DirectPeerId, IP}; use p2p_net::types::{
BootstrapContentV0, ClientInfo, ClientInfoV0, ClientType, CreateAccountBSP, DirectPeerId, IP,
};
use p2p_net::utils::{retrieve_local_bootstrap, spawn_and_log_error, Receiver, ResultSend, Sender}; use p2p_net::utils::{retrieve_local_bootstrap, spawn_and_log_error, Receiver, ResultSend, Sender};
use p2p_net::WS_PORT; use p2p_net::WS_PORT;
use p2p_repo::log::*; use p2p_repo::log::*;
@ -39,7 +41,18 @@ use wasm_bindgen_futures::{future_to_promise, JsFuture};
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[wasm_bindgen] #[wasm_bindgen]
pub async fn get_local_bootstrap(location: String, invite: JsValue) -> JsValue { pub async fn get_local_bootstrap(location: String, invite: JsValue) -> JsValue {
let res = retrieve_local_bootstrap(location, invite.as_string()).await; let res = retrieve_local_bootstrap(location, invite.as_string(), false).await;
if res.is_some() {
serde_wasm_bindgen::to_value(&res.unwrap()).unwrap()
} else {
JsValue::FALSE
}
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn get_local_bootstrap_with_public(location: String, invite: JsValue) -> JsValue {
let res = retrieve_local_bootstrap(location, invite.as_string(), true).await;
if res.is_some() { if res.is_some() {
serde_wasm_bindgen::to_value(&res.unwrap()).unwrap() serde_wasm_bindgen::to_value(&res.unwrap()).unwrap()
} else { } else {
@ -120,7 +133,7 @@ extern "C" {
#[cfg(wasmpack_target = "nodejs")] #[cfg(wasmpack_target = "nodejs")]
#[wasm_bindgen] #[wasm_bindgen]
pub fn client_info() -> JsValue { pub fn client_info() -> ClientInfoV0 {
let res = ClientInfoV0 { let res = ClientInfoV0 {
client_type: ClientType::NodeService, client_type: ClientType::NodeService,
details: client_details(), details: client_details(),
@ -128,6 +141,18 @@ pub fn client_info() -> JsValue {
timestamp_install: 0, timestamp_install: 0,
timestamp_updated: 0, timestamp_updated: 0,
}; };
res
//serde_wasm_bindgen::to_value(&res).unwrap()
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn encode_create_account(payload: JsValue) -> JsValue {
log_info!("{:?}", payload);
let create_account = serde_wasm_bindgen::from_value::<CreateAccountBSP>(payload).unwrap();
log_info!("create_account {:?}", create_account);
let res = create_account.encode();
log_info!("res {:?}", res);
serde_wasm_bindgen::to_value(&res).unwrap() serde_wasm_bindgen::to_value(&res).unwrap()
} }
@ -148,18 +173,18 @@ extern "C" {
#[cfg(not(wasmpack_target = "nodejs"))] #[cfg(not(wasmpack_target = "nodejs"))]
#[wasm_bindgen(module = "/js/browser.js")] #[wasm_bindgen(module = "/js/browser.js")]
extern "C" { extern "C" {
fn client_details2(val: JsValue) -> String; fn client_details2(val: JsValue, version: String) -> String;
} }
#[cfg(all(not(wasmpack_target = "nodejs"), target_arch = "wasm32"))] #[cfg(all(not(wasmpack_target = "nodejs"), target_arch = "wasm32"))]
#[wasm_bindgen] #[wasm_bindgen]
pub fn client_info() -> JsValue { pub fn client_info() -> ClientInfoV0 {
let ua = client_details(); let ua = client_details();
let bowser = Bowser::parse(ua); let bowser = Bowser::parse(ua);
//log_info!("{:?}", bowser); //log_info!("{:?}", bowser);
let details_string = client_details2(bowser); let details_string = client_details2(bowser, env!("CARGO_PKG_VERSION").to_string());
let res = ClientInfoV0 { let res = ClientInfoV0 {
client_type: ClientType::Web, client_type: ClientType::Web,
@ -168,18 +193,16 @@ pub fn client_info() -> JsValue {
timestamp_install: 0, timestamp_install: 0,
timestamp_updated: 0, timestamp_updated: 0,
}; };
serde_wasm_bindgen::to_value(&res).unwrap() res
//serde_wasm_bindgen::to_value(&res).unwrap()
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[wasm_bindgen] #[wasm_bindgen]
pub async fn test() { pub async fn test() {
log_info!("test is {}", BROKER.read().await.test()); log_info!("test is {}", BROKER.read().await.test());
//let client_info = client_info(); let client_info = client_info();
//log_info!("{:?}", client_info); log_info!("{:?}", client_info);
//let b = Bowser::parse(ua);
//log_info!("{:?}", b);
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
@ -313,6 +336,7 @@ pub async fn start() {
user_priv, user_priv,
client, client,
client_priv, client_priv,
info: ClientInfo::V0(client_info()),
}), }),
) )
.await; .await;

@ -23,6 +23,6 @@ serde_bare = "0.5.0"
serde_bytes = "0.11.7" serde_bytes = "0.11.7"
serde-big-array = "0.5.1" serde-big-array = "0.5.1"
base64-url = "2.0.0" base64-url = "2.0.0"
slice_as_array = "1.1.0"
serde_json = "1.0.96" serde_json = "1.0.96"
bytes = "1.0" bytes = "1.0"
anyhow = "1.0.71"

@ -7,7 +7,7 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
#[macro_use] #[macro_use]
extern crate slice_as_array; extern crate anyhow;
mod types; mod types;
@ -23,7 +23,7 @@ use std::{env, fs};
use crate::types::*; use crate::types::*;
use ng_wallet::types::*; use ng_wallet::types::*;
use p2p_net::types::{APP_NG_ONE_URL, NG_ONE_URL}; use p2p_net::types::{CreateAccountBSP, APP_NG_ONE_URL, NG_ONE_URL};
use p2p_repo::log::*; use p2p_repo::log::*;
use p2p_repo::types::*; use p2p_repo::types::*;
use p2p_repo::utils::{generate_keypair, sign, verify}; use p2p_repo::utils::{generate_keypair, sign, verify};
@ -34,49 +34,81 @@ struct Static;
struct Server {} struct Server {}
impl Server {} impl Server {
fn register_(&self, ca: String) -> Result<(), NgHttpError> {
log_debug!("registering {}", ca);
let cabsp: CreateAccountBSP = ca.try_into().map_err(|_| NgHttpError::InvalidParams)?;
log_debug!("{:?}", cabsp);
Ok(())
}
pub fn register(&self, ca: String) -> Response {
match self.register_(ca) {
Ok(_) => warp::http::StatusCode::CREATED.into_response(),
Err(e) => e.into_response(),
}
}
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() -> anyhow::Result<()> {
if std::env::var("RUST_LOG").is_err() { if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "info"); //trace std::env::set_var("RUST_LOG", "info"); //trace
} }
env_logger::init(); env_logger::init();
// let (wallet_key, wallet_id) = generate_keypair(); let server = Arc::new(Server {});
// 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 { let domain =
// id: wallet_id, env::var("NG_ACCOUNT_DOMAIN").map_err(|_| anyhow!("NG_ACCOUNT_DOMAIN must be set"))?;
// content,
// sig,
// });
let server = Arc::new(Server {}); let admin_user =
env::var("NG_ACCOUNT_ADMIN").map_err(|_| anyhow!("NG_ACCOUNT_ADMIN must be set"))?;
// format is IP,PORT,PEERID
let server_address =
env::var("NG_ACCOUNT_SERVER").map_err(|_| anyhow!("NG_ACCOUNT_SERVER must be set"))?;
let addr: Vec<&str> = server_address.split(',').collect();
if addr.len() != 3 {
return Err(anyhow!(
"NG_ACCOUNT_SERVER is invalid. format is IP,PORT,PEERID"
));
}
let ip: IP = addr[0].into();
log::info!("{}", domain);
// GET /api/v1/register/ca with the same ?ca= query param => 201 CREATED
let server_for_move = Arc::clone(&server);
let register_api = warp::get()
.and(warp::path!("register" / String))
.map(move |ca| server_for_move.register(ca));
let api_v1 = warp::path!("api" / "v1" / ..).and(register_api);
let static_files = warp::get().and(warp_embed::embed(&Static)).boxed(); let static_files = warp::get().and(warp_embed::embed(&Static)).boxed();
let mut cors = warp::cors() let mut cors = warp::cors()
.allow_methods(vec!["GET", "POST"]) .allow_methods(vec!["GET"])
.allow_headers(vec!["Content-Type"]); .allow_headers(vec!["Content-Type"]);
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
{ {
cors = cors cors = cors.allow_origin(format!("https://{}", domain));
.allow_origin(NG_ONE_URL)
.allow_origin(APP_NG_ONE_URL)
.allow_origin("https://nextgraph.eu")
.allow_origin("https://nextgraph.net");
} }
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
{ {
log_debug!("CORS: any origin"); log_debug!("CORS: any origin");
cors = cors.allow_any_origin(); cors = cors.allow_any_origin();
} }
log::info!("Starting server on http://localhost:3030"); log::info!("Starting server on http://localhost:3031");
warp::serve(static_files.with(cors)) warp::serve(api_v1.or(static_files).with(cors))
.run(([127, 0, 0, 1], 3031)) .run(([127, 0, 0, 1], 3031))
.await; .await;
Ok(())
} }

@ -20,7 +20,13 @@ impl Reply for NgHttpError {
fn into_response(self) -> Response { fn into_response(self) -> Response {
match (self) { match (self) {
NgHttpError::NotFound => warp::http::StatusCode::NOT_FOUND.into_response(), NgHttpError::NotFound => warp::http::StatusCode::NOT_FOUND.into_response(),
NgHttpError::InvalidParams => warp::http::StatusCode::BAD_REQUEST.into_response(), NgHttpError::InvalidParams => {
let response = Response::new("Invalid params".into());
let (mut parts, body) = response.into_parts();
parts.status = warp::http::StatusCode::BAD_REQUEST;
let response = Response::from_parts(parts, body);
response
}
NgHttpError::AlreadyExists => warp::http::StatusCode::CONFLICT.into_response(), NgHttpError::AlreadyExists => warp::http::StatusCode::CONFLICT.into_response(),
NgHttpError::InternalError => { NgHttpError::InternalError => {
warp::http::StatusCode::INTERNAL_SERVER_ERROR.into_response() warp::http::StatusCode::INTERNAL_SERVER_ERROR.into_response()

@ -4,7 +4,8 @@
"version": "0.1.0", "version": "0.1.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "cross-env NG_ACCOUNT_DOMAIN=example.com vite",
"devenv": "vite",
"build": "vite build --base=./", "build": "vite build --base=./",
"preview": "vite preview" "preview": "vite preview"
}, },
@ -22,6 +23,7 @@
"svelte-preprocess": "^5.0.3", "svelte-preprocess": "^5.0.3",
"tailwindcss": "^3.3.1", "tailwindcss": "^3.3.1",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"vite-plugin-svelte-svg": "^2.2.1" "vite-plugin-svelte-svg": "^2.2.1",
"cross-env": "^7.0.3"
} }
} }

@ -3,6 +3,7 @@ lockfileVersion: 5.4
specifiers: specifiers:
'@sveltejs/vite-plugin-svelte': ^2.0.4 '@sveltejs/vite-plugin-svelte': ^2.0.4
autoprefixer: ^10.4.14 autoprefixer: ^10.4.14
cross-env: ^7.0.3
flowbite: ^1.6.5 flowbite: ^1.6.5
flowbite-svelte: ^0.37.1 flowbite-svelte: ^0.37.1
postcss: ^8.4.23 postcss: ^8.4.23
@ -22,6 +23,7 @@ dependencies:
devDependencies: devDependencies:
'@sveltejs/vite-plugin-svelte': 2.4.1_svelte@3.59.1+vite@4.3.9 '@sveltejs/vite-plugin-svelte': 2.4.1_svelte@3.59.1+vite@4.3.9
autoprefixer: 10.4.14_postcss@8.4.24 autoprefixer: 10.4.14_postcss@8.4.24
cross-env: 7.0.3
postcss: 8.4.24 postcss: 8.4.24
postcss-load-config: 4.0.1_postcss@8.4.24 postcss-load-config: 4.0.1_postcss@8.4.24
svelte: 3.59.1 svelte: 3.59.1
@ -455,6 +457,23 @@ packages:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: true dev: true
/cross-env/7.0.3:
resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
hasBin: true
dependencies:
cross-spawn: 7.0.3
dev: true
/cross-spawn/7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
dev: true
/css-select/5.1.0: /css-select/5.1.0:
resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==}
dependencies: dependencies:
@ -755,6 +774,10 @@ packages:
engines: {node: '>=0.12.0'} engines: {node: '>=0.12.0'}
dev: true dev: true
/isexe/2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
dev: true
/jiti/1.18.2: /jiti/1.18.2:
resolution: {integrity: sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==} resolution: {integrity: sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==}
hasBin: true hasBin: true
@ -895,6 +918,11 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/path-key/3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
dev: true
/path-parse/1.0.7: /path-parse/1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
dev: true dev: true
@ -1054,6 +1082,18 @@ packages:
rimraf: 2.7.1 rimraf: 2.7.1
dev: true dev: true
/shebang-command/2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
dependencies:
shebang-regex: 3.0.0
dev: true
/shebang-regex/3.0.0:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
dev: true
/sorcery/0.11.0: /sorcery/0.11.0:
resolution: {integrity: sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==} resolution: {integrity: sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==}
hasBin: true hasBin: true
@ -1300,6 +1340,14 @@ packages:
vite: 4.3.9 vite: 4.3.9
dev: true dev: true
/which/2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
hasBin: true
dependencies:
isexe: 2.0.0
dev: true
/wrappy/1.0.2: /wrappy/1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
dev: true dev: true

@ -13,11 +13,15 @@
import { onMount, tick } from "svelte"; import { onMount, tick } from "svelte";
import Home from "./routes/Home.svelte"; import Home from "./routes/Home.svelte";
import Create from "./routes/Create.svelte";
import Delete from "./routes/Delete.svelte";
import NotFound from "./routes/NotFound.svelte"; import NotFound from "./routes/NotFound.svelte";
const routes = new Map(); const routes = new Map();
routes.set("/", Home); routes.set("/", Home);
routes.set("/create", Create);
routes.set("/delete", Delete);
routes.set("*", NotFound); routes.set("*", NotFound);
</script> </script>

@ -0,0 +1,265 @@
<!--
// 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 EULogo from "../assets/EU.svg?component";
import Logo from "../assets/nextgraph.svg?component";
import { link, querystring } from "svelte-spa-router";
import { onMount } from "svelte";
let domain = import.meta.env.NG_ACCOUNT_DOMAIN;
const params = new URLSearchParams($querystring);
let ca = params.get("ca");
let top;
const api_url = import.meta.env.PROD
? "api/v1/"
: "http://localhost:3031/api/v1/";
async function register() {
const opts = {
method: "get",
};
const response = await fetch(api_url + "register/" + ca, opts);
const result = await response.text();
console.log("Result:", response.status, result); // 400 is error, 201 ok
}
async function bootstrap() {}
let error;
onMount(() => bootstrap());
const accept = async (event) => {
await register();
};
const refuse = (event) => {
window.history.go(-1);
};
</script>
<main class="container3" bind:this={top}>
<div class="row">
<Logo class="logo block h-24" alt="NextGraph Logo" />
{#if domain == "nextgraph.eu"}
<EULogo
class="logo block h-20"
style="margin-top: 0.5em;"
alt="European Union Logo"
/>
{/if}
</div>
{#if error}
<div class=" max-w-6xl lg:px-8 mx-auto px-4">
<p class="max-w-xl md:mx-auto lg:max-w-2xl">
An error occurred while registering on this broker :<br />
{error}
</p>
</div>
{:else}
{#if ca}
<div class=" max-w-6xl lg:px-8 mx-auto px-4">
<p class="max-w-xl md:mx-auto lg:max-w-2xl">
You would like to choose <b>{domain}</b> as your Broker Service
Provider.<br />Please read carefully the Terms of Service here below,
before you accept them.
</p>
</div>
{/if}
<div class="px-4 pt-5 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">{domain} Terms of Service</h2>
<ul class="mb-8 space-y-4 text-left text-gray-500 dark:text-gray-400">
{#if domain == "nextgraph.eu"}
<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="M20.893 13.393l-1.135-1.135a2.252 2.252 0 01-.421-.585l-1.08-2.16a.414.414 0 00-.663-.107.827.827 0 01-.812.21l-1.273-.363a.89.89 0 00-.738 1.595l.587.39c.59.395.674 1.23.172 1.732l-.2.2c-.212.212-.33.498-.33.796v.41c0 .409-.11.809-.32 1.158l-1.315 2.191a2.11 2.11 0 01-1.81 1.025 1.055 1.055 0 01-1.055-1.055v-1.172c0-.92-.56-1.747-1.414-2.089l-.655-.261a2.25 2.25 0 01-1.383-2.46l.007-.042a2.25 2.25 0 01.29-.787l.09-.15a2.25 2.25 0 012.37-1.048l1.178.236a1.125 1.125 0 001.302-.795l.208-.73a1.125 1.125 0 00-.578-1.315l-.665-.332-.091.091a2.25 2.25 0 01-1.591.659h-.18c-.249 0-.487.1-.662.274a.931.931 0 01-1.458-1.137l1.411-2.353a2.25 2.25 0 00.286-.76m11.928 9.869A9 9 0 008.965 3.525m11.928 9.868A9 9 0 118.965 3.525"
/>
</svg>
<span
>Our servers are located in Germany, and we comply with the GDPR
regulation.</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="M11.35 3.836c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m8.9-4.414c.376.023.75.05 1.124.08 1.131.094 1.976 1.057 1.976 2.192V16.5A2.25 2.25 0 0118 18.75h-2.25m-7.5-10.5H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V18.75m-7.5-10.5h6.375c.621 0 1.125.504 1.125 1.125v9.375m-8.25-3l1.5 1.5 3-3.75"
/>
</svg>
<span>legal details about GDPR... TBD</span>
</li>
{/if}
<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 the data you exchange with us while using the broker is
end-to-end encrypted and we do not have access to your decryption
keys, meaning that we cannot see the content of your documents.</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
>We do not log any private information about you (nor IP, nor
country, nor statistics of any kind). Only your UserId is kept,
together with the list of devices (clientId) you use to connect to
the broker. We collect general purpose information about your
device (OS version, browser version, and if you use the app, the
version and date of last update). We do not have access to any
unique tracking identifier of your device (like Android MAID or
iPhone IDFA). We could nevertheless be asked by law enforcement
authorities, depending on the jurisdiction of the server, to log
the IP you use when connecting to the broker, and/or to provide
them with the encrypted content you have stored on our servers. If
you prefer to avoid that eventually, please refrain from any
illegal activity while using this 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="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>
You can delete your account with us at any time by going to the
link <a target="_blank" href="https://account.{domain}/#/delete"
>https://account.{domain}/#/delete</a
> or by entering in your NextGraph application and selecting the menu,
then Account, then delete</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="M14.25 7.756a4.5 4.5 0 100 8.488M7.5 10.5h5.25m-5.25 3h5.25M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span
>Registration is free of charge. And it would be very nice of you
if you wanted to donate a small amount for the fees we have to pay
for the servers. Here is the donation link: <a
target="_blank"
href="https://nextgraph.org/donate"
>https://nextgraph.org/donate</a
>
</span>
</li>
</ul>
</div>
</div>
{#if ca}
<div class="row mb-20">
<button
on:click|once={accept}
role="button"
class="mr-5 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:outline-none focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
<svg
class="w-8 h-8 mr-2 -ml-1"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19 7.5v3m0 0v3m0-3h3m-3 0h-3m-2.25-4.125a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zM4 19.235v-.11a6.375 6.375 0 0112.75 0v.109A12.318 12.318 0 0110.374 21c-2.331 0-4.512-.645-6.374-1.766z"
/>
</svg>
I accept
</button>
<button
on:click|once={refuse}
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"
>
I refuse
</button>
</div>
{/if}
{/if}
</main>

@ -0,0 +1,186 @@
<!--
// 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 EULogo from "../assets/EU.svg?component";
import Logo from "../assets/nextgraph.svg?component";
import { link, querystring } from "svelte-spa-router";
import { onMount } from "svelte";
const params = new URLSearchParams($querystring);
let ca = params.get("ca");
let domain = import.meta.env.NG_ACCOUNT_DOMAIN;
let top;
const api_url = import.meta.env.PROD
? "api/v1/"
: "http://localhost:3031/api/v1/";
async function bootstrap() {}
let error;
onMount(() => bootstrap());
const accept = (event) => {};
const refuse = (event) => {
window.history.go(-1);
};
</script>
<main class="container3" bind:this={top}>
<div class="row">
<Logo class="logo block h-24" alt="NextGraph Logo" />
{#if domain == "nextgraph.eu"}
<EULogo
class="logo block h-20"
style="margin-top: 0.5em;"
alt="European Union Logo"
/>
{/if}
</div>
{#if error}
<div class=" max-w-6xl lg:px-8 mx-auto px-4">
<p class="max-w-xl md:mx-auto lg:max-w-2xl">
An error occurred while deleting your account on this broker :<br />
{error}
</p>
</div>
{:else}
<div class=" max-w-6xl lg:px-8 mx-auto px-4">
<p class="max-w-xl md:mx-auto lg:max-w-2xl">
You want to delete your account at <b>{domain}</b>?<br />Please read
carefully the details below before you do so.
</p>
</div>
<div class="px-4 pt-5 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">Delete your account at {domain}</h2>
<ul class="mb-8 space-y-4 text-left text-gray-500 dark:text-gray-400">
<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="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
<span
>Your personal data on this broker will be permanently removed
(UserId, ClientId) and the data of your documents will be removed,
except if they are shared with other users who are using this
broker as well.</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="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25"
/>
</svg>
<span
>You can come back anytime. Please understand that you must have
at least one broker configured in your wallet in order to be able
to use NextGraph. You have other options to select a new broker,
like hosting it yourself, or buying an NG Box. Please visit <a
target="_blank"
href="https://nextgraph.one/#/account/register"
>https://nextgraph.one/#/account/register</a
> in order to choose a new 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="M12 9.75v6.75m0 0l-3-3m3 3l3-3m-8.25 6a4.5 4.5 0 01-1.41-8.775 5.25 5.25 0 0110.233-2.33 3 3 0 013.758 3.848A3.752 3.752 0 0118 19.5H6.75z"
/>
</svg>
<span
>All the data you still have locally on your devices (if you
installed the NextGraph application) will remain accessible to you
even after you delete your account from this broker.<br /> If you
haven't installed any NextGraph app yet, maybe it is a good idea
to do so now, before you delete your account from here. This way,
you will keep a copy of all your documents data locally. To
install the app,
<a target="_blank" href="https://nextgraph.one/#/install"
>go here</a
>. After installing the app, you will have to go to the menu and
select "Sync all my documents now".</span
>
</li>
</ul>
</div>
</div>
<div class="row mb-20">
<button
on:click|once={accept}
role="button"
class="mr-5 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:outline-none focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
<svg
class="w-8 h-8 mr-2 -ml-1"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19 7.5v3m0 0v3m0-3h3m-3 0h-3m-2.25-4.125a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zM4 19.235v-.11a6.375 6.375 0 0112.75 0v.109A12.318 12.318 0 0110.374 21c-2.331 0-4.512-.645-6.374-1.766z"
/>
</svg>
Delete my account
</button>
<button
on:click|once={refuse}
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"
>
Cancel
</button>
</div>
{/if}
</main>

@ -5,6 +5,7 @@ import svelteSVG from "vite-plugin-svelte-svg";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
envPrefix: ["VITE_", "NG_"],
plugins: [svelte({ plugins: [svelte({
preprocess: [ preprocess: [
vitePreprocess(), vitePreprocess(),

@ -112,6 +112,14 @@ pub(crate) struct Cli {
#[arg(long, conflicts_with("registration_off"))] #[arg(long, conflicts_with("registration_off"))]
pub registration_open: bool, pub registration_open: bool,
/// Admin userID
#[arg(long)]
pub admin: Option<String>,
/// Admin invitation
#[arg(long, conflicts_with("admin"))]
pub invite_admin: bool,
/// Saves the quick config into a file on disk, that can then be modified for advanced configs /// Saves the quick config into a file on disk, that can then be modified for advanced configs
#[arg(long)] #[arg(long)]
pub save_config: bool, pub save_config: bool,

@ -916,10 +916,24 @@ async fn main_inner() -> Result<(), ()> {
RegistrationConfig::Invitation RegistrationConfig::Invitation
}; };
let admin_user = if args.admin.is_some() {
args.admin
.unwrap()
.as_str()
.try_into()
.map_err(|e| {
log_warn!("The admin UserId supplied is invalid. no admin user configured.");
})
.ok()
} else {
None
};
config = Some(DaemonConfig::V0(DaemonConfigV0 { config = Some(DaemonConfig::V0(DaemonConfigV0 {
listeners, listeners,
overlays_configs: vec![overlays_config], overlays_configs: vec![overlays_config],
registration, registration,
admin_user,
})); }));
if args.print_config { if args.print_config {
@ -972,7 +986,15 @@ async fn main_inner() -> Result<(), ()> {
match config.unwrap() { match config.unwrap() {
DaemonConfig::V0(v0) => { DaemonConfig::V0(v0) => {
run_server_v0(privkey, pubkey, SymKey::from_array(keys[2]), v0, path).await? run_server_v0(
privkey,
pubkey,
SymKey::from_array(keys[2]),
v0,
path,
args.invite_admin,
)
.await?
} }
} }

@ -11,3 +11,5 @@ pub mod repostoreinfo;
pub mod topic; pub mod topic;
pub mod invitation; pub mod invitation;
pub mod wallet;

@ -0,0 +1,101 @@
// 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.
//! Broker Wallet, persists to store all the SymKeys needed to open other storages
use p2p_net::types::*;
use p2p_repo::kcv_store::KCVStore;
use p2p_repo::store::*;
use p2p_repo::types::*;
use serde::{Deserialize, Serialize};
use serde_bare::{from_slice, to_vec};
pub struct Wallet<'a> {
store: &'a dyn KCVStore,
}
impl<'a> Wallet<'a> {
const PREFIX: u8 = b"w"[0];
const PREFIX_OVERLAY: u8 = b"o"[0];
const PREFIX_USER: u8 = b"u"[0];
const KEY_ACCOUNTS: [u8; 8] = *b"accounts";
const KEY_PEERS: [u8; 5] = *b"peers";
// propertie's suffixes
const SYM_KEY: u8 = b"s"[0];
const ALL_PROPERTIES: [u8; 1] = [Self::SYM_KEY];
const SUFFIX_FOR_EXIST_CHECK: u8 = Self::SYM_KEY;
pub fn open(store: &'a dyn KCVStore) -> Wallet<'a> {
Wallet { store }
}
pub fn get_or_create_single_key(
&self,
prefix: u8,
key: &Vec<u8>,
) -> Result<SymKey, StorageError> {
// FIXME. this get or create is not using a transaction, because calls will be made from the broker, that is behind a mutex.
// if this was to change, we should make the get and put inside one transaction.
let get = self
.store
.get(prefix, key, Some(Self::SUFFIX_FOR_EXIST_CHECK));
match get {
Err(e) => {
if e == StorageError::NotFound {
self.create_single_key(prefix, key)
} else {
Err(StorageError::BackendError)
}
}
Ok(p) => {
let k: SymKey = p
.as_slice()
.try_into()
.map_err(|_| StorageError::BackendError)?;
Ok(k)
}
}
}
pub fn get_or_create_user_key(&self, user: &UserId) -> Result<SymKey, StorageError> {
self.get_or_create_single_key(Self::PREFIX_USER, &to_vec(user)?)
}
pub fn get_or_create_overlay_key(&self, overlay: &OverlayId) -> Result<SymKey, StorageError> {
self.get_or_create_single_key(Self::PREFIX_USER, &to_vec(overlay)?)
}
pub fn create_single_key(&self, prefix: u8, key: &Vec<u8>) -> Result<SymKey, StorageError> {
let symkey = SymKey::random();
let vec = symkey.slice().to_vec();
self.store.put(prefix, key, Some(Self::SYM_KEY), vec)?;
Ok(symkey)
}
pub fn exists_single_key(&self, prefix: u8, key: &Vec<u8>) -> bool {
self.store
.get(prefix, key, Some(Self::SUFFIX_FOR_EXIST_CHECK))
.is_ok()
}
pub fn exists_accounts_key(&self) -> bool {
self.exists_single_key(Self::PREFIX, &Self::KEY_ACCOUNTS.to_vec())
}
pub fn create_accounts_key(&self) -> Result<SymKey, StorageError> {
self.create_single_key(Self::PREFIX, &Self::KEY_ACCOUNTS.to_vec())
}
pub fn get_or_create_peers_key(&self) -> Result<SymKey, StorageError> {
self.get_or_create_single_key(Self::PREFIX, &Self::KEY_PEERS.to_vec())
}
pub fn get_or_create_accounts_key(&self) -> Result<SymKey, StorageError> {
self.get_or_create_single_key(Self::PREFIX, &Self::KEY_ACCOUNTS.to_vec())
}
}

@ -39,7 +39,7 @@ use p2p_net::types::*;
use p2p_net::utils::get_domain_without_port; use p2p_net::utils::get_domain_without_port;
use p2p_net::utils::is_private_ip; use p2p_net::utils::is_private_ip;
use p2p_net::utils::is_public_ip; use p2p_net::utils::is_public_ip;
use p2p_net::NG_BOOTSTRAP_LOCAL_URL; use p2p_net::NG_BOOTSTRAP_LOCAL_PATH;
use p2p_repo::log::*; use p2p_repo::log::*;
use p2p_repo::types::SymKey; use p2p_repo::types::SymKey;
use p2p_repo::types::{PrivKey, PubKey}; use p2p_repo::types::{PrivKey, PubKey};
@ -255,7 +255,7 @@ fn upgrade_ws_or_serve_app(
.body(Some(file.data.to_vec())) .body(Some(file.data.to_vec()))
.unwrap(); .unwrap();
return Err(res); return Err(res);
} else if uri == NG_BOOTSTRAP_LOCAL_URL { } else if uri == NG_BOOTSTRAP_LOCAL_PATH {
log_debug!("Serving bootstrap"); log_debug!("Serving bootstrap");
let mut builder = Response::builder().status(StatusCode::OK); let mut builder = Response::builder().status(StatusCode::OK);
@ -609,6 +609,7 @@ pub async fn run_server_v0(
wallet_master_key: SymKey, wallet_master_key: SymKey,
config: DaemonConfigV0, config: DaemonConfigV0,
mut path: PathBuf, mut path: PathBuf,
admin_invite: bool,
) -> Result<(), ()> { ) -> Result<(), ()> {
// check config // check config
@ -754,8 +755,8 @@ pub async fn run_server_v0(
if !accept_clients { if !accept_clients {
log_warn!("There isn't any listener that accept clients. This is a misconfiguration as a core server that cannot receive client connections is useless"); log_warn!("There isn't any listener that accept clients. This is a misconfiguration as a core server that cannot receive client connections is useless");
} }
let bootstrap_v0 = BootstrapContentV0 { servers };
let bootstrap = BootstrapContent::V0(BootstrapContentV0 { servers }); let bootstrap = BootstrapContent::V0(bootstrap_v0.clone());
BOOTSTRAP_STRING.set(json!(bootstrap).to_string()).unwrap(); BOOTSTRAP_STRING.set(json!(bootstrap).to_string()).unwrap();
// saving the infos in the broker. This needs to happen before we start listening, as new incoming connections can happen anytime after that. // saving the infos in the broker. This needs to happen before we start listening, as new incoming connections can happen anytime after that.
@ -766,7 +767,16 @@ pub async fn run_server_v0(
std::fs::create_dir_all(path.clone()).unwrap(); std::fs::create_dir_all(path.clone()).unwrap();
// opening the server storage (that contains the encryption keys for each store/overlay ) // opening the server storage (that contains the encryption keys for each store/overlay )
let broker_storage = LmdbBrokerStorage::open(&mut path, wallet_master_key); let broker_storage = LmdbBrokerStorage::open(
&mut path,
wallet_master_key,
if admin_invite {
Some(bootstrap_v0)
} else {
None
},
)
.map_err(|e| log_err!("Error while opening broker storage: {:?}", e))?;
let mut broker = BROKER.write().await; let mut broker = BROKER.write().await;
broker.set_my_peer_id(peer_id); broker.set_my_peer_id(peer_id);

@ -11,24 +11,85 @@
use std::path::PathBuf; use std::path::PathBuf;
use crate::broker_store::invitation::Invitation;
use crate::broker_store::wallet::Wallet;
use crate::types::*; use crate::types::*;
use p2p_net::broker_storage::*; use p2p_net::broker_storage::*;
use p2p_net::types::{BootstrapContentV0, InvitationCode, InvitationV0};
use p2p_repo::kcv_store::KCVStore; use p2p_repo::kcv_store::KCVStore;
use p2p_repo::log::*;
use p2p_repo::store::StorageError;
use p2p_repo::types::SymKey; use p2p_repo::types::SymKey;
use stores_lmdb::kcv_store::LmdbKCVStore; use stores_lmdb::kcv_store::LmdbKCVStore;
use stores_lmdb::repo_store::LmdbRepoStore; use stores_lmdb::repo_store::LmdbRepoStore;
pub struct LmdbBrokerStorage { pub struct LmdbBrokerStorage {
wallet_storage: LmdbKCVStore, wallet_storage: LmdbKCVStore,
accounts_storage: LmdbKCVStore,
peers_storage: LmdbKCVStore,
} }
impl LmdbBrokerStorage { impl LmdbBrokerStorage {
pub fn open(path: &mut PathBuf, master_key: SymKey) -> Self { pub fn open(
path.push("wallet"); path: &mut PathBuf,
std::fs::create_dir_all(path.clone()).unwrap(); master_key: SymKey,
//TODO redo the whole key passing mechanism so it uses zeroize all the way admin_invite: Option<BootstrapContentV0>,
let wallet_storage = LmdbKCVStore::open(&path, master_key.slice().clone()); ) -> Result<Self, StorageError> {
LmdbBrokerStorage { wallet_storage } // create/open the WALLET
let mut wallet_path = path.clone();
wallet_path.push("wallet");
std::fs::create_dir_all(wallet_path.clone()).unwrap();
//TODO redo the whole key passing mechanism in RKV so it uses zeroize all the way
let wallet_storage = LmdbKCVStore::open(&wallet_path, master_key.slice().clone());
let wallet = Wallet::open(&wallet_storage);
// create/open the ACCOUNTS storage
let mut accounts_path = path.clone();
let accounts_key;
accounts_path.push("accounts");
if admin_invite.is_some() && !accounts_path.exists() && !wallet.exists_accounts_key() {
accounts_key = wallet.create_accounts_key()?;
std::fs::create_dir_all(accounts_path.clone()).unwrap();
let accounts_storage = LmdbKCVStore::open(&accounts_path, accounts_key.slice().clone());
let symkey = SymKey::random();
let invite_code = InvitationCode::Admin(symkey.clone());
let _ = Invitation::create(&invite_code, 0, &accounts_storage)?;
let invitation = p2p_net::types::Invitation::V0(InvitationV0 {
code: Some(symkey),
name: Some("your NG Box, as admin".into()),
url: None,
bootstrap: admin_invite.unwrap(),
});
for link in invitation.get_urls() {
println!("The admin invitation link is: {}", link)
}
} else {
if admin_invite.is_some() {
log_warn!("Cannot add an admin invitation anymore, as it is not the first start of the server.");
}
accounts_key = wallet.get_or_create_accounts_key()?;
}
std::fs::create_dir_all(accounts_path.clone()).unwrap();
//TODO redo the whole key passing mechanism in RKV so it uses zeroize all the way
let accounts_storage = LmdbKCVStore::open(&accounts_path, accounts_key.slice().clone());
// create/open the PEERS storage
let peers_key = wallet.get_or_create_peers_key()?;
let mut peers_path = path.clone();
peers_path.push("peers");
std::fs::create_dir_all(peers_path.clone()).unwrap();
//TODO redo the whole key passing mechanism in RKV so it uses zeroize all the way
let peers_storage = LmdbKCVStore::open(&peers_path, peers_key.slice().clone());
Ok(LmdbBrokerStorage {
wallet_storage,
accounts_storage,
peers_storage,
})
} }
} }

@ -7,7 +7,7 @@
// notice may not be copied, modified, or distributed except // notice may not be copied, modified, or distributed except
// according to those terms. // according to those terms.
use p2p_net::types::{BrokerOverlayConfigV0, ListenerV0}; use p2p_net::types::{BrokerOverlayConfigV0, ListenerV0};
use p2p_repo::types::PrivKey; use p2p_repo::types::{PrivKey, PubKey};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Registration config /// Registration config
@ -27,6 +27,8 @@ pub struct DaemonConfigV0 {
pub overlays_configs: Vec<BrokerOverlayConfigV0>, pub overlays_configs: Vec<BrokerOverlayConfigV0>,
pub registration: RegistrationConfig, pub registration: RegistrationConfig,
pub admin_user: Option<PubKey>,
} }
/// Daemon config /// Daemon config

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

@ -43,6 +43,7 @@ impl IConnect for ConnectionWebSocket {
remote_peer: DirectPeerId, remote_peer: DirectPeerId,
config: StartConfig, config: StartConfig,
) -> Result<ConnectionBase, NetError> { ) -> Result<ConnectionBase, NetError> {
log_debug!("url {}", url);
let mut cnx = ConnectionBase::new(ConnectionDir::Client, TransportProtocol::WS); let mut cnx = ConnectionBase::new(ConnectionDir::Client, TransportProtocol::WS);
let (mut ws, wsio) = WsMeta::connect(url, None).await.map_err(|e| { let (mut ws, wsio) = WsMeta::connect(url, None).await.map_err(|e| {

@ -53,7 +53,6 @@ pub struct BrokerPeerInfo {
#[derive(Debug)] #[derive(Debug)]
pub struct DirectConnection { pub struct DirectConnection {
ip: IP, ip: IP,
interface: String,
remote_peer_id: X25519PrivKey, remote_peer_id: X25519PrivKey,
tp: TransportProtocol, tp: TransportProtocol,
//dir: ConnectionDir, //dir: ConnectionDir,
@ -62,7 +61,7 @@ pub struct DirectConnection {
pub static BROKER: Lazy<Arc<RwLock<Broker>>> = Lazy::new(|| Arc::new(RwLock::new(Broker::new()))); pub static BROKER: Lazy<Arc<RwLock<Broker>>> = Lazy::new(|| Arc::new(RwLock::new(Broker::new())));
pub struct Broker { pub struct Broker<'a> {
direct_connections: HashMap<IP, DirectConnection>, direct_connections: HashMap<IP, DirectConnection>,
/// tuple of optional userId and peer key in montgomery form. userId is always None on the server side. /// tuple of optional userId and peer key in montgomery form. userId is always None on the server side.
peers: HashMap<(Option<PubKey>, X25519PubKey), BrokerPeerInfo>, peers: HashMap<(Option<PubKey>, X25519PubKey), BrokerPeerInfo>,
@ -76,13 +75,13 @@ pub struct Broker {
shutdown_sender: Sender<ProtocolError>, shutdown_sender: Sender<ProtocolError>,
closing: bool, closing: bool,
my_peer_id: Option<PubKey>, my_peer_id: Option<PubKey>,
storage: Option<Box<dyn BrokerStorage + Send + Sync>>, storage: Option<Box<dyn BrokerStorage + Send + Sync + 'a>>,
test: u32, test: u32,
tauri_streams: HashMap<String, Sender<Commit>>, tauri_streams: HashMap<String, Sender<Commit>>,
} }
impl Broker { impl<'a> Broker<'a> {
/// helper function to store the sender of a tauri stream in order to be able to cancel it later on /// helper function to store the sender of a tauri stream in order to be able to cancel it later on
/// only used in Tauri, not used in the JS SDK /// only used in Tauri, not used in the JS SDK
pub fn tauri_stream_add(&mut self, stream_id: String, sender: Sender<Commit>) { pub fn tauri_stream_add(&mut self, stream_id: String, sender: Sender<Commit>) {
@ -104,7 +103,7 @@ impl Broker {
} }
} }
pub fn set_storage(&mut self, storage: impl BrokerStorage + 'static) { pub fn set_storage(&mut self, storage: impl BrokerStorage + 'a) {
self.storage = Some(Box::new(storage)); self.storage = Some(Box::new(storage));
} }
@ -152,7 +151,7 @@ impl Broker {
} }
} }
Authorization::ExtMessage => Err(ProtocolError::AccessDenied), Authorization::ExtMessage => Err(ProtocolError::AccessDenied),
Authorization::Client(_) => Err(ProtocolError::AccessDenied), Authorization::Client(user) => Err(ProtocolError::AccessDenied),
Authorization::Core => Err(ProtocolError::AccessDenied), Authorization::Core => Err(ProtocolError::AccessDenied),
Authorization::Admin(_) => Err(ProtocolError::AccessDenied), Authorization::Admin(_) => Err(ProtocolError::AccessDenied),
Authorization::OverlayJoin(_) => Err(ProtocolError::AccessDenied), Authorization::OverlayJoin(_) => Err(ProtocolError::AccessDenied),
@ -401,6 +400,7 @@ impl Broker {
let _ = self.shutdown_sender.send(ProtocolError::Closing).await; let _ = self.shutdown_sender.send(ProtocolError::Closing).await;
} }
#[cfg(not(target_arch = "wasm32"))]
pub async fn accept( pub async fn accept(
&mut self, &mut self,
mut connection: ConnectionBase, mut connection: ConnectionBase,
@ -460,33 +460,69 @@ impl Broker {
Ok(()) Ok(())
} }
pub async fn attach_peer_id( #[cfg(not(target_arch = "wasm32"))]
pub async fn attach_and_authorize_peer_id(
&mut self, &mut self,
remote_bind_address: BindAddress, remote_bind_address: BindAddress,
local_bind_address: BindAddress, local_bind_address: BindAddress,
remote_peer_id: X25519PrivKey, remote_peer_id: X25519PrivKey,
core: Option<String>, // if client is None it means we are Core mode
) -> Result<(), NetError> { client: Option<ClientAuthContentV0>,
) -> Result<(), ProtocolError> {
log_debug!("ATTACH PEER_ID {:?}", remote_peer_id); log_debug!("ATTACH PEER_ID {:?}", remote_peer_id);
let already = self.peers.get(&(None, remote_peer_id));
if (already.is_some()) {
match already.unwrap().connected {
PeerConnection::NONE => {}
_ => {
return Err(ProtocolError::PeerAlreadyConnected);
}
};
}
// find the listener
let listener_id = self
.bind_addresses
.get(&local_bind_address)
.ok_or(ProtocolError::AccessDenied)?;
let listener = self
.listeners
.get(listener_id)
.ok_or(ProtocolError::AccessDenied)?;
// authorize
if client.is_none() {
// it is a Core connection
if !listener.config.is_core() {
return Err(ProtocolError::AccessDenied);
}
} else {
if !listener.config.accepts_client() {
return Err(ProtocolError::AccessDenied);
}
}
let mut connection = self let mut connection = self
.anonymous_connections .anonymous_connections
.remove(&(local_bind_address, remote_bind_address)) .remove(&(local_bind_address, remote_bind_address))
.ok_or(NetError::InternalError)?; .ok_or(ProtocolError::BrokerError)?;
connection.reset_shutdown(remote_peer_id).await; connection.reset_shutdown(remote_peer_id).await;
let ip = remote_bind_address.ip; let ip = remote_bind_address.ip;
let connected = if core.is_some() { let connected = if let Some(client_auth) = client {
// TODO add client to storage
PeerConnection::Client(connection)
} else {
let dc = DirectConnection { let dc = DirectConnection {
ip, ip,
interface: core.clone().unwrap(),
remote_peer_id, remote_peer_id,
tp: connection.transport_protocol(), tp: connection.transport_protocol(),
cnx: connection, cnx: connection,
}; };
self.direct_connections.insert(ip, dc); self.direct_connections.insert(ip, dc);
PeerConnection::Core(ip) PeerConnection::Core(ip)
} else {
PeerConnection::Client(connection)
}; };
let bpi = BrokerPeerInfo { let bpi = BrokerPeerInfo {
lastPeerAdvert: None, lastPeerAdvert: None,
@ -521,29 +557,39 @@ impl Broker {
return Err(NetError::Closing); return Err(NetError::Closing);
} }
// TODO check that not already connected to peer
// IpAddr::from_str("127.0.0.1");
log_info!("CONNECTING"); log_info!("CONNECTING");
let remote_peer_id_dh = remote_peer_id.to_dh_from_ed();
let already = self
.peers
.get(&(config.get_user(), *remote_peer_id_dh.slice()));
if already.is_some() {
match already.unwrap().connected {
PeerConnection::NONE => {}
_ => {
return Err(NetError::PeerAlreadyConnected);
}
};
}
let mut connection = cnx let mut connection = cnx
.open( .open(
config.get_url(), config.get_url(),
peer_privk.clone(), peer_privk.clone(),
peer_pubk, peer_pubk,
remote_peer_id, remote_peer_id_dh,
config.clone(), config.clone(),
) )
.await?; .await?;
let join = connection.take_shutdown(); let join = connection.take_shutdown();
let remote_peer_id_dh = remote_peer_id.to_dh_slice();
let connected = match &config { let connected = match &config {
StartConfig::Core(config) => { StartConfig::Core(config) => {
let ip = config.addr.ip.clone(); let ip = config.addr.ip.clone();
let dc = DirectConnection { let dc = DirectConnection {
ip, ip,
interface: config.interface.clone(), remote_peer_id: *remote_peer_id_dh.slice(),
remote_peer_id: remote_peer_id_dh,
tp: connection.transport_protocol(), tp: connection.transport_protocol(),
cnx: connection, cnx: connection,
}; };
@ -560,7 +606,7 @@ impl Broker {
}; };
self.peers self.peers
.insert((config.get_user(), remote_peer_id_dh), bpi); .insert((config.get_user(), *remote_peer_id_dh.slice()), bpi);
async fn watch_close( async fn watch_close(
mut join: Receiver<Either<NetError, X25519PrivKey>>, mut join: Receiver<Either<NetError, X25519PrivKey>>,
@ -602,7 +648,7 @@ impl Broker {
cnx, cnx,
peer_privk, peer_privk,
peer_pubk, peer_pubk,
remote_peer_id_dh, *remote_peer_id_dh.slice(),
config, config,
)); ));
Ok(()) Ok(())

@ -93,7 +93,7 @@ pub enum FSMstate {
Start, Start,
Probe, Probe,
Relay, Relay,
Noise0, Noise0, // unused
Noise1, Noise1,
Noise2, Noise2,
Noise3, // unused Noise3, // unused
@ -150,6 +150,7 @@ pub struct ClientConfig {
pub user_priv: PrivKey, pub user_priv: PrivKey,
pub client: PubKey, pub client: PubKey,
pub client_priv: PrivKey, pub client_priv: PrivKey,
pub info: ClientInfo,
} }
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone)]
@ -373,7 +374,7 @@ impl NoiseFSM {
self.local.take().unwrap().to_dh(), self.local.take().unwrap().to_dh(),
)), )),
None, None,
Some(*self.remote.unwrap().to_dh_from_ed().slice()), Some(*self.remote.unwrap().slice()),
None, None,
); );
@ -538,20 +539,7 @@ impl NoiseFSM {
return Err(ProtocolError::NoiseHandshakeFailed); return Err(ProtocolError::NoiseHandshakeFailed);
} }
let peer_id = handshake.get_rs().unwrap(); let peer_id = handshake.get_rs().unwrap();
//self.remote = Some(peer_id); self.remote = Some(PubKey::X25519PubKey(peer_id));
let (local_bind_address, remote_bind_address) =
self.bind_addresses.ok_or(ProtocolError::BrokerError)?;
BROKER
.write()
.await
.attach_peer_id(
remote_bind_address,
local_bind_address,
peer_id,
None,
)
.await
.map_err(|_| ProtocolError::BrokerError)?;
let ciphers = handshake.get_ciphers(); let ciphers = handshake.get_ciphers();
self.noise_cipher_state_enc = Some(ciphers.1); self.noise_cipher_state_enc = Some(ciphers.1);
@ -586,11 +574,13 @@ impl NoiseFSM {
if let StartConfig::Client(client_config) = if let StartConfig::Client(client_config) =
self.config.as_ref().unwrap() self.config.as_ref().unwrap()
{ {
let ClientInfo::V0(info) = &client_config.info;
let content = ClientAuthContentV0 { let content = ClientAuthContentV0 {
user: client_config.user, user: client_config.user,
client: client_config.client, client: client_config.client,
/// Nonce from ServerHello /// Nonce from ServerHello
nonce: hello.nonce().clone(), nonce: hello.nonce().clone(),
info: info.clone(),
}; };
let ser = serde_bare::to_vec(&content)?; let ser = serde_bare::to_vec(&content)?;
let sig = let sig =
@ -610,7 +600,9 @@ impl NoiseFSM {
} }
} }
} }
FSMstate::ServerHello => { FSMstate::ServerHello =>
{
#[cfg(not(target_arch = "wasm32"))]
if let Some(msg) = msg_opt.as_ref() { if let Some(msg) = msg_opt.as_ref() {
if self.dir.is_server() { if self.dir.is_server() {
if let ProtocolMessage::ClientAuth(client_auth) = msg { if let ProtocolMessage::ClientAuth(client_auth) = msg {
@ -625,8 +617,20 @@ impl NoiseFSM {
if verif.is_err() { if verif.is_err() {
result = verif.unwrap_err().into(); result = verif.unwrap_err().into();
} else { } else {
let (local_bind_address, remote_bind_address) =
// TODO check that the device has been registered for this user. if not, set result = AccessDenied self.bind_addresses.ok_or(ProtocolError::BrokerError)?;
result = BROKER
.write()
.await
.attach_and_authorize_peer_id(
remote_bind_address,
local_bind_address,
*self.remote.unwrap().slice(),
Some(client_auth.content_v0()),
)
.await
.err()
.unwrap_or(ProtocolError::NoError);
} }
let auth_result = AuthResult::V0(AuthResultV0 { let auth_result = AuthResult::V0(AuthResultV0 {
result: result.clone() as u16, result: result.clone() as u16,

@ -31,6 +31,7 @@ pub enum NetError {
ProtocolError, ProtocolError,
AccessDenied, AccessDenied,
InternalError, InternalError,
PeerAlreadyConnected,
Closing, Closing,
} //MAX 50 NetErrors } //MAX 50 NetErrors

@ -33,7 +33,7 @@ pub mod tests;
pub mod site; pub mod site;
pub static NG_BOOTSTRAP_LOCAL_URL: &str = "/.ng_bootstrap"; pub static NG_BOOTSTRAP_LOCAL_PATH: &str = "/.ng_bootstrap";
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
pub static WS_PORT: u16 = 14400; pub static WS_PORT: u16 = 14400;

@ -399,7 +399,7 @@ impl BrokerServerV0 {
} }
} }
pub async fn is_public_broker(&self) -> bool { pub fn is_public_server(&self) -> bool {
match &self.server_type { match &self.server_type {
BrokerServerTypeV0::Localhost(_) => false, BrokerServerTypeV0::Localhost(_) => false,
BrokerServerTypeV0::BoxPrivate(_) => false, BrokerServerTypeV0::BoxPrivate(_) => false,
@ -672,18 +672,30 @@ impl TryFrom<String> for CreateAccountBSP {
} }
} }
impl CreateAccountBSP {
pub fn encode(&self) -> Option<String> {
let payload_ser = serde_bare::to_vec(self).ok();
if payload_ser.is_none() {
return None;
}
Some(base64_url::encode(&payload_ser.unwrap()))
}
}
/// Create an account at a Broker Service Provider (BSP). Version 0 /// Create an account at a Broker Service Provider (BSP). Version 0
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CreateAccountBSPV0 { pub struct CreateAccountBSPV0 {
pub invitation_code: Option<SymKey>, pub invitation: Option<InvitationV0>,
pub additional_bootstrap: Option<BootstrapContentV0>,
/// the user asking to create an account /// the user asking to create an account
pub user: PubKey, pub user: PubKey,
/// signature over serialized invitation, with user key /// signature over serialized invitation code, with user key
pub sig: Sig, // pub sig: Sig,
/// for web access, will redirect after successful signup. if left empty, it means user is on native app. /// for web access, will redirect after successful signup. if left empty, it means user was on native app.
pub redirect_url: Option<String>, pub redirect_url: Option<String>,
} }
@ -854,7 +866,6 @@ pub struct ListenerV0 {
pub accept_forward_for: AcceptForwardForV0, pub accept_forward_for: AcceptForwardForV0,
// impl fn is_private() // impl fn is_private()
// returns false if public IP in interface, or if PublicDyn, PublicStatic // returns false if public IP in interface, or if PublicDyn, PublicStatic
// if the ip is local or private, and the forwarding is not PublicDyn nor PublicStatic, (if is_private) then the app is served on HTTP get of /
// an interface with no accept_forward_for and no accept_direct, is de facto, disabled // an interface with no accept_forward_for and no accept_direct, is de facto, disabled
} }
@ -890,6 +901,30 @@ impl ListenerV0 {
} }
} }
pub fn is_core(&self) -> bool {
match self.accept_forward_for {
AcceptForwardForV0::PublicStatic(_) => true,
AcceptForwardForV0::PublicDyn(_) => true,
AcceptForwardForV0::PublicDomain(_) | AcceptForwardForV0::PublicDomainPeer(_) => false,
AcceptForwardForV0::PrivateDomain(_) => false,
AcceptForwardForV0::No => self.if_type == InterfaceType::Public,
}
}
pub fn accepts_client(&self) -> bool {
match self.accept_forward_for {
AcceptForwardForV0::PublicStatic(_)
| AcceptForwardForV0::PublicDyn(_)
| AcceptForwardForV0::PublicDomain(_)
| AcceptForwardForV0::PublicDomainPeer(_) => self.accept_direct || !self.refuse_clients,
AcceptForwardForV0::PrivateDomain(_) => true,
AcceptForwardForV0::No => {
self.if_type == InterfaceType::Public && !self.refuse_clients
|| self.if_type != InterfaceType::Public
}
}
}
pub fn get_bootstraps(&self, addrs: Vec<BindAddress>) -> Vec<BrokerServerTypeV0> { pub fn get_bootstraps(&self, addrs: Vec<BindAddress>) -> Vec<BrokerServerTypeV0> {
let mut res: Vec<BrokerServerTypeV0> = vec![]; let mut res: Vec<BrokerServerTypeV0> = vec![];
match self.accept_forward_for { match self.accept_forward_for {
@ -2910,6 +2945,8 @@ pub struct ClientAuthContentV0 {
/// Client pub key /// Client pub key
pub client: PubKey, pub client: PubKey,
pub info: ClientInfoV0,
/// Nonce from ServerHello /// Nonce from ServerHello
#[serde(with = "serde_bytes")] #[serde(with = "serde_bytes")]
pub nonce: Vec<u8>, pub nonce: Vec<u8>,

@ -11,7 +11,7 @@
use crate::types::BootstrapContent; use crate::types::BootstrapContent;
use crate::types::Invitation; use crate::types::Invitation;
use crate::NG_BOOTSTRAP_LOCAL_URL; use crate::NG_BOOTSTRAP_LOCAL_PATH;
use async_std::task; use async_std::task;
use ed25519_dalek::*; use ed25519_dalek::*;
use futures::{channel::mpsc, select, Future, FutureExt, SinkExt}; use futures::{channel::mpsc, select, Future, FutureExt, SinkExt};
@ -61,6 +61,7 @@ const APP_PREFIX: &str = "";
pub async fn retrieve_local_bootstrap( pub async fn retrieve_local_bootstrap(
location_string: String, location_string: String,
invite_string: Option<String>, invite_string: Option<String>,
must_be_public: bool,
) -> Option<Invitation> { ) -> Option<Invitation> {
let invite1: Option<Invitation> = if invite_string.is_some() { let invite1: Option<Invitation> = if invite_string.is_some() {
let invitation: Result<Invitation, NgError> = invite_string.clone().unwrap().try_into(); let invitation: Result<Invitation, NgError> = invite_string.clone().unwrap().try_into();
@ -72,7 +73,7 @@ pub async fn retrieve_local_bootstrap(
log_debug!("invite_String {:?} invite1{:?}", invite_string, invite1); log_debug!("invite_String {:?} invite1{:?}", invite_string, invite1);
let invite2: Option<Invitation> = { let invite2: Option<Invitation> = {
let resp = reqwest::get(format!("{}{}", APP_PREFIX, NG_BOOTSTRAP_LOCAL_URL)).await; let resp = reqwest::get(format!("{}{}", APP_PREFIX, NG_BOOTSTRAP_LOCAL_PATH)).await;
if resp.is_ok() { if resp.is_ok() {
let resp = resp.unwrap().json::<BootstrapContent>().await; let resp = resp.unwrap().json::<BootstrapContent>().await;
resp.ok().map(|v| v.into()) resp.ok().map(|v| v.into())
@ -91,7 +92,9 @@ pub async fn retrieve_local_bootstrap(
if res.is_some() { if res.is_some() {
for server in res.as_ref().unwrap().get_servers() { for server in res.as_ref().unwrap().get_servers() {
if server if must_be_public && server.is_public_server()
|| !must_be_public
&& server
.get_ws_url(Some(location_string.clone())) .get_ws_url(Some(location_string.clone()))
.await .await
.is_some() .is_some()

@ -69,6 +69,15 @@ impl SymKey {
} }
} }
impl TryFrom<&[u8]> for SymKey {
type Error = NgError;
fn try_from(buf: &[u8]) -> Result<Self, NgError> {
let sym_key_array = *slice_as_array!(buf, [u8; 32]).ok_or(NgError::InvalidKey)?;
let sym_key = SymKey::ChaCha20Key(sym_key_array);
Ok(sym_key)
}
}
/// Curve25519 public key Edwards form /// Curve25519 public key Edwards form
pub type Ed25519PubKey = [u8; 32]; pub type Ed25519PubKey = [u8; 32];

Loading…
Cancel
Save