From 0c98b2bdc6eccc0052ca35030e1e64d281093973 Mon Sep 17 00:00:00 2001 From: Niko PLP Date: Wed, 5 Jul 2023 19:47:49 +0300 Subject: [PATCH] invite_admin --- Cargo.lock | 2 +- ng-app/src-tauri/src/lib.rs | 8 + ng-app/src/api.ts | 10 +- ng-app/src/routes/WalletCreate.svelte | 277 +++++++++++++------------ ng-sdk-js/README.md | 3 +- ng-sdk-js/index.html | 2 +- ng-sdk-js/js/browser.js | 5 +- ng-sdk-js/src/lib.rs | 48 +++-- ngaccount/Cargo.toml | 4 +- ngaccount/src/main.rs | 76 +++++-- ngaccount/src/types.rs | 8 +- ngaccount/web/package.json | 6 +- ngaccount/web/pnpm-lock.yaml | 48 +++++ ngaccount/web/src/App.svelte | 4 + ngaccount/web/src/routes/Create.svelte | 265 +++++++++++++++++++++++ ngaccount/web/src/routes/Delete.svelte | 186 +++++++++++++++++ ngaccount/web/vite.config.js | 1 + ngd/src/cli.rs | 8 + ngd/src/main.rs | 24 ++- p2p-broker/src/broker_store/mod.rs | 2 + p2p-broker/src/broker_store/wallet.rs | 101 +++++++++ p2p-broker/src/server_ws.rs | 20 +- p2p-broker/src/storage.rs | 73 ++++++- p2p-broker/src/types.rs | 4 +- p2p-client-ws/src/remote_ws.rs | 1 + p2p-client-ws/src/remote_ws_wasm.rs | 1 + p2p-net/src/broker.rs | 92 ++++++-- p2p-net/src/connection.rs | 42 ++-- p2p-net/src/errors.rs | 1 + p2p-net/src/lib.rs | 2 +- p2p-net/src/types.rs | 49 ++++- p2p-net/src/utils.rs | 15 +- p2p-repo/src/types.rs | 9 + 33 files changed, 1150 insertions(+), 247 deletions(-) create mode 100644 ngaccount/web/src/routes/Create.svelte create mode 100644 ngaccount/web/src/routes/Delete.svelte create mode 100644 p2p-broker/src/broker_store/wallet.rs diff --git a/Cargo.lock b/Cargo.lock index 928508d..5fc933c 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 d0f62d0..d409547 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 Result { + 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 9b2d0d4..3a2d939 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 34de688..ad21528 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) => {};
@@ -612,11 +659,12 @@ - Very soon we will offer you the opportunity to host your own - broker at home or office. 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.home + or office. 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.
  • @@ -637,10 +685,13 @@ - Large organizations and companies have the opportunity to host a - broker on-premise or in the cloud, as the software is open - source. Individuals can also self-host a broker on any VPS server - or at home.on-premise + or in the cloud, as the software is open source. + Individuals can also + self-host a broker on any VPS server or at home, on their dedicated + hardware.
  • @@ -648,75 +699,37 @@
    - {#if !tauri_platform} - - - - {:else} - - {/if} +
    - {#if !tauri_platform} - - - - {:else} - - {/if} + + + For the rest of the world +
    @@ -774,59 +787,55 @@
    {/if}
    - - - + + + Self-hosted broker +
    - - - + + + + NG Box (owned or invited) +
    {:else if pin.length < 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 - Terms and Conditions of our cloudTerms of Service of our cloud.
    Create a link to access your wallet easily?
    - 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 + Terms of Service of our cloud.
    Create a link to my wallet? { // test(); diff --git a/ng-sdk-js/js/browser.js b/ng-sdk-js/js/browser.js index c2a7500..e0edfa3 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 ee272f5..ca07e8d 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::(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 396d10e..d2aefb3 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 fd11021..b339571 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 ebea555..6404797 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 55f0957..7f95437 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 a71774a..8a2fcde 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 18fb37d..c104b44 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); diff --git a/ngaccount/web/src/routes/Create.svelte b/ngaccount/web/src/routes/Create.svelte new file mode 100644 index 0000000..de94a95 --- /dev/null +++ b/ngaccount/web/src/routes/Create.svelte @@ -0,0 +1,265 @@ + + + + +
    +
    +
    + {#if error} +
    +

    + An error occurred while registering on this broker :
    + {error} +

    +
    + {:else} + {#if ca} +
    +

    + You would like to choose {domain} as your Broker Service + Provider.
    Please read carefully the Terms of Service here below, + before you accept them. +

    +
    + {/if} +
    +
    +

    {domain} Terms of Service

    + +
      + {#if domain == "nextgraph.eu"} +
    • + + Our servers are located in Germany, and we comply with the GDPR + regulation. +
    • +
    • + + legal details about GDPR... TBD +
    • + {/if} +
    • + + 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. +
    • +
    • + + 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. +
    • +
    • + + + You can delete your account with us at any time by going to the + link https://account.{domain}/#/delete or by entering in your NextGraph application and selecting the menu, + then Account, then delete +
    • +
    • + + 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: https://nextgraph.org/donate + +
    • +
    +
    +
    + {#if ca} +
    + + +
    + {/if} + {/if} +
    diff --git a/ngaccount/web/src/routes/Delete.svelte b/ngaccount/web/src/routes/Delete.svelte new file mode 100644 index 0000000..8a2691d --- /dev/null +++ b/ngaccount/web/src/routes/Delete.svelte @@ -0,0 +1,186 @@ + + + + +
    +
    +
    + {#if error} +
    +

    + An error occurred while deleting your account on this broker :
    + {error} +

    +
    + {:else} +
    +

    + You want to delete your account at {domain}?
    Please read + carefully the details below before you do so. +

    +
    +
    +
    +

    Delete your account at {domain}

    + +
      +
    • + + 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. +
    • +
    • + + 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 https://nextgraph.one/#/account/register in order to choose a new broker. + +
    • +
    • + + + 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.
      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, + go here. After installing the app, you will have to go to the menu and + select "Sync all my documents now".
      +
    • +
    +
    +
    +
    + + +
    + {/if} +
    diff --git a/ngaccount/web/vite.config.js b/ngaccount/web/vite.config.js index cb3bff8..7798ab2 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 4edb285..b547060 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, + + /// 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 97f75d8..d70f032 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 8fcb113..f429099 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 0000000..a7b6e2b --- /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 +// +// or the MIT license , +// 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, + ) -> Result { + // 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 { + self.get_or_create_single_key(Self::PREFIX_USER, &to_vec(user)?) + } + + pub fn get_or_create_overlay_key(&self, overlay: &OverlayId) -> Result { + self.get_or_create_single_key(Self::PREFIX_USER, &to_vec(overlay)?) + } + + pub fn create_single_key(&self, prefix: u8, key: &Vec) -> Result { + 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) -> 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 { + self.create_single_key(Self::PREFIX, &Self::KEY_ACCOUNTS.to_vec()) + } + pub fn get_or_create_peers_key(&self) -> Result { + self.get_or_create_single_key(Self::PREFIX, &Self::KEY_PEERS.to_vec()) + } + pub fn get_or_create_accounts_key(&self) -> Result { + 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 328f020..475f475 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 38649c4..b7878fd 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, + ) -> Result { + // 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 d2e70da..4a5274f 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, pub registration: RegistrationConfig, + + pub admin_user: Option, } /// Daemon config diff --git a/p2p-client-ws/src/remote_ws.rs b/p2p-client-ws/src/remote_ws.rs index cb8a7c4..f3f1e58 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 f865dd1..d371387 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 { + 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 b9069a0..3678c85 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>> = Lazy::new(|| Arc::new(RwLock::new(Broker::new()))); -pub struct Broker { +pub struct Broker<'a> { direct_connections: HashMap, /// tuple of optional userId and peer key in montgomery form. userId is always None on the server side. peers: HashMap<(Option, X25519PubKey), BrokerPeerInfo>, @@ -76,13 +75,13 @@ pub struct Broker { shutdown_sender: Sender, closing: bool, my_peer_id: Option, - storage: Option>, + storage: Option>, test: u32, tauri_streams: HashMap>, } -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) { @@ -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, - ) -> Result<(), NetError> { + // if client is None it means we are Core mode + client: Option, + ) -> 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>, @@ -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 2be5a14..40c2031 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 f3656b8..50b68c2 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 83a0b0d..d9d72f7 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 83a8cb0..52545a0 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 for CreateAccountBSP { } } +impl CreateAccountBSP { + pub fn encode(&self) -> Option { + 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, + pub invitation: Option, + + pub additional_bootstrap: Option, /// 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, } @@ -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) -> Vec { let mut res: Vec = 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, diff --git a/p2p-net/src/utils.rs b/p2p-net/src/utils.rs index 8543f32..088d20e 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, + must_be_public: bool, ) -> Option { let invite1: Option = if invite_string.is_some() { let invitation: Result = 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 = { - 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::().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 b78d6ad..6bcc87a 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 { + 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];