A NextGraph Wallet is unique to each person. It stores your
@@ -526,10 +597,340 @@
Ok, I create my wallet now !
+ {:else if !invitation}
+
+
+ NextGraph is based on an efficient decentralized P2P network, and in
+ order to join this network and start using the app, you need to first
+ select a broker server.
+
+
+
+
+
+ What is a broker? Please read
+
+
+
+
+
+ The broker helps you keep all your data in sync, as it is
+ connected to the internet 24/7 and keeps a copy of the updates for
+ you. This way, even if the devices of the other participants are
+ offline, you can still see their changes
+
+
+
+
+ All your data is secure and end-to-end encrypted, and the
+ broker cannot see the content of the documents as it does not have
+ the keys to decrypt them.
+
+
+
+
+ The broker helps you enforce your privacy as it hides your internet
+ address (IP) from other users you share documents with.
+
+
+
+
+
+ It will be possible in the future to use NextGraph without any
+ broker and to have direct connections between peers, but this will
+ imply a less smooth experience.
+
+
+
+
+ At anytime you can decide to switch to another broker service
+ provider or host it yourself. Your data is totally portable
+ and can freely move to another broker.
+
+
+
+
+
+ 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.
+
+
+
+
+
+ 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 their dedicated
+ hardware.
+
+
+
Please choose one broker among the list
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {#if mobile}
+
+
+
+ {/if}
+
+
+
+
+
+
{:else if pin.length < 4}
+ {#if registration_success}
+
+ You have been successfully registered to {registration_success}
+
+ {/if}
- Let's start by choosing a PIN code
+ Let's start creating your wallet by choosing a PIN code
We recommend you to choose a PIN code that you already know very well.
@@ -779,7 +1180,7 @@
here. {#if !tauri_platform}By selecting this option, you agree to save
some cookies on your browser.{/if} Save your wallet on this device?Save my wallet on this device?
@@ -794,7 +1195,7 @@
>.
Save your wallet in the cloud?Save my wallet in the cloud?
@@ -855,7 +1256,7 @@
{:else if !error}
{#if !ready}
- We are creating your wallet...
+ Your wallet is being created...
- {:else if !invitation}
-
-
- NextGraph is based on an efficient decentralized P2P network, and in
- order to join this network and start using the app, you need to first
- select a broker server.
-
-
-
-
-
- What is a broker? Please read
-
-
-
-
-
- The broker helps you keep all your data in sync, as it is
- connected to the internet 24/7 and keeps a copy of the updates
- for you. This way, even if the devices of the other participants
- are offline, you can still see their changes
-
-
-
-
- All your data is secure and end-to-end encrypted, and the
- broker cannot see the content of the documents as it does not
- have the keys to decrypt them.
-
-
-
-
- The broker helps you enforce your privacy as it hides your
- internet address (IP) from other users you share documents with.
-
-
-
-
-
- It will be possible in the future to use NextGraph without any
- broker and to have direct connections between peers, but this
- will imply a less smooth experience.
-
-
-
-
- At anytime you can decide to switch to another broker service
- provider or host it yourself. Your data is totally portable
- and can freely move to another broker.
-
-
-
-
-
- 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.
-
-
-
-
-
- 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 their
- dedicated hardware.
-
-
-
Please choose one broker among the list
-
-
-
-
-
-
-
-
-
-
-
-
-
- {#if mobile}
-
-
-
- {/if}
-
-
-
-
-
-
{:else}
Your wallet is ready!
diff --git a/ng-sdk-js/src/lib.rs b/ng-sdk-js/src/lib.rs
index 237152c..fcc9a01 100644
--- a/ng-sdk-js/src/lib.rs
+++ b/ng-sdk-js/src/lib.rs
@@ -76,6 +76,17 @@ pub async fn get_local_url(location: String) -> JsValue {
}
}
+#[cfg(target_arch = "wasm32")]
+#[wasm_bindgen]
+pub async fn get_ngone_url_of_invitation(invitation_string: String) -> JsValue {
+ let res = decode_invitation_string(invitation_string);
+ if res.is_some() {
+ serde_wasm_bindgen::to_value(&res.unwrap().get_urls()[0]).unwrap()
+ } else {
+ JsValue::FALSE
+ }
+}
+
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn get_local_bootstrap_with_public(location: String, invite: JsValue) -> JsValue {
@@ -308,11 +319,16 @@ pub async fn wallet_create_wallet(js_params: JsValue) -> Result(js_params)
.map_err(|_| "Deserialization error of args")?;
params.result_with_wallet_file = true;
+ let local_save = params.local_save;
let res = create_wallet_v0(params).await;
match res {
Ok(r) => {
- let session = save_wallet_locally(&r)?;
- Ok(serde_wasm_bindgen::to_value(&(r, session)).unwrap())
+ if local_save {
+ let session = save_wallet_locally(&r)?;
+ Ok(serde_wasm_bindgen::to_value(&(r, session)).unwrap())
+ } else {
+ Ok(serde_wasm_bindgen::to_value(&(r, false)).unwrap())
+ }
}
Err(e) => Err(e.to_string().into()),
}
@@ -329,6 +345,9 @@ pub fn test_create_wallet() -> JsValue {
9,
false,
false,
+ BootstrapContentV0::new(),
+ None,
+ None,
);
serde_wasm_bindgen::to_value(&r).unwrap()
}
diff --git a/ng-wallet/src/lib.rs b/ng-wallet/src/lib.rs
index 95cee2c..3752896 100644
--- a/ng-wallet/src/lib.rs
+++ b/ng-wallet/src/lib.rs
@@ -341,10 +341,8 @@ pub fn gen_shuffle_for_pin() -> Vec {
/// creates a Wallet from a pin, a security text and image (with option to send the bootstrap and wallet to nextgraph.one)
/// and returns the Wallet, the pazzle and the mnemonic
pub async fn create_wallet_v0(
- params: CreateWalletV0,
+ mut params: CreateWalletV0,
) -> Result {
- // TODO : use some automatically zeroed variable for the 2 first arguments, and for the returned values
-
let creating_pazzle = Instant::now();
// pazzle_length can only be 9, 12, or 15
@@ -481,7 +479,33 @@ pub async fn create_wallet_v0(
//Creating a new peerId for this Client and User
let peer = generate_keypair();
- let wallet_log = WalletLog::new_v0(create_op);
+ let mut wallet_log = WalletLog::new_v0(create_op);
+
+ // adding some more operations in the log
+
+ // pub core_bootstrap: BootstrapContentV0,
+ // #[zeroize(skip)]
+ // pub core_registration: Option<[u8; 32]>,
+ // #[zeroize(skip)]
+ // pub additional_bootstrap: Option,
+
+ wallet_log.add(WalletOperation::AddSiteCoreV0((
+ user,
+ params
+ .core_bootstrap
+ .get_first_peer_id()
+ .ok_or(NgWalletError::InvalidBootstrap)?,
+ params.core_registration,
+ )));
+
+ if let Some(additional) = ¶ms.additional_bootstrap {
+ params.core_bootstrap.merge(additional);
+ }
+
+ for server in ¶ms.core_bootstrap.servers {
+ wallet_log.add(WalletOperation::AddBrokerServerV0(server.clone()));
+ wallet_log.add(WalletOperation::AddSiteBootstrapV0((user, server.peer_id)));
+ }
let mut master_key = [0u8; 32];
getrandom::getrandom(&mut master_key).map_err(|e| NgWalletError::InternalError)?;
@@ -632,6 +656,9 @@ mod test {
9,
false,
false,
+ BootstrapContentV0::new(),
+ None,
+ None,
))
.await
.expect("create_wallet_v0");
diff --git a/ng-wallet/src/types.rs b/ng-wallet/src/types.rs
index 100ba3a..9307593 100644
--- a/ng-wallet/src/types.rs
+++ b/ng-wallet/src/types.rs
@@ -226,6 +226,11 @@ impl WalletLog {
pub fn new_v0(create_op: WalletOpCreateV0) -> Self {
WalletLog::V0(WalletLogV0::new(create_op))
}
+ pub fn add(&mut self, op: WalletOperation) {
+ match self {
+ Self::V0(v0) => v0.add(op),
+ }
+ }
}
impl WalletLogV0 {
@@ -289,10 +294,10 @@ impl WalletLogV0 {
}
}
WalletOperation::RemoveOverlayCoreOverrideV0(_) => {}
- WalletOperation::AddSiteCoreV0((site, core)) => {
+ WalletOperation::AddSiteCoreV0((site, core, registration)) => {
if self.is_first_and_not_deleted_afterwards(op, "RemoveSiteCoreV0") {
let _ = wallet.sites.get_mut(&site).and_then(|site| {
- site.cores.push(*core);
+ site.cores.push((*core, *registration));
None::
});
}
@@ -460,7 +465,7 @@ pub enum WalletOperation {
SetClientV0(ClientV0),
AddOverlayCoreOverrideV0((OverlayId, Vec)),
RemoveOverlayCoreOverrideV0(OverlayId),
- AddSiteCoreV0((PubKey, PubKey)),
+ AddSiteCoreV0((PubKey, PubKey, Option<[u8; 32]>)),
RemoveSiteCoreV0((PubKey, PubKey)),
AddSiteBootstrapV0((PubKey, PubKey)),
RemoveSiteBootstrapV0((PubKey, PubKey)),
@@ -763,6 +768,12 @@ pub struct CreateWalletV0 {
pub result_with_wallet_file: bool,
#[zeroize(skip)]
pub local_save: bool,
+ #[zeroize(skip)]
+ pub core_bootstrap: BootstrapContentV0,
+ #[zeroize(skip)]
+ pub core_registration: Option<[u8; 32]>,
+ #[zeroize(skip)]
+ pub additional_bootstrap: Option,
}
impl CreateWalletV0 {
@@ -773,6 +784,9 @@ impl CreateWalletV0 {
pazzle_length: u8,
send_bootstrap: bool,
send_wallet: bool,
+ core_bootstrap: BootstrapContentV0,
+ core_registration: Option<[u8; 32]>,
+ additional_bootstrap: Option,
) -> Self {
CreateWalletV0 {
result_with_wallet_file: false,
@@ -783,6 +797,9 @@ impl CreateWalletV0 {
pazzle_length,
send_bootstrap,
send_wallet,
+ core_bootstrap,
+ core_registration,
+ additional_bootstrap,
}
}
}
@@ -822,6 +839,7 @@ pub enum NgWalletError {
DecryptionError,
InvalidSignature,
NoCreateWalletPresent,
+ InvalidBootstrap,
}
impl fmt::Display for NgWalletError {
diff --git a/ngaccount/Cargo.toml b/ngaccount/Cargo.toml
index 27086ea..8115819 100644
--- a/ngaccount/Cargo.toml
+++ b/ngaccount/Cargo.toml
@@ -26,4 +26,5 @@ serde-big-array = "0.5.1"
base64-url = "2.0.0"
serde_json = "1.0.96"
bytes = "1.0"
-anyhow = "1.0.71"
\ No newline at end of file
+anyhow = "1.0.71"
+duration-str = "0.5.1"
\ No newline at end of file
diff --git a/ngaccount/src/main.rs b/ngaccount/src/main.rs
index 0e73823..2a8d935 100644
--- a/ngaccount/src/main.rs
+++ b/ngaccount/src/main.rs
@@ -11,8 +11,9 @@ extern crate anyhow;
mod types;
+use duration_str::parse;
use p2p_client_ws::remote_ws::ConnectionWebSocket;
-use p2p_net::actors::add_user::*;
+use p2p_net::actors::add_invitation::*;
use p2p_net::broker::BROKER;
use p2p_repo::store::StorageError;
use warp::reply::Response;
@@ -30,12 +31,12 @@ use std::{env, fs};
use crate::types::*;
use ng_wallet::types::*;
use p2p_net::types::{
- BindAddress, CreateAccountBSP, Invitation, InvitationV0, APP_ACCOUNT_REGISTERED_SUFFIX,
- APP_NG_ONE_URL, NG_ONE_URL,
+ AdminResponseContentV0, BindAddress, CreateAccountBSP, Invitation, InvitationCode,
+ InvitationV0, APP_ACCOUNT_REGISTERED_SUFFIX, APP_NG_ONE_URL, NG_ONE_URL,
};
use p2p_repo::log::*;
use p2p_repo::types::*;
-use p2p_repo::utils::{generate_keypair, sign, verify};
+use p2p_repo::utils::{generate_keypair, sign, timestamp_after, verify};
#[derive(RustEmbed)]
#[folder = "web/dist"]
@@ -60,6 +61,11 @@ impl Server {
// if needed, proceed with payment and verify it here. once validated, add the user
+ let duration = parse("5m").unwrap();
+ let expiry = timestamp_after(duration);
+ let symkey = SymKey::random();
+ let invite_code = InvitationCode::Unique(symkey.clone());
+
let local_peer_pubk = self.local_peer_key.to_pub();
let res = BROKER
.write()
@@ -75,43 +81,45 @@ impl Server {
port: self.port,
ip: (&self.ip).into(),
},
- AddUser::V0(AddUserV0 {
- user: cabsp.user(),
- is_admin: false,
+ AddInvitation::V0(AddInvitationV0 {
+ invite_code,
+ expiry,
+ memo: None,
+ tos_url: false,
}),
)
.await;
- let mut redirect_url = cabsp
+ let redirect_url = cabsp
.redirect_url()
.clone()
.unwrap_or(format!("{}{}", self.domain, APP_ACCOUNT_REGISTERED_SUFFIX));
- match &res {
+ match res {
Err(e) => {
log_err!("error while registering: {e} {:?}", cabsp);
- Err(Some(format!("{}?e={}", redirect_url, e)))
+ Err(Some(format!("{}?re={}", redirect_url, e)))
}
- Ok(_) => {
- log_info!("User added successfully {}", cabsp.user());
- let mut return_invitation = if cabsp.invitation().is_some() {
- InvitationV0 {
- bootstrap: cabsp.invitation().as_ref().unwrap().bootstrap.clone(),
- name: cabsp.invitation().as_ref().unwrap().name.clone(),
- code: None,
- url: None,
- }
- } else {
- InvitationV0::empty(Some(self.domain.clone()))
- };
- return_invitation.append_bootstraps(cabsp.additional_bootstrap());
+ Ok(AdminResponseContentV0::Invitation(Invitation::V0(mut invitation))) => {
+ log_info!("invitation created successfully {:?}", invitation);
+ invitation.name = Some(self.domain.clone());
Ok(format!(
- "{}?i={}&u={}",
+ "{}?i={}&rs={}",
redirect_url,
- Invitation::V0(return_invitation),
- cabsp.user()
+ Invitation::V0(invitation),
+ self.domain
))
}
+ _ => {
+ log_err!(
+ "error while registering: invalid response from add_invitation {:?}",
+ cabsp
+ );
+ Err(Some(format!(
+ "{}?re={}",
+ redirect_url, "add_invitation_failed"
+ )))
+ }
}
}
diff --git a/ngaccount/web/src/routes/Create.svelte b/ngaccount/web/src/routes/Create.svelte
index e85d03c..6f9c8b3 100644
--- a/ngaccount/web/src/routes/Create.svelte
+++ b/ngaccount/web/src/routes/Create.svelte
@@ -45,6 +45,7 @@
error = "We are redirecting you...";
go_back = false;
} else {
+ //console.log(result);
success(result);
}
} catch (e) {
diff --git a/ngcli/src/main.rs b/ngcli/src/main.rs
index 83c0a3a..7df0462 100644
--- a/ngcli/src/main.rs
+++ b/ngcli/src/main.rs
@@ -95,7 +95,7 @@ async fn main() -> Result<(), ProtocolError> {
.arg(arg!(
-v --verbose ... "Increase the logging output. once : info, twice : debug, 3 times : trace"
))
- .arg(arg!(-b --base [PATH] "Base path for client home folder containing all persistent files, config, and key")
+ .arg(arg!(-b --base "Base path for client home folder containing all persistent files, config, and key")
.required(false)
.value_parser(value_parser!(PathBuf))
.default_value(".ng"))
@@ -137,12 +137,12 @@ async fn main() -> Result<(), ProtocolError> {
.subcommand(
Command::new("add-user")
.about("add a user to the server, so it can connect to it")
- .arg(arg!([USER_ID] "userId of the user to add. should be a base64-url encoded serde serialization of its pubkey [u8; 32]").required(true))
+ .arg(arg!( "userId of the user to add. should be a base64-url encoded serde serialization of its pubkey [u8; 32]").required(true))
.arg(arg!(-a --admin "make this user admin as well").required(false)))
.subcommand(
Command::new("del-user")
.about("removes a user from the broker")
- .arg(arg!([USER_ID] "userId of the user to remove. should be a base64-url encoded serde serialization of its pubkey [u8; 32]").required(true)))
+ .arg(arg!( "userId of the user to remove. should be a base64-url encoded serde serialization of its pubkey [u8; 32]").required(true)))
.subcommand(
Command::new("list-users")
.about("list all users registered in the broker")
@@ -156,7 +156,8 @@ async fn main() -> Result<(), ProtocolError> {
.arg(arg!(-u --unique "this invitation can be used only once. this is the default").required(false).conflicts_with("admin"))
.arg(arg!(-f --forever "this invitation does not expire. it can be used forever (or until deleted by an admin). default if no EXPIRES provided").required(false))
.arg(arg!(-n --name "optional name of this broker that will be displayed to the user when registering: You have been invited to register an account at [NAME]").required(false))
- .arg(arg!(-m --memo "optional memo about this invitation that will be kept in the server. it will help you to remember who you invited and to manage the invitation").required(false)))
+ .arg(arg!(-m --memo "optional memo about this invitation that will be kept in the server. it will help you to remember who you invited and to manage the invitation").required(false))
+ .arg(arg!(--notos "the TOS have already been accepted by the user. No need to redirect to a page for TOS acceptance.").required(false)))
.subcommand(
Command::new("list-invitations")
.about("list all invitations")
@@ -521,6 +522,7 @@ async fn main() -> Result<(), ProtocolError> {
invite_code,
expiry,
memo: sub2_matches.get_one::("memo").map(|s| s.clone()),
+ tos_url: !sub2_matches.get_flag("notos"),
}),
)
.await;
diff --git a/p2p-net/src/actors/add_invitation.rs b/p2p-net/src/actors/add_invitation.rs
index 723aa8d..2bbcdf7 100644
--- a/p2p-net/src/actors/add_invitation.rs
+++ b/p2p-net/src/actors/add_invitation.rs
@@ -27,6 +27,7 @@ pub struct AddInvitationV0 {
pub invite_code: InvitationCode,
pub expiry: u32,
pub memo: Option,
+ pub tos_url: bool,
}
/// Add invitation
@@ -51,6 +52,11 @@ impl AddInvitation {
AddInvitation::V0(o) => &o.memo,
}
}
+ pub fn tos_url(&self) -> bool {
+ match self {
+ AddInvitation::V0(o) => o.tos_url,
+ }
+ }
pub fn get_actor(&self) -> Box {
Actor::::new_responder()
}
@@ -103,7 +109,11 @@ impl EActor for Actor<'_, AddInvitation, AdminResponse> {
broker.get_bootstrap()?.clone(),
Some(req.code().get_symkey()),
None,
- broker.get_registration_url().map(|s| s.clone()),
+ if req.tos_url() {
+ broker.get_registration_url().map(|s| s.clone())
+ } else {
+ None
+ },
));
let response: AdminResponseV0 = invitation.into();
fsm.lock().await.send(response.into()).await?;
diff --git a/p2p-net/src/types.rs b/p2p-net/src/types.rs
index cabb6c9..f94d1ae 100644
--- a/p2p-net/src/types.rs
+++ b/p2p-net/src/types.rs
@@ -145,7 +145,7 @@ pub struct SiteV0 {
// Identity::OrgPrivate or Identity::IndividualPrivate
pub private: SiteStore,
- pub cores: Vec,
+ pub cores: Vec<(PubKey, Option<[u8; 32]>)>,
pub bootstraps: Vec,
}
@@ -529,6 +529,25 @@ pub struct BootstrapContentV0 {
pub servers: Vec,
}
+impl BootstrapContentV0 {
+ pub fn new() -> Self {
+ BootstrapContentV0 { servers: vec![] }
+ }
+ pub fn merge(&mut self, with: &BootstrapContentV0) {
+ 'outer: for server2 in &with.servers {
+ for server1 in &self.servers {
+ if *server1 == *server2 {
+ continue 'outer;
+ }
+ }
+ self.servers.push(server2.clone());
+ }
+ }
+ pub fn get_first_peer_id(&self) -> Option {
+ self.servers.first().map(|s| s.peer_id)
+ }
+}
+
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum BootstrapContent {
V0(BootstrapContentV0),
@@ -679,6 +698,14 @@ impl Invitation {
}
}
+ pub fn set_url(&mut self, url: Option<&String>) {
+ if url.is_some() {
+ match self {
+ Invitation::V0(v0) => v0.url = Some(url.unwrap().clone()),
+ }
+ }
+ }
+
/// first URL in the list is the ngone one
pub fn get_urls(&self) -> Vec {
match self {
@@ -773,37 +800,36 @@ impl CreateAccountBSP {
}
Some(base64_url::encode(&payload_ser.unwrap()))
}
- pub fn user(&self) -> PubKey {
- match self {
- Self::V0(v0) => v0.user,
- }
- }
+ // pub fn user(&self) -> PubKey {
+ // match self {
+ // Self::V0(v0) => v0.user,
+ // }
+ // }
pub fn redirect_url(&self) -> &Option {
match self {
Self::V0(v0) => &v0.redirect_url,
}
}
- pub fn invitation(&self) -> &Option {
- match self {
- Self::V0(v0) => &v0.invitation,
- }
- }
- pub fn additional_bootstrap(&mut self) -> &mut Option {
- match self {
- Self::V0(v0) => &mut v0.additional_bootstrap,
- }
- }
+ // pub fn invitation(&self) -> &Option {
+ // match self {
+ // Self::V0(v0) => &v0.invitation,
+ // }
+ // }
+ // pub fn additional_bootstrap(&mut self) -> &mut Option {
+ // match self {
+ // Self::V0(v0) => &mut v0.additional_bootstrap,
+ // }
+ // }
}
/// Create an account at a Broker Service Provider (BSP). Version 0
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CreateAccountBSPV0 {
- pub invitation: Option,
-
- pub additional_bootstrap: Option,
+ //pub invitation: Option,
+ //pub additional_bootstrap: Option,
/// the user asking to create an account
- pub user: PubKey,
+ //pub user: PubKey,
/// signature over serialized invitation code, with user key
// pub sig: Sig,
diff --git a/p2p-net/src/utils.rs b/p2p-net/src/utils.rs
index 5642deb..0575b6d 100644
--- a/p2p-net/src/utils.rs
+++ b/p2p-net/src/utils.rs
@@ -9,6 +9,7 @@
* according to those terms.
*/
+use crate::broker::BROKER;
use crate::types::*;
use crate::NG_BOOTSTRAP_LOCAL_PATH;
use async_std::task;
@@ -150,10 +151,13 @@ pub async fn retrieve_local_bootstrap(
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())
- } else {
- None
+ if resp.is_ok() {
+ let mut inv: Invitation = resp.unwrap().into();
+ inv.set_url(BROKER.read().await.get_registration_url());
+ return Some(inv);
+ }
}
+ None
};
let res = if invite1.is_none() {