diff --git a/Cargo.lock b/Cargo.lock index 928508dc..5fc933cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2792,6 +2792,7 @@ dependencies = [ name = "ngaccount" version = "0.1.0" dependencies = [ + "anyhow", "base64-url", "bytes", "env_logger", @@ -2805,7 +2806,6 @@ dependencies = [ "serde_bare", "serde_bytes", "serde_json", - "slice_as_array", "stores-lmdb", "tokio", "warp", diff --git a/ng-app/src-tauri/src/lib.rs b/ng-app/src-tauri/src/lib.rs index d0f62d00..d4095473 100644 --- a/ng-app/src-tauri/src/lib.rs +++ b/ng-app/src-tauri/src/lib.rs @@ -10,6 +10,7 @@ use async_std::stream::StreamExt; use ng_wallet::types::*; use ng_wallet::*; use p2p_net::broker::*; +use p2p_net::types::CreateAccountBSP; use p2p_net::utils::{spawn_and_log_error, Receiver, ResultSend}; use p2p_repo::log::*; use p2p_repo::types::*; @@ -75,6 +76,12 @@ async fn wallet_create_wallet(mut params: CreateWalletV0) -> Result<CreateWallet 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")] async fn doc_sync_branch(nuri: &str, stream_id: &str, app: tauri::AppHandle) -> Result<(), ()> { log_info!("doc_sync_branch {} {}", nuri, stream_id); @@ -184,6 +191,7 @@ impl AppBuilder { wallet_gen_shuffle_for_pin, wallet_open_wallet_with_pazzle, wallet_create_wallet, + encode_create_account, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/ng-app/src/api.ts b/ng-app/src/api.ts index 9b2d0d4f..3a2d9398 100644 --- a/ng-app/src/api.ts +++ b/ng-app/src/api.ts @@ -18,6 +18,7 @@ const mapping = { "wallet_gen_shuffle_for_pin": [], "wallet_open_wallet_with_pazzle": ["wallet","pazzle","pin"], "wallet_create_wallet": ["params"], + "encode_create_account": ["payload"], "test": [ ] } @@ -61,7 +62,7 @@ const handler = { 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 } + V0 : { client_type, details: JSON.stringify(info), version, timestamp_install:0, timestamp_updated:0 } }; //console.log(res); return res; @@ -93,7 +94,7 @@ 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") { + } else if (path[0].starts_with("get_local_bootstrap")) { return false; } else { @@ -108,7 +109,12 @@ const handler = { const api = createAsyncProxy({}, handler); export const NG_EU_BSP = "https://nextgraph.eu"; +export const NG_EU_BSP_REGISTER = "https://account.nextgraph.eu/#/create"; +export const NG_EU_BSP_REGISTERED = "https://nextgraph.eu/#/user/registered"; export const NG_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; \ No newline at end of file diff --git a/ng-app/src/routes/WalletCreate.svelte b/ng-app/src/routes/WalletCreate.svelte index 34de688a..ad215281 100644 --- a/ng-app/src/routes/WalletCreate.svelte +++ b/ng-app/src/routes/WalletCreate.svelte @@ -14,7 +14,13 @@ import { link, querystring } from "svelte-spa-router"; import EULogo from "../assets/EU.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 { onMount, tick } from "svelte"; @@ -122,7 +128,10 @@ async function bootstrap() { 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); // TODO: implement this error screen and link button if (!invitation && params.get("i")) { @@ -210,14 +219,52 @@ 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) { - 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 enterINVITE = (event) => {}; const enterQRcode = (event) => {}; + const displayNGbox = (event) => {}; + const displaySelfHost = (event) => {}; </script> <main class="container3" bind:this={top}> @@ -612,11 +659,12 @@ </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 + 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 <b>NG Box</b> and will be available soon.</span > </li> <li class="flex space-x-3"> @@ -637,10 +685,13 @@ </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 + Organizations and companies have the opportunity to host a broker <b + >on-premise</b + > + 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> </ul> @@ -648,75 +699,37 @@ </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} + <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> </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" + <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" > - <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} + <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> </div> <div class="row mt-5"> @@ -774,59 +787,55 @@ </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" + <button + 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" + > + <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" > - <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> + <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> </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" + <button + 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" + > + <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" > - <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> + <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> </div> {:else if pin.length < 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 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 - <a target="_blank" href="https://nextgraph.one/tos" - >Terms and Conditions of our cloud</a + <a target="_blank" href="https://nextgraph.one/#/tos" + >Terms of Service of our cloud</a >. <br /> <Toggle class="mt-3" bind:checked={options.cloud} @@ -1116,11 +1125,15 @@ <span class="text-xl" >Create a link to access your wallet easily? </span> <br /> - When you want to use your wallet on the web or in other apps, we can help - you find your wallet by creating a simple link accessible from anywhere. + When you want to use your wallet on the web or from other devices, we can + 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 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 /> <Toggle class="mt-3" bind:checked={options.bootstrap} >Create a link to my wallet?</Toggle diff --git a/ng-sdk-js/README.md b/ng-sdk-js/README.md index 0f1bc8f9..742a4651 100644 --- a/ng-sdk-js/README.md +++ b/ng-sdk-js/README.md @@ -46,7 +46,7 @@ node prepare-node.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 // open http://localhost:8000 @@ -63,6 +63,7 @@ wasm-pack test --chrome --headless ``` wasm-pack build --target bundler wasm-pack build -t nodejs -d pkg-node +wasm-pack build --target web -d web node prepare-node.js cd pkg npm publish --access=public diff --git a/ng-sdk-js/index.html b/ng-sdk-js/index.html index b0f95566..f947c575 100644 --- a/ng-sdk-js/index.html +++ b/ng-sdk-js/index.html @@ -25,7 +25,7 @@ start(); test(); }); - // DON'T DO THE FOLLOW: + // DON'T DO THE FOLLOWING: // it will instantiate twice the SDK, which is not what we want // init().then(() => { // test(); diff --git a/ng-sdk-js/js/browser.js b/ng-sdk-js/js/browser.js index c2a7500f..e0edfa3d 100644 --- a/ng-sdk-js/js/browser.js +++ b/ng-sdk-js/js/browser.js @@ -1,10 +1,9 @@ -import {version} from '../../../package.json'; - export function client_details() { 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.arch = navigator?.platform; obj.browser.vendor = navigator?.vendor; diff --git a/ng-sdk-js/src/lib.rs b/ng-sdk-js/src/lib.rs index ee272f5e..ca07e8da 100644 --- a/ng-sdk-js/src/lib.rs +++ b/ng-sdk-js/src/lib.rs @@ -21,7 +21,9 @@ 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::{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::WS_PORT; use p2p_repo::log::*; @@ -39,7 +41,18 @@ 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; + 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() { serde_wasm_bindgen::to_value(&res.unwrap()).unwrap() } else { @@ -120,7 +133,7 @@ extern "C" { #[cfg(wasmpack_target = "nodejs")] #[wasm_bindgen] -pub fn client_info() -> JsValue { +pub fn client_info() -> ClientInfoV0 { let res = ClientInfoV0 { client_type: ClientType::NodeService, details: client_details(), @@ -128,6 +141,18 @@ pub fn client_info() -> JsValue { timestamp_install: 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() } @@ -148,18 +173,18 @@ extern "C" { #[cfg(not(wasmpack_target = "nodejs"))] #[wasm_bindgen(module = "/js/browser.js")] 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"))] #[wasm_bindgen] -pub fn client_info() -> JsValue { +pub fn client_info() -> ClientInfoV0 { let ua = client_details(); let bowser = Bowser::parse(ua); //log_info!("{:?}", bowser); - let details_string = client_details2(bowser); + let details_string = client_details2(bowser, env!("CARGO_PKG_VERSION").to_string()); let res = ClientInfoV0 { client_type: ClientType::Web, @@ -168,18 +193,16 @@ pub fn client_info() -> JsValue { timestamp_install: 0, timestamp_updated: 0, }; - serde_wasm_bindgen::to_value(&res).unwrap() + res + //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); + let client_info = client_info(); + log_info!("{:?}", client_info); } #[cfg(target_arch = "wasm32")] @@ -313,6 +336,7 @@ pub async fn start() { user_priv, client, client_priv, + info: ClientInfo::V0(client_info()), }), ) .await; diff --git a/ngaccount/Cargo.toml b/ngaccount/Cargo.toml index 396d10ef..d2aefb33 100644 --- a/ngaccount/Cargo.toml +++ b/ngaccount/Cargo.toml @@ -23,6 +23,6 @@ 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" \ No newline at end of file +bytes = "1.0" +anyhow = "1.0.71" \ No newline at end of file diff --git a/ngaccount/src/main.rs b/ngaccount/src/main.rs index fd11021b..b3395714 100644 --- a/ngaccount/src/main.rs +++ b/ngaccount/src/main.rs @@ -7,7 +7,7 @@ // notice may not be copied, modified, or distributed except // according to those terms. #[macro_use] -extern crate slice_as_array; +extern crate anyhow; mod types; @@ -23,7 +23,7 @@ use std::{env, fs}; use crate::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::types::*; use p2p_repo::utils::{generate_keypair, sign, verify}; @@ -34,49 +34,81 @@ struct Static; 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] -async fn main() { +async fn main() -> anyhow::Result<()> { 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 server = Arc::new(Server {}); + + let domain = + env::var("NG_ACCOUNT_DOMAIN").map_err(|_| anyhow!("NG_ACCOUNT_DOMAIN must be set"))?; - // let bootstrap = Bootstrap::V0(BootstrapV0 { - // id: wallet_id, - // content, - // sig, - // }); + let admin_user = + env::var("NG_ACCOUNT_ADMIN").map_err(|_| anyhow!("NG_ACCOUNT_ADMIN must be set"))?; - let server = Arc::new(Server {}); + // 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 mut cors = warp::cors() - .allow_methods(vec!["GET", "POST"]) + .allow_methods(vec!["GET"]) .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"); + cors = cors.allow_origin(format!("https://{}", domain)); } #[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)) + log::info!("Starting server on http://localhost:3031"); + warp::serve(api_v1.or(static_files).with(cors)) .run(([127, 0, 0, 1], 3031)) .await; + + Ok(()) } diff --git a/ngaccount/src/types.rs b/ngaccount/src/types.rs index ebea5552..64047977 100644 --- a/ngaccount/src/types.rs +++ b/ngaccount/src/types.rs @@ -20,7 +20,13 @@ 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::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::InternalError => { warp::http::StatusCode::INTERNAL_SERVER_ERROR.into_response() diff --git a/ngaccount/web/package.json b/ngaccount/web/package.json index 55f0957f..7f954374 100644 --- a/ngaccount/web/package.json +++ b/ngaccount/web/package.json @@ -4,7 +4,8 @@ "version": "0.1.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "cross-env NG_ACCOUNT_DOMAIN=example.com vite", + "devenv": "vite", "build": "vite build --base=./", "preview": "vite preview" }, @@ -22,6 +23,7 @@ "svelte-preprocess": "^5.0.3", "tailwindcss": "^3.3.1", "autoprefixer": "^10.4.14", - "vite-plugin-svelte-svg": "^2.2.1" + "vite-plugin-svelte-svg": "^2.2.1", + "cross-env": "^7.0.3" } } diff --git a/ngaccount/web/pnpm-lock.yaml b/ngaccount/web/pnpm-lock.yaml index a71774a2..8a2fcdee 100644 --- a/ngaccount/web/pnpm-lock.yaml +++ b/ngaccount/web/pnpm-lock.yaml @@ -3,6 +3,7 @@ lockfileVersion: 5.4 specifiers: '@sveltejs/vite-plugin-svelte': ^2.0.4 autoprefixer: ^10.4.14 + cross-env: ^7.0.3 flowbite: ^1.6.5 flowbite-svelte: ^0.37.1 postcss: ^8.4.23 @@ -22,6 +23,7 @@ dependencies: devDependencies: '@sveltejs/vite-plugin-svelte': 2.4.1_svelte@3.59.1+vite@4.3.9 autoprefixer: 10.4.14_postcss@8.4.24 + cross-env: 7.0.3 postcss: 8.4.24 postcss-load-config: 4.0.1_postcss@8.4.24 svelte: 3.59.1 @@ -455,6 +457,23 @@ packages: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 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: resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} dependencies: @@ -755,6 +774,10 @@ packages: engines: {node: '>=0.12.0'} dev: true + /isexe/2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true + /jiti/1.18.2: resolution: {integrity: sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==} hasBin: true @@ -895,6 +918,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /path-key/3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: true + /path-parse/1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true @@ -1054,6 +1082,18 @@ packages: rimraf: 2.7.1 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: resolution: {integrity: sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==} hasBin: true @@ -1300,6 +1340,14 @@ packages: vite: 4.3.9 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: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} dev: true diff --git a/ngaccount/web/src/App.svelte b/ngaccount/web/src/App.svelte index 18fb37d4..c104b44c 100644 --- a/ngaccount/web/src/App.svelte +++ b/ngaccount/web/src/App.svelte @@ -13,11 +13,15 @@ import { onMount, tick } from "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"; const routes = new Map(); routes.set("/", Home); + routes.set("/create", Create); + routes.set("/delete", Delete); routes.set("*", NotFound); </script> diff --git a/ngaccount/web/src/routes/Create.svelte b/ngaccount/web/src/routes/Create.svelte new file mode 100644 index 00000000..de94a954 --- /dev/null +++ b/ngaccount/web/src/routes/Create.svelte @@ -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> diff --git a/ngaccount/web/src/routes/Delete.svelte b/ngaccount/web/src/routes/Delete.svelte new file mode 100644 index 00000000..8a2691db --- /dev/null +++ b/ngaccount/web/src/routes/Delete.svelte @@ -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> diff --git a/ngaccount/web/vite.config.js b/ngaccount/web/vite.config.js index cb3bff81..7798ab2f 100644 --- a/ngaccount/web/vite.config.js +++ b/ngaccount/web/vite.config.js @@ -5,6 +5,7 @@ import svelteSVG from "vite-plugin-svelte-svg"; // https://vitejs.dev/config/ export default defineConfig({ + envPrefix: ["VITE_", "NG_"], plugins: [svelte({ preprocess: [ vitePreprocess(), diff --git a/ngd/src/cli.rs b/ngd/src/cli.rs index 4edb285b..b5470606 100644 --- a/ngd/src/cli.rs +++ b/ngd/src/cli.rs @@ -112,6 +112,14 @@ pub(crate) struct Cli { #[arg(long, conflicts_with("registration_off"))] 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 #[arg(long)] pub save_config: bool, diff --git a/ngd/src/main.rs b/ngd/src/main.rs index 97f75d86..d70f032d 100644 --- a/ngd/src/main.rs +++ b/ngd/src/main.rs @@ -916,10 +916,24 @@ async fn main_inner() -> Result<(), ()> { 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 { listeners, overlays_configs: vec![overlays_config], registration, + admin_user, })); if args.print_config { @@ -972,7 +986,15 @@ async fn main_inner() -> Result<(), ()> { match config.unwrap() { 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? } } diff --git a/p2p-broker/src/broker_store/mod.rs b/p2p-broker/src/broker_store/mod.rs index 8fcb1134..f4290998 100644 --- a/p2p-broker/src/broker_store/mod.rs +++ b/p2p-broker/src/broker_store/mod.rs @@ -11,3 +11,5 @@ pub mod repostoreinfo; pub mod topic; pub mod invitation; + +pub mod wallet; diff --git a/p2p-broker/src/broker_store/wallet.rs b/p2p-broker/src/broker_store/wallet.rs new file mode 100644 index 00000000..a7b6e2b4 --- /dev/null +++ b/p2p-broker/src/broker_store/wallet.rs @@ -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()) + } +} diff --git a/p2p-broker/src/server_ws.rs b/p2p-broker/src/server_ws.rs index 328f020c..475f4752 100644 --- a/p2p-broker/src/server_ws.rs +++ b/p2p-broker/src/server_ws.rs @@ -39,7 +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_net::NG_BOOTSTRAP_LOCAL_PATH; use p2p_repo::log::*; use p2p_repo::types::SymKey; use p2p_repo::types::{PrivKey, PubKey}; @@ -255,7 +255,7 @@ fn upgrade_ws_or_serve_app( .body(Some(file.data.to_vec())) .unwrap(); return Err(res); - } else if uri == NG_BOOTSTRAP_LOCAL_URL { + } else if uri == NG_BOOTSTRAP_LOCAL_PATH { log_debug!("Serving bootstrap"); let mut builder = Response::builder().status(StatusCode::OK); @@ -609,6 +609,7 @@ pub async fn run_server_v0( wallet_master_key: SymKey, config: DaemonConfigV0, mut path: PathBuf, + admin_invite: bool, ) -> Result<(), ()> { // check config @@ -754,8 +755,8 @@ pub async fn run_server_v0( 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"); } - - let bootstrap = BootstrapContent::V0(BootstrapContentV0 { servers }); + let bootstrap_v0 = BootstrapContentV0 { servers }; + let bootstrap = BootstrapContent::V0(bootstrap_v0.clone()); 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. @@ -766,7 +767,16 @@ pub async fn run_server_v0( std::fs::create_dir_all(path.clone()).unwrap(); // opening the server storage (that contains the encryption keys for each store/overlay ) - let broker_storage = LmdbBrokerStorage::open(&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; broker.set_my_peer_id(peer_id); diff --git a/p2p-broker/src/storage.rs b/p2p-broker/src/storage.rs index 38649c44..b7878fd3 100644 --- a/p2p-broker/src/storage.rs +++ b/p2p-broker/src/storage.rs @@ -11,24 +11,85 @@ use std::path::PathBuf; +use crate::broker_store::invitation::Invitation; +use crate::broker_store::wallet::Wallet; use crate::types::*; use p2p_net::broker_storage::*; +use p2p_net::types::{BootstrapContentV0, InvitationCode, InvitationV0}; use p2p_repo::kcv_store::KCVStore; +use p2p_repo::log::*; +use p2p_repo::store::StorageError; use p2p_repo::types::SymKey; use stores_lmdb::kcv_store::LmdbKCVStore; use stores_lmdb::repo_store::LmdbRepoStore; pub struct LmdbBrokerStorage { wallet_storage: LmdbKCVStore, + accounts_storage: LmdbKCVStore, + peers_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 } + pub fn open( + path: &mut PathBuf, + master_key: SymKey, + admin_invite: Option<BootstrapContentV0>, + ) -> Result<Self, StorageError> { + // create/open the WALLET + + let mut wallet_path = path.clone(); + wallet_path.push("wallet"); + std::fs::create_dir_all(wallet_path.clone()).unwrap(); + //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, + }) } } diff --git a/p2p-broker/src/types.rs b/p2p-broker/src/types.rs index d2e70da0..4a5274fb 100644 --- a/p2p-broker/src/types.rs +++ b/p2p-broker/src/types.rs @@ -7,7 +7,7 @@ // notice may not be copied, modified, or distributed except // according to those terms. use p2p_net::types::{BrokerOverlayConfigV0, ListenerV0}; -use p2p_repo::types::PrivKey; +use p2p_repo::types::{PrivKey, PubKey}; use serde::{Deserialize, Serialize}; /// Registration config @@ -27,6 +27,8 @@ pub struct DaemonConfigV0 { pub overlays_configs: Vec<BrokerOverlayConfigV0>, pub registration: RegistrationConfig, + + pub admin_user: Option<PubKey>, } /// Daemon config diff --git a/p2p-client-ws/src/remote_ws.rs b/p2p-client-ws/src/remote_ws.rs index cb8a7c45..f3f1e587 100644 --- a/p2p-client-ws/src/remote_ws.rs +++ b/p2p-client-ws/src/remote_ws.rs @@ -337,6 +337,7 @@ mod test { user_priv, client, client_priv, + info: ClientInfo::new(ClientType::Cli, "".into(), "".into()), }), ) .await; diff --git a/p2p-client-ws/src/remote_ws_wasm.rs b/p2p-client-ws/src/remote_ws_wasm.rs index f865dd15..d371387d 100644 --- a/p2p-client-ws/src/remote_ws_wasm.rs +++ b/p2p-client-ws/src/remote_ws_wasm.rs @@ -43,6 +43,7 @@ impl IConnect for ConnectionWebSocket { remote_peer: DirectPeerId, config: StartConfig, ) -> Result<ConnectionBase, NetError> { + log_debug!("url {}", url); let mut cnx = ConnectionBase::new(ConnectionDir::Client, TransportProtocol::WS); let (mut ws, wsio) = WsMeta::connect(url, None).await.map_err(|e| { diff --git a/p2p-net/src/broker.rs b/p2p-net/src/broker.rs index b9069a0b..3678c85b 100644 --- a/p2p-net/src/broker.rs +++ b/p2p-net/src/broker.rs @@ -53,7 +53,6 @@ pub struct BrokerPeerInfo { #[derive(Debug)] pub struct DirectConnection { ip: IP, - interface: String, remote_peer_id: X25519PrivKey, tp: TransportProtocol, //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 struct Broker { +pub struct Broker<'a> { direct_connections: HashMap<IP, DirectConnection>, /// tuple of optional userId and peer key in montgomery form. userId is always None on the server side. peers: HashMap<(Option<PubKey>, X25519PubKey), BrokerPeerInfo>, @@ -76,13 +75,13 @@ pub struct Broker { shutdown_sender: Sender<ProtocolError>, closing: bool, my_peer_id: Option<PubKey>, - storage: Option<Box<dyn BrokerStorage + Send + Sync>>, + storage: Option<Box<dyn BrokerStorage + Send + Sync + 'a>>, test: u32, 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 /// only used in Tauri, not used in the JS SDK 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)); } @@ -152,7 +151,7 @@ impl Broker { } } Authorization::ExtMessage => Err(ProtocolError::AccessDenied), - Authorization::Client(_) => Err(ProtocolError::AccessDenied), + Authorization::Client(user) => Err(ProtocolError::AccessDenied), Authorization::Core => Err(ProtocolError::AccessDenied), Authorization::Admin(_) => Err(ProtocolError::AccessDenied), Authorization::OverlayJoin(_) => Err(ProtocolError::AccessDenied), @@ -401,6 +400,7 @@ impl Broker { let _ = self.shutdown_sender.send(ProtocolError::Closing).await; } + #[cfg(not(target_arch = "wasm32"))] pub async fn accept( &mut self, mut connection: ConnectionBase, @@ -460,33 +460,69 @@ impl Broker { Ok(()) } - pub async fn attach_peer_id( + #[cfg(not(target_arch = "wasm32"))] + pub async fn attach_and_authorize_peer_id( &mut self, remote_bind_address: BindAddress, local_bind_address: BindAddress, remote_peer_id: X25519PrivKey, - core: Option<String>, - ) -> Result<(), NetError> { + // if client is None it means we are Core mode + client: Option<ClientAuthContentV0>, + ) -> Result<(), ProtocolError> { 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 .anonymous_connections .remove(&(local_bind_address, remote_bind_address)) - .ok_or(NetError::InternalError)?; + .ok_or(ProtocolError::BrokerError)?; connection.reset_shutdown(remote_peer_id).await; 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 { ip, - interface: core.clone().unwrap(), remote_peer_id, tp: connection.transport_protocol(), cnx: connection, }; self.direct_connections.insert(ip, dc); PeerConnection::Core(ip) - } else { - PeerConnection::Client(connection) }; let bpi = BrokerPeerInfo { lastPeerAdvert: None, @@ -521,29 +557,39 @@ impl Broker { return Err(NetError::Closing); } - // TODO check that not already connected to peer - // IpAddr::from_str("127.0.0.1"); - 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 .open( config.get_url(), peer_privk.clone(), peer_pubk, - remote_peer_id, + remote_peer_id_dh, config.clone(), ) .await?; let join = connection.take_shutdown(); - let remote_peer_id_dh = remote_peer_id.to_dh_slice(); + let connected = match &config { StartConfig::Core(config) => { let ip = config.addr.ip.clone(); let dc = DirectConnection { ip, - interface: config.interface.clone(), - remote_peer_id: remote_peer_id_dh, + remote_peer_id: *remote_peer_id_dh.slice(), tp: connection.transport_protocol(), cnx: connection, }; @@ -560,7 +606,7 @@ impl Broker { }; 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( mut join: Receiver<Either<NetError, X25519PrivKey>>, @@ -602,7 +648,7 @@ impl Broker { cnx, peer_privk, peer_pubk, - remote_peer_id_dh, + *remote_peer_id_dh.slice(), config, )); Ok(()) diff --git a/p2p-net/src/connection.rs b/p2p-net/src/connection.rs index 2be5a147..40c20313 100644 --- a/p2p-net/src/connection.rs +++ b/p2p-net/src/connection.rs @@ -93,7 +93,7 @@ pub enum FSMstate { Start, Probe, Relay, - Noise0, + Noise0, // unused Noise1, Noise2, Noise3, // unused @@ -150,6 +150,7 @@ pub struct ClientConfig { pub user_priv: PrivKey, pub client: PubKey, pub client_priv: PrivKey, + pub info: ClientInfo, } #[derive(PartialEq, Debug, Clone)] @@ -373,7 +374,7 @@ impl NoiseFSM { self.local.take().unwrap().to_dh(), )), None, - Some(*self.remote.unwrap().to_dh_from_ed().slice()), + Some(*self.remote.unwrap().slice()), None, ); @@ -538,20 +539,7 @@ impl NoiseFSM { return Err(ProtocolError::NoiseHandshakeFailed); } let peer_id = handshake.get_rs().unwrap(); - //self.remote = Some(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)?; + self.remote = Some(PubKey::X25519PubKey(peer_id)); let ciphers = handshake.get_ciphers(); self.noise_cipher_state_enc = Some(ciphers.1); @@ -586,11 +574,13 @@ impl NoiseFSM { if let StartConfig::Client(client_config) = self.config.as_ref().unwrap() { + let ClientInfo::V0(info) = &client_config.info; let content = ClientAuthContentV0 { user: client_config.user, client: client_config.client, /// Nonce from ServerHello nonce: hello.nonce().clone(), + info: info.clone(), }; let ser = serde_bare::to_vec(&content)?; 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 self.dir.is_server() { if let ProtocolMessage::ClientAuth(client_auth) = msg { @@ -625,8 +617,20 @@ impl NoiseFSM { if verif.is_err() { result = verif.unwrap_err().into(); } else { - - // TODO check that the device has been registered for this user. if not, set result = AccessDenied + let (local_bind_address, remote_bind_address) = + 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 { result: result.clone() as u16, diff --git a/p2p-net/src/errors.rs b/p2p-net/src/errors.rs index f3656b88..50b68c28 100644 --- a/p2p-net/src/errors.rs +++ b/p2p-net/src/errors.rs @@ -31,6 +31,7 @@ pub enum NetError { ProtocolError, AccessDenied, InternalError, + PeerAlreadyConnected, Closing, } //MAX 50 NetErrors diff --git a/p2p-net/src/lib.rs b/p2p-net/src/lib.rs index 83a0b0de..d9d72f79 100644 --- a/p2p-net/src/lib.rs +++ b/p2p-net/src/lib.rs @@ -33,7 +33,7 @@ pub mod tests; pub mod site; -pub static NG_BOOTSTRAP_LOCAL_URL: &str = "/.ng_bootstrap"; +pub static NG_BOOTSTRAP_LOCAL_PATH: &str = "/.ng_bootstrap"; #[cfg(debug_assertions)] pub static WS_PORT: u16 = 14400; diff --git a/p2p-net/src/types.rs b/p2p-net/src/types.rs index 83a8cb0e..52545a03 100644 --- a/p2p-net/src/types.rs +++ b/p2p-net/src/types.rs @@ -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 { BrokerServerTypeV0::Localhost(_) => 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 #[derive(Clone, Debug, Serialize, Deserialize)] pub struct CreateAccountBSPV0 { - pub invitation_code: Option<SymKey>, + pub invitation: Option<InvitationV0>, + + pub additional_bootstrap: Option<BootstrapContentV0>, /// the user asking to create an account pub user: PubKey, - /// signature over serialized invitation, with user key - pub sig: Sig, + /// signature over serialized invitation code, with user key + // 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>, } @@ -854,7 +866,6 @@ pub struct ListenerV0 { pub accept_forward_for: AcceptForwardForV0, // impl fn is_private() // 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 } @@ -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> { let mut res: Vec<BrokerServerTypeV0> = vec![]; match self.accept_forward_for { @@ -2910,6 +2945,8 @@ pub struct ClientAuthContentV0 { /// Client pub key pub client: PubKey, + pub info: ClientInfoV0, + /// Nonce from ServerHello #[serde(with = "serde_bytes")] pub nonce: Vec<u8>, diff --git a/p2p-net/src/utils.rs b/p2p-net/src/utils.rs index 8543f325..088d20e8 100644 --- a/p2p-net/src/utils.rs +++ b/p2p-net/src/utils.rs @@ -11,7 +11,7 @@ use crate::types::BootstrapContent; use crate::types::Invitation; -use crate::NG_BOOTSTRAP_LOCAL_URL; +use crate::NG_BOOTSTRAP_LOCAL_PATH; use async_std::task; use ed25519_dalek::*; use futures::{channel::mpsc, select, Future, FutureExt, SinkExt}; @@ -61,6 +61,7 @@ const APP_PREFIX: &str = ""; pub async fn retrieve_local_bootstrap( location_string: String, invite_string: Option<String>, + must_be_public: bool, ) -> Option<Invitation> { let invite1: Option<Invitation> = if invite_string.is_some() { 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); 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() { let resp = resp.unwrap().json::<BootstrapContent>().await; resp.ok().map(|v| v.into()) @@ -91,10 +92,12 @@ pub async fn retrieve_local_bootstrap( 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() + if must_be_public && server.is_public_server() + || !must_be_public + && server + .get_ws_url(Some(location_string.clone())) + .await + .is_some() { return res; } diff --git a/p2p-repo/src/types.rs b/p2p-repo/src/types.rs index b78d6ad0..6bcc87a0 100644 --- a/p2p-repo/src/types.rs +++ b/p2p-repo/src/types.rs @@ -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 pub type Ed25519PubKey = [u8; 32];