improved onboarding, first start, README

master
Niko PLP 2 weeks ago
parent cf30aee425
commit e3e04ce1bd
  1. 5
      Cargo.lock
  2. 83
      DEV.md
  3. 6
      ng-app/src-tauri/src/lib.rs
  4. 2
      ng-app/src/api.ts
  5. 25
      ng-app/src/routes/AccountInfo.svelte
  6. 2
      ng-app/src/routes/WalletCreate.svelte
  7. 9
      ng-broker/src/rocksdb_server_storage.rs
  8. 19
      ng-broker/src/server_broker.rs
  9. 10
      ng-broker/src/server_storage/admin/account.rs
  10. 1
      ng-broker/src/server_storage/admin/invitation.rs
  11. 12
      ng-broker/src/server_ws.rs
  12. 38
      ng-broker/src/utils.rs
  13. 37
      ng-net/src/broker.rs
  14. 3
      ng-net/src/server_broker.rs
  15. 6
      ng-net/src/types.rs
  16. 3
      ng-sdk-js/README.md
  17. 7
      ng-sdk-js/src/lib.rs
  18. 2
      ngaccount/.env
  19. 18
      ngaccount/README.md
  20. 1
      ngd/Cargo.toml
  21. 36
      ngd/README.md
  22. 6
      ngd/src/cli.rs
  23. 272
      ngd/src/main.rs

5
Cargo.lock generated

@ -3429,8 +3429,8 @@ dependencies = [
[[package]]
name = "ng-rocksdb"
version = "0.21.0-ngpreview.6"
source = "git+https://git.nextgraph.org/NextGraph/rust-rocksdb.git?branch=master#75ee95166954e6ec67a229d5697b2319dd02efc6"
version = "0.21.0-ngpreview.7"
source = "git+https://git.nextgraph.org/NextGraph/rust-rocksdb.git?branch=master#a56d4082b0286c691d9004b34b9248f28a6f74f0"
dependencies = [
"bindgen",
"bzip2-sys",
@ -3595,6 +3595,7 @@ dependencies = [
"async-std",
"clap",
"env_logger",
"getrandom 0.2.10",
"lazy_static",
"log",
"ng-broker",

@ -4,9 +4,9 @@
- [Install Nodejs](https://nodejs.org/en/download/)
- [Install LLVM](https://rust-lang.github.io/rust-bindgen/requirements.html)
On openbsd, for LLVM you need to choose llvm-17.
On OpenBSD, for LLVM you need to choose llvm-17.
until this [PR](https://github.com/rustwasm/wasm-pack/pull/1271) is accepted, will have to install wasm-pack this way:
Until this [PR](https://github.com/rustwasm/wasm-pack/pull/1271) is accepted, will have to install wasm-pack this way:
```
cargo install wasm-pack --git https://github.com/rustwasm/wasm-pack.git --rev c2b663f25abe50631a236d57a8c6d7fd806413b2
@ -16,12 +16,71 @@ cargo install wasm-pack --git https://github.com/rustwasm/wasm-pack.git --rev c2
cargo install cargo-watch
// optionally, if you want a Rust REPL: cargo install evcxr_repl
git clone git@git.nextgraph.org:NextGraph/nextgraph-rs.git
// or if you don't have a git account: git clone https://git.nextgraph.org/NextGraph/nextgraph-rs.git
// or if you don't have a git account with us: git clone https://git.nextgraph.org/NextGraph/nextgraph-rs.git
cd nextgraph-rs
cargo build
npm install -g pnpm
cd ng-sdk-js
wasm-pack build --target bundler
npm install --no-save pkg
cd ../ng-app
pnpm install
pnpm webfilebuild
cd ..
```
For building the native apps, see the [ng-app/README](ng-app/README.md)
### First run
The current directory will be used to save all the config, keys and storage data.
If you prefer to change the base directory, use the argument `--base [PATH]` when using `ngd` and/or `ngcli`.
```
// runs the daemon in one terminal
cargo run -p ngd -- -vv --save-key -l 14400
```
If you are developing also the front-end, you should run it with this command in a separate terminal:
```
cd ng-app
pnpm webdev
```
In the logs/output of ngd, you will see an invitation link that you should open in your web browser. If there are many links, choose the one that starts with `http://localhost:`, and if you run a local front-end, replace the prefix `http://localhost:14400/` with `http://localhost:1421/` before you open the link in your browser.
The computer you use to open the link should have direct access to the ngd server on localhost. In most of the cases, it will work, as you are running ngd on localhost. If you are running ngd in a docker container, then you need to give access to the container to the local network of the host by using `docker run --network="host"`. see more here https://docs.docker.com/network/drivers/host/
Follow the steps on the screen to create your wallet :)
Once your ngd server will run in your dev env, replace the string in `nextgraph/src/local_broker_dev_env.rs` with the actual PEER ID of your ngd server that is displayed when you first start `ngd`, with a line starting with `INFO ngd] PeerId of node:`.
once your ngd server will run in your dev env, replace the above string in `nextgraph/src/local_broker_dev_env.rs` with the actual PEER ID of your ngd server.
### Using ngcli with the account you just created
The current directory will be used to save all the config, keys and storage data.
If you prefer to change the base directory, use the argument `--base [PATH]` when using `ngd` and/or `ngcli`.
`PEER_ID_OF_SERVER` is displayed when you first start `ngd`, with a line starting with `INFO ngd] PeerId of node:`.
`THE_PRIVATE_KEY_OF_THE_USER_YOU_JUST_CREATED` can be found in the app, after you opened your wallet, click on the logo of NextGraph, and you will see the User Panel. Click on `Accounts` and you will find the User Private Key.
By example, to list all the admin users :
```
cargo run -p ngcli -- --save-key --save-config -s 127.0.0.1,14400,<PEER_ID_OF_SERVER> -u <THE_PRIVATE_KEY_OF_THE_USER_YOU_JUST_CREATED> admin list-users -a
```
### Adding more accounts and wallets
In your dev env, if you want to create more wallets and accounts, you need to run a local instance of `ngaccount`.
See the [README of ngaccount here](ngaccount/README.md).
Then you need to stop your ngd and start it again with the additional option :
```
--registration-url="http://127.0.0.1:5173/#/create"
```
### Packages
@ -43,20 +102,6 @@ The crates are organized as follow :
- ngone : server for nextgraph.one. helps user bootstrap into the right app. Not useful to you. Published here for transparency
- ngaccount : server for nextgraph's Broker Service Provider account manager. Not useful to you. Published here for transparency
### Run
Build & run debug executables:
```
// runs the daemon
cargo run --bin ngd
// runs the client
cargo run --bin ngcli
```
For the apps, see the [README](ng-app/README.md)
### Test
Please test by following this order (as we need to generate some files locally)

@ -41,6 +41,11 @@ pub use mobile::*;
pub type SetupHook = Box<dyn FnOnce(&mut App) -> Result<(), Box<dyn std::error::Error>> + Send>;
#[tauri::command(rename_all = "snake_case")]
async fn privkey_to_string(privkey: PrivKey) -> Result<String, String> {
Ok(format!("{privkey}"))
}
#[tauri::command(rename_all = "snake_case")]
async fn locales() -> Result<Vec<String>, ()> {
Ok(get_locales()
@ -1025,6 +1030,7 @@ impl AppBuilder {
.invoke_handler(tauri::generate_handler![
test,
locales,
privkey_to_string,
wallet_gen_shuffle_for_pazzle_opening,
wallet_gen_shuffle_for_pin,
wallet_open_with_pazzle,

@ -12,7 +12,7 @@ import { Bowser } from "../../ng-sdk-js/js/bowser.js";
import {version} from '../package.json';
const mapping = {
"privkey_to_string": ["privkey"],
"wallet_gen_shuffle_for_pazzle_opening": ["pazzle_length"],
"wallet_gen_shuffle_for_pin": [],
"wallet_open_with_pazzle": ["wallet","pazzle","pin"],

@ -18,7 +18,7 @@
<script lang="ts">
import { link, push } from "svelte-spa-router";
import CenteredLayout from "../lib/CenteredLayout.svelte";
import { ArrowLeft, ServerStack } from "svelte-heros-v2";
import { ArrowLeft, ServerStack, Key } from "svelte-heros-v2";
import { onMount, tick } from "svelte";
import { Sidebar, SidebarGroup, SidebarWrapper } from "flowbite-svelte";
import { t } from "svelte-i18n";
@ -86,7 +86,7 @@
* site_type: Object { Individual: (2) [] } // Some key data as well
*/
$: walletSites = wallet_unlocked?.sites;
/** Type:
* client_type: "Web"
* details: '{"browser":{"name":"Firefox","version":"127.0","appVersion":"5.0 (X11)","arch":"Linux x86_64","vendor":"","ua":"Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0"},"os":{"name":"Linux"},"platform":{"type":"desktop"},"engine":{"name":"Gecko","version":"20100101","sdk":"0.1.0-preview.1"}}'
@ -168,6 +168,7 @@
<CenteredLayout>
<div class="container3" bind:this={top}>
<div class="row mb-20">
<Sidebar {nonActiveClass}>
<SidebarWrapper
divClass="bg-gray-60 overflow-y-auto py-4 px-3 rounded dark:bg-gray-800"
@ -202,6 +203,26 @@
</h3>
</li>
<li
class="flex items-center p-2 text-base font-normal text-gray-900 bg-white shadow-md rounded-lg border-b"
>
<div>
<Key />
</div>
<div
class="flex flex-col ml-3 items-start text-left overflow-auto"
>
<div>
<span class="text-gray-500">User Private Key (for ngcli)<br/></span>
<span class="break-all">{#if walletSites}
{#await ng.privkey_to_string(walletSites[personal_site_id].site_type.Individual[0]) then userprivkey}
{userprivkey}
{/await}
{/if}</span>
</div>
</div>
</li>
<!-- Device Details -->
<SidebarGroup ulClass="space-y-1">
<li

@ -195,7 +195,7 @@
invitation = await ng.decode_invitation(param.get("i"));
window.location.replace(window.location.href.split("?")[0]);
} else if (param.get("i")) {
invitation = await ng.get_local_bootstrap_with_public(
invitation = await ng.get_local_bootstrap(
location.href,
param.get("i")
);

@ -68,7 +68,7 @@ impl RocksDbServerStorage {
let accounts_storage =
RocksDbKCVStorage::open(&accounts_path, accounts_key.slice().clone())?;
let symkey = SymKey::random();
let invite_code = InvitationCode::Admin(symkey.clone());
let invite_code = InvitationCode::Setup(symkey.clone());
let _ = Invitation::create(
&invite_code,
0,
@ -77,7 +77,7 @@ impl RocksDbServerStorage {
)?;
let invitation = ng_net::types::Invitation::V0(InvitationV0 {
code: Some(symkey),
name: Some("your NG Box, as admin".into()),
name: Some("your Broker, as admin".into()),
url: None,
bootstrap: admin_invite.unwrap(),
});
@ -200,7 +200,10 @@ impl RocksDbServerStorage {
log_debug!("get_user {user_id}");
Ok(Account::open(&user_id, &self.accounts_storage)?.is_admin()?)
}
/// returns the crednetials, storage_master_key, and peer_priv_key
pub(crate) fn has_no_user(&self) -> Result<bool, ProtocolError> {
Ok(!Account::has_users(&self.accounts_storage)?)
}
/// returns the credentials, storage_master_key, and peer_priv_key
pub(crate) fn get_user_credentials(
&self,
user_id: &PubKey,

@ -161,10 +161,16 @@ pub struct ServerBroker {
state: RwLock<ServerBrokerState>,
path_users: PathBuf,
master_key: Option<SymKey>,
}
impl ServerBroker {
pub(crate) fn new(storage: RocksDbServerStorage, path_users: PathBuf) -> Self {
pub(crate) fn new(
storage: RocksDbServerStorage,
path_users: PathBuf,
master_key: Option<SymKey>,
) -> Self {
ServerBroker {
storage: storage,
state: RwLock::new(ServerBrokerState {
@ -177,7 +183,7 @@ impl ServerBroker {
wallet_exports: HashMap::new(),
wallet_exports_timestamp: BTreeMap::new(),
}),
master_key,
path_users,
}
}
@ -316,6 +322,12 @@ async fn wait_for_wallet(
//for now this cache is not implemented, but the structs are ready (see above), and it would just require to change slightly the implementation of the trait functions here below.
#[async_trait::async_trait]
impl IServerBroker for ServerBroker {
fn take_master_key(&mut self) -> Result<SymKey, ProtocolError> {
match self.master_key.take() {
None => Err(ProtocolError::AccessDenied),
Some(key) => Ok(key),
}
}
async fn remove_rendezvous(&self, rendezvous: &SymKey) {
let mut lock = self.state.write().await;
let _ = lock.wallet_rendezvous.remove(&rendezvous);
@ -440,6 +452,9 @@ impl IServerBroker for ServerBroker {
fn get_user(&self, user_id: PubKey) -> Result<bool, ProtocolError> {
self.storage.get_user(user_id)
}
fn has_no_user(&self) -> Result<bool, ProtocolError> {
self.storage.has_no_user()
}
fn add_user_credentials(
&self,
user_id: &PubKey,

@ -105,6 +105,16 @@ impl<'a> Account<'a> {
}
Ok(res)
}
pub fn has_users(storage: &'a dyn KCVStorage) -> Result<bool, StorageError> {
let size = to_vec(&UserId::nil())?.len();
let mut res: Vec<UserId> = vec![];
//TODO: fix this. we shouldn't have to fetch all the users to know if there is at least one user. highly inefficient. need to add a storage.has_one_key_value method
Ok(!storage
.get_all_keys_and_values(Self::PREFIX_ACCOUNT, size, vec![], None, &None)?
.is_empty())
}
pub fn exists(&self) -> bool {
self.storage
.get(

@ -60,6 +60,7 @@ impl<'a> Invitation<'a> {
InvitationCode::Unique(c) => (0u8, c.slice()),
InvitationCode::Multi(c) => (1u8, c.slice()),
InvitationCode::Admin(c) => (2u8, c.slice()),
InvitationCode::Setup(c) => (3u8, c.slice()),
};
let acc = Invitation {
id: code.clone(),

@ -786,7 +786,7 @@ pub async fn run_server_v0(
// opening the server storage (that contains the encryption keys for each store/overlay )
let server_storage = RocksDbServerStorage::open(
&mut path,
wallet_master_key,
wallet_master_key.clone(),
if admin_invite {
Some(bootstrap_v0.clone())
} else {
@ -797,7 +797,15 @@ pub async fn run_server_v0(
NgError::BrokerConfigError(format!("Error while opening server storage: {}", e))
})?;
let server_broker = ServerBroker::new(server_storage, path_users);
let server_broker = ServerBroker::new(
server_storage,
path_users,
if admin_invite {
Some(wallet_master_key)
} else {
None
},
);
let mut broker = BROKER.write().await;
broker.set_server_broker(server_broker);

@ -7,25 +7,25 @@
// notice may not be copied, modified, or distributed except
// according to those terms.
use ng_repo::log::*;
// use ng_repo::log::*;
pub fn gen_broker_keys(key: Option<[u8; 32]>) -> [[u8; 32]; 4] {
let key = match key {
None => {
let mut master_key = [0u8; 32];
log_warn!("gen_broker_keys: No key provided, generating one");
getrandom::getrandom(&mut master_key).expect("getrandom failed");
master_key
}
Some(k) => k,
};
let peerid: [u8; 32];
let wallet: [u8; 32];
let sig: [u8; 32];
// pub fn gen_broker_keys(key: Option<[u8; 32]>) -> [[u8; 32]; 4] {
// let key = match key {
// None => {
// let mut master_key = [0u8; 32];
// log_warn!("gen_broker_keys: No key provided, generating one");
// getrandom::getrandom(&mut master_key).expect("getrandom failed");
// master_key
// }
// Some(k) => k,
// };
// let peerid: [u8; 32];
// let wallet: [u8; 32];
// let sig: [u8; 32];
peerid = blake3::derive_key("NextGraph Broker BLAKE3 key PeerId privkey", &key);
wallet = blake3::derive_key("NextGraph Broker BLAKE3 key wallet encryption", &key);
sig = blake3::derive_key("NextGraph Broker BLAKE3 key config signature", &key);
// peerid = blake3::derive_key("NextGraph Broker BLAKE3 key PeerId privkey", &key);
// wallet = blake3::derive_key("NextGraph Broker BLAKE3 key wallet encryption", &key);
// sig = blake3::derive_key("NextGraph Broker BLAKE3 key config signature", &key);
[key, peerid, wallet, sig]
}
// [key, peerid, wallet, sig]
// }

@ -289,10 +289,30 @@ impl Broker {
if user_and_registration.1.is_some() {
// user wants to register
let lock = self.get_server_broker()?;
let storage = lock.read().await;
if storage.get_user(user_and_registration.0).is_ok() {
return Ok(());
{
let storage = lock.read().await;
if storage.get_user(user_and_registration.0).is_ok() {
return Ok(());
}
}
{
let mut storage = lock.write().await;
if storage.has_no_user()? {
let code = user_and_registration.1.unwrap().unwrap();
let inv_type = storage.get_invitation_type(code)?;
if inv_type == 3u8 {
// it is a setup invite
// TODO send (return here) master_key to client (so they can save it in their wallet)
let _master_key = storage.take_master_key()?;
// TODO save remote_boot (in server.path)
storage.add_user(user_and_registration.0, true)?;
storage.remove_invitation(code)?;
return Ok(());
}
return Err(ProtocolError::InvalidState);
}
}
let storage = lock.read().await;
if let Some(ServerConfig {
registration: reg, ..
}) = &self.config
@ -304,17 +324,10 @@ impl Broker {
if user_and_registration.1.unwrap().is_none() {
Err(ProtocolError::InvitationRequired)
} else {
let mut is_admin = false;
let code = user_and_registration.1.unwrap().unwrap();
let inv_type = storage.get_invitation_type(code)?;
if inv_type == 2u8 {
// admin
is_admin = true;
storage.remove_invitation(code)?;
} else if inv_type == 1u8 {
storage.remove_invitation(code)?;
}
storage.add_user(user_and_registration.0, is_admin)?;
storage.add_user(user_and_registration.0, inv_type == 2u8)?;
storage.remove_invitation(code)?;
Ok(())
}
}

@ -47,6 +47,7 @@ pub trait IServerBroker: Send + Sync {
fn get_block(&self, overlay_id: &OverlayId, block_id: &BlockId) -> Result<Block, ServerError>;
async fn create_user(&self, broker_id: &DirectPeerId) -> Result<UserId, ProtocolError>;
fn get_user(&self, user_id: PubKey) -> Result<bool, ProtocolError>;
fn has_no_user(&self) -> Result<bool, ProtocolError>;
fn get_user_credentials(&self, user_id: &PubKey) -> Result<Credentials, ProtocolError>;
fn add_user_credentials(
&self,
@ -70,7 +71,7 @@ pub trait IServerBroker: Send + Sync {
) -> Result<(), ProtocolError>;
fn get_invitation_type(&self, invite: [u8; 32]) -> Result<u8, ProtocolError>;
fn remove_invitation(&self, invite: [u8; 32]) -> Result<(), ProtocolError>;
fn take_master_key(&mut self) -> Result<SymKey, ProtocolError>;
async fn app_process_request(
&self,
req: AppRequest,

@ -308,7 +308,7 @@ impl BrokerServerV0 {
pub const APP_ACCOUNT_REGISTERED_SUFFIX: &str = "/#/user/registered";
#[doc(hidden)]
pub const NG_ONE_URL: &str = "https://nextgraph.one";
pub const NG_ONE_URL: &str = "https://nextgraph.net";
#[doc(hidden)]
pub const APP_NG_ONE_URL: &str = "https://app.nextgraph.one";
@ -700,12 +700,13 @@ pub enum InvitationCode {
Unique(SymKey),
Admin(SymKey),
Multi(SymKey),
Setup(SymKey),
}
impl InvitationCode {
pub fn get_symkey(&self) -> SymKey {
match self {
Self::Unique(s) | Self::Admin(s) | Self::Multi(s) => s.clone(),
Self::Unique(s) | Self::Admin(s) | Self::Multi(s) | Self::Setup(s) => s.clone(),
}
}
}
@ -716,6 +717,7 @@ impl fmt::Display for InvitationCode {
Self::Unique(k) => write!(f, "unique {}", k),
Self::Admin(k) => write!(f, "admin {}", k),
Self::Multi(k) => write!(f, "multi {}", k),
Self::Setup(k) => write!(f, "setup {}", k),
}
}
}

@ -43,7 +43,7 @@ Still, this API will always be available as it is used internally by the NextGra
## Headless server (runs the verifiers of the users on the server)
NextGraph daemon (ngd) is normally used only as a Broker of encrypted messages, but it can also be configured to run the verifiers of some or all of the users' data.
The verifier is the service that opens the encrypted data and "materialize" it. In local-first/CRDT terminology, this means that the many commits that form the DAG of operations, are reduced in order to obtain the current state of a document, that can then be read or edited locally by the user. Usually, the verifier runs locally in the native NextGraph app, and the materialized state is persisted locally (with encryption at rest). The web version of the app (available at https://app.nextgraph.one) is not persisting the materialized state yet, because the "UserStorage for Web" feature is not ready yet. Programmers can also run a local verifier with the wallet API in Rust or nodeJS (not documented), or use the CLI to create a local materialized state.
The verifier is the service that opens the encrypted data and "materialize" it. In local-first/CRDT terminology, this means that the many commits that form the DAG of operations, are reduced in order to obtain the current state of a document, that can then be read or edited locally by the user. Usually, the verifier runs locally in the native NextGraph app, and the materialized state is persisted locally (with encryption at rest). The web version of the app (available at https://nextgraph.net) is not persisting the materialized state yet, because the "UserStorage for Web" feature is not ready yet. Programmers can also run a local verifier with the wallet API in Rust or nodeJS (not documented), or use the CLI to create a local materialized state.
It is also possible to run a remote verifier on ngd, and the user has to give their credentials to the server (partially or fully) so the server can decrypt the data and process it. Obviously this breaks the end-to-end-encryption. But depending on the use-cases, it can be useful to have the verifier run on some server.
@ -192,7 +192,6 @@ Licensed under either of
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/assure) and the [NGI Zero Commons Fund](https://nlnet.nl/commonsfund/), both funds established by [NLnet](https://nlnet.nl/) Foundation with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of DG Communications Networks, Content and Technology under grant agreements No 957073 and No 101092990, respectively.
[license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg
[license-link]: https://git.nextgraph.org/NextGraph/nextgraph-rs/raw/branch/master/LICENSE-APACHE2
[license-image2]: https://img.shields.io/badge/license-MIT-blue.svg

@ -128,6 +128,13 @@ pub fn wallet_gen_shuffle_for_pin() -> Vec<u8> {
gen_shuffle_for_pin()
}
#[wasm_bindgen]
pub fn privkey_to_string(privkey: JsValue) -> Result<String, JsValue> {
let p = serde_wasm_bindgen::from_value::<PrivKey>(privkey)
.map_err(|_| "Deserialization error of privkey")?;
Ok(format!("{p}"))
}
#[wasm_bindgen]
pub fn wallet_open_with_pazzle(
wallet: JsValue,

@ -1,5 +1,5 @@
NG_ACCOUNT_DOMAIN=
NG_ACCOUNT_ADMIN=
NG_ACCOUNT_LOCAL_PEER_KEY=
NG_ACCOUNT_SERVER=127.0.0.1,1440,[the broker's peer ID]
NG_ACCOUNT_SERVER=127.0.0.1,14400,[the broker's peer ID]
RUST_LOG=

@ -12,21 +12,31 @@ pnpm --ignore-workspace install
## Dev
edit your `.env` file as follow
```
NG_ACCOUNT_DOMAIN=test.com
NG_ACCOUNT_ADMIN=[YOUR_USER_PRIV_KEY]
NG_ACCOUNT_LOCAL_PEER_KEY=kbz34OFqaWu59xYaqViP0esME2MmcroS94pc4lEEsEsA
NG_ACCOUNT_SERVER=127.0.0.1,14400,[YOUR_NGD_PEER_ID]
RUST_LOG=debug
```
`NG_ACCOUNT_LOCAL_PEER_KEY` is given as an example. You can generate a random one by using the command `ngcli gen-key` and use the private key.
```bash
cd web
pnpm run dev --host
# In another terminal...
cd ../
# In another terminal... in the folder ngaccount
# Please set the required environment variables in the .env and then source it it with:
source .env
cargo watch -c -w src -x run
# Then open http://localhost:5173/#/create
```
> Currently, the ng-account server api is listening on http://127.0.0.1:3031 only which might cause you trouble (coded in `main.rs`, `Create.svelte` and `Delete.svelte`).
> Currently, the ng-account server api is listening on http://127.0.0.1:3031 only which might cause you trouble with Android emulator (hardcoded in `main.rs`, `Create.svelte` and `Delete.svelte`).
> If you need to test from a (virtual) android device, you can use adb to tunnel the connection like: [`adb reverse tcp:3031 tcp:3031`](https://justinchips.medium.com/proxying-adb-client-connections-2ab495f774eb).
## Prod

@ -22,6 +22,7 @@ serde_json = "1.0"
async-std = { version = "1.12.0", features = ["attributes"] }
zeroize = { version = "1.7.0" }
addr = "0.15.6"
getrandom = "0.2.7"
regex = "1.8.4"
lazy_static = "1.4.0"
log = "0.4"

@ -36,13 +36,13 @@ See [Build release binaries](../DEV.md#build-release-binaries) in the main READM
## Usage
### For a localhost server: The first start will create an invitation for the admin, so you can create your wallet
### The first start of ngd will create an invitation for the admin, so you can create your wallet
```
ngd --save-key -l 1440 --invite-admin --save-config
ngd --save-key -l 1440 --save-config
```
this will give you a link that you should open in your web browser. If there are many links, choose the one that starts with `http://localhost:`.
In the logs/output, you will see a link that you should open in your web browser. If there are many links, choose the one that starts with `http://localhost:`.
The computer you use to open the link should have direct access to the ngd server on localhost. In most of the cases, it will work, as you are running ngd on localhost. If you are running ngd in a docker container, then you need to give access to the container to the local network of the host by using `docker run --network="host"`. see more here https://docs.docker.com/network/drivers/host/
@ -54,38 +54,19 @@ for the next start of ngd :
ngd
```
### For a server behind a domain: create the first admin user
### Using ngcli with the account you just created
The current directory will be used to save all the config, keys and storage data.
If you prefer to change the base directory, use the argument `--base [PATH]` when using `ngd` and/or `ngcli`.
```
ngcli gen-key
ngd -v --save-key -l 1440 -d <SERVER_DOMAIN> --admin <THE_USER_ID_YOU_JUST_CREATED>
// note the server peerID from the logs
```
in another terminal:
```
ngcli --save-key -s 127.0.0.1,1440,<PEER_ID_OF_SERVER> -u <THE_PRIVATE_KEY_OF_THE_USER_YOU_JUST_CREATED> admin add-user <THE_USER_ID_YOU_JUST_CREATED> -a
```
`PEER_ID_OF_SERVER` is displayed when you first start `ngd`, with a line starting with `INFO ngd] PeerId of node:`.
you should see a message `User added successfully`.
`THE_PRIVATE_KEY_OF_THE_USER_YOU_JUST_CREATED` can be found in the app, after you opened your wallet, click on the logo of NextGraph, and you will see the User Panel. Click on `Accounts` and you will find the User Private Key.
to check that the admin user has been created :
By example, to list all the admin users :
```
ngcli --save-key -s 127.0.0.1,1440,<PEER_ID_OF_SERVER> -u <THE_PRIVATE_KEY_OF_THE_USER_YOU_JUST_CREATED> admin list-users -a
```
should return your userId
you can now save the configs of both the server and client
```
ngd -l 1440 -d <SERVER_DOMAIN> --save-config
ngcli -s 127.0.0.1,1440,<PEER_ID_OF_SERVER> -u <THE_PRIVATE_KEY_OF_THE_USER_YOU_JUST_CREATED> --save-config
ngcli --save-key --save-config -s 127.0.0.1,1440,<PEER_ID_OF_SERVER> -u <THE_PRIVATE_KEY_OF_THE_USER_YOU_JUST_CREATED> admin list-users -a
```
## License
@ -108,7 +89,6 @@ additional terms or conditions.
NextGraph received funding through the [NGI Assure Fund](https://nlnet.nl/assure) and the [NGI Zero Commons Fund](https://nlnet.nl/commonsfund/), both funds established by [NLnet](https://nlnet.nl/) Foundation with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of DG Communications Networks, Content and Technology under grant agreements No 957073 and No 101092990, respectively.
[rustc-image]: https://img.shields.io/badge/rustc-1.74+-blue.svg
[license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg
[license-link]: https://git.nextgraph.org/NextGraph/nextgraph-rs/raw/branch/master/LICENSE-APACHE2

@ -30,7 +30,7 @@ pub(crate) struct Cli {
#[arg(short, long, env = "NG_SERVER_KEY")]
pub key: Option<String>,
/// Saves to disk the provided or automatically generated key. Only use if file storage is secure. Alternatives are passing the key at every start with --key or NG_SERVER_KEY env var.
/// Saves to disk the provided or automatically generated key. Only use for development purpose. Alternatives are passing the key at every start with --key or NG_SERVER_KEY env var.
#[arg(long)]
pub save_key: bool,
@ -120,8 +120,8 @@ pub(crate) struct Cli {
pub admin: Option<String>,
/// Admin invitation
#[arg(long, conflicts_with("admin"))]
pub invite_admin: bool,
// #[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)]

@ -32,7 +32,7 @@ use ng_repo::log::*;
use ng_repo::types::{Sig, SymKey};
use ng_repo::utils::ed_keypair_from_priv_bytes;
use ng_repo::{
types::PrivKey,
types::{PrivKey, PubKey},
utils::{decode_key, decode_priv_key, sign, verify},
};
@ -217,6 +217,8 @@ fn prepare_accept_forward_for_domain(
pub enum NgdError {
IoError(std::io::Error),
NgError(NgError),
InvalidPeerFile(String),
CannotSavePeer(String),
InvalidKeyFile(String),
CannotSaveKey(String),
InvalidSignature,
@ -332,97 +334,53 @@ async fn main_inner() -> Result<(), NgdError> {
log_debug!("home {}", path.to_str().unwrap());
std::fs::create_dir_all(path.clone()).unwrap();
// reading key from file, if any
let mut key_path = path.clone();
key_path.push("key");
let key_from_file: Option<[u8; 32]> = match read_to_string(key_path.clone()) {
Err(_) => None,
// reading peer privkey file, if any
let mut peer_path = path.clone();
peer_path.push("peer");
let peer_from_file: PrivKey = match read_to_string(peer_path.clone()) {
Err(_) => {
// if no peer privKey found, create one
let priv_key = PrivKey::random_ed();
write(peer_path, priv_key.to_string())
.map_err(|e| NgdError::CannotSavePeer(e.to_string()))?;
priv_key
}
Ok(mut file) => {
let first_line = file
.lines()
.nth(0)
.ok_or(NgdError::InvalidKeyFile("empty file".to_string()))?;
.ok_or(NgdError::InvalidPeerFile("empty file".to_string()))?;
let res = decode_priv_key(first_line.trim())
.map_err(|_| NgdError::InvalidKeyFile("deserialization error".to_string()))?;
.map_err(|_| NgdError::InvalidPeerFile("deserialization error".to_string()))?;
file.zeroize();
Some(*res.slice())
}
};
let mut keys: [[u8; 32]; 4] = match &args.key {
Some(key_string) => {
if key_from_file.is_some() {
log_err!("provided --key option will not be used as a key file is already present");
args.key.as_mut().unwrap().zeroize();
gen_broker_keys(key_from_file)
} else {
let res = decode_priv_key(key_string.as_str()).map_err(|_| {
NgdError::InvalidKeyFile(
"check the argument provided in command line".to_string(),
)
})?;
if args.save_key {
write(key_path.clone(), res.to_string())
.map_err(|e| NgdError::CannotSaveKey(e.to_string()))?;
//master_key.zeroize();
log_info!("The key has been saved to {}", key_path.to_str().unwrap());
}
args.key.as_mut().unwrap().zeroize();
gen_broker_keys(Some(*res.slice()))
}
}
None => {
if key_from_file.is_some() {
gen_broker_keys(key_from_file)
} else {
let res = gen_broker_keys(None);
let key = PrivKey::Ed25519PrivKey(res[0]);
let mut master_key = key.to_string();
if args.save_key {
write(key_path.clone(), &master_key)
.map_err(|e| NgdError::CannotSaveKey(e.to_string()))?;
log_info!("The key has been saved to {}", key_path.to_str().unwrap());
} else {
// on purpose we don't log the key, just print it out to stdout, as it should not be saved in logger's files
println!("YOUR GENERATED KEY IS: {}", master_key);
log_err!("At your request, the key wasn't saved. If you want to save it to disk, use ---save-key");
log_err!("provide it again to the next start of ngd with --key option or NG_SERVER_KEY env variable");
}
master_key.zeroize();
res
}
res
}
};
key_from_file.and_then(|mut key| {
key.zeroize();
None::<()>
});
let mut sign_path = path.clone();
sign_path.push("sign");
//let sign_from_file: Option<[u8; 32]>;
let privkey: PrivKey = keys[3].into();
let pubkey = privkey.to_pub();
if match std::fs::read(sign_path.clone()) {
Err(_) => true,
Ok(file) => {
let sig: Sig = serde_bare::from_slice(&file).map_err(|_| NgdError::InvalidSignature)?;
verify(&vec![110u8, 103u8, 100u8], sig, pubkey)
.map_err(|_| NgdError::InvalidSignature)?;
false
}
} {
// time to save the signature
let sig = sign(&privkey, &pubkey, &vec![110u8, 103u8, 100u8])
.map_err(|e| NgdError::CannotSaveSignature(e.to_string()))?;
let sig_ser = serde_bare::to_vec(&sig).unwrap();
std::fs::write(sign_path, sig_ser)
.map_err(|e| NgdError::CannotSaveSignature(e.to_string()))?;
}
// let mut sign_path = path.clone();
// sign_path.push("sign");
// //let sign_from_file: Option<[u8; 32]>;
// let privkey: PrivKey = keys[3].into();
// let pubkey = privkey.to_pub();
// if match std::fs::read(sign_path.clone()) {
// Err(_) => true,
// Ok(file) => {
// let sig: Sig = serde_bare::from_slice(&file).map_err(|_| NgdError::InvalidSignature)?;
// verify(&vec![110u8, 103u8, 100u8], sig, pubkey)
// .map_err(|_| NgdError::InvalidSignature)?;
// false
// }
// } {
// // time to save the signature
// let sig = sign(&privkey, &pubkey, &vec![110u8, 103u8, 100u8])
// .map_err(|e| NgdError::CannotSaveSignature(e.to_string()))?;
// let sig_ser = serde_bare::to_vec(&sig).unwrap();
// std::fs::write(sign_path, sig_ser)
// .map_err(|e| NgdError::CannotSaveSignature(e.to_string()))?;
// }
// DEALING WITH CONFIG
@ -932,10 +890,7 @@ async fn main_inner() -> Result<(), NgdError> {
}
}
let (privkey, pubkey) = ed_keypair_from_priv_bytes(keys[1]);
keys[1].zeroize();
keys[0].zeroize();
let pubkey = peer_from_file.to_pub();
log_info!("PeerId of node: {}", pubkey);
//debug_println!("Private key of peer: {}", privkey.to_string());
@ -943,17 +898,142 @@ async fn main_inner() -> Result<(), NgdError> {
//let x_from_ed = pubkey.to_dh_from_ed();
//log_info!("du Pubkey from ed: {}", x_from_ed);
// let mut users_path = path.clone();
// users_path.push("users");
let mut storage_path = path.clone();
storage_path.push("storage");
// reading setup from file, if any
let mut setup_path = path.clone();
setup_path.push("setup");
let setup_from_file: Option<()> = match read_to_string(setup_path.clone()) {
// TODO: use binary read instead
Err(_) => None,
Ok(mut file) => Some(()),
};
let mut invite_admin = false;
let wallet_key: SymKey = if setup_from_file.is_some() {
// reading remote_boot from file, if any
let mut remote_boot_path = path.clone();
remote_boot_path.push("remote_boot");
let remote_boot: Option<PubKey> = match read_to_string(remote_boot_path.clone()) {
Err(_) => None,
Ok(mut file) => {
let first_line = file
.lines()
.nth(0)
.ok_or(NgdError::InvalidKeyFile("empty file".to_string()))?;
let res = decode_key(first_line.trim())
.map_err(|_| NgdError::InvalidKeyFile("deserialization error".to_string()))?;
Some(res)
}
};
match remote_boot {
Some(pub_key) => {
// TODO: wait master key with a tiny server listening and waiting for Noise handshake between the pub_key (client) and peer_from_file (privkey of peerId of server)
// then receive the wallet key and return it
SymKey::nil()
}
None => {
// TODO: increment nonce (from setup_nonce file)
// create blob SetupRDV and send it to setup.nextgraph.net
// start server normally with a temporary key (erase all data before)
invite_admin = true;
std::fs::remove_dir_all(storage_path.clone()).unwrap();
SymKey::random()
}
}
} else {
// reading key from file, if any
let mut key_path = path.clone();
key_path.push("key");
let key_from_file: Option<[u8; 32]> = match read_to_string(key_path.clone()) {
Err(_) => None,
Ok(mut file) => {
let first_line = file
.lines()
.nth(0)
.ok_or(NgdError::InvalidKeyFile("empty file".to_string()))?;
let res = decode_priv_key(first_line.trim())
.map_err(|_| NgdError::InvalidKeyFile("deserialization error".to_string()))?;
file.zeroize();
Some(*res.slice())
}
};
let wallet_key: SymKey = match &args.key {
Some(key_string) => {
match key_from_file {
Some(key) => {
log_err!(
"provided --key option will not be used as a key file is already present"
);
args.key.as_mut().unwrap().zeroize();
SymKey::from_array(key)
}
None => {
let res = decode_priv_key(key_string.as_str()).map_err(|_| {
NgdError::InvalidKeyFile(
"check the argument provided in command line".to_string(),
)
})?;
if args.save_key {
write(key_path.clone(), res.to_string())
.map_err(|e| NgdError::CannotSaveKey(e.to_string()))?;
//master_key.zeroize();
log_info!("The key has been saved to {}", key_path.to_str().unwrap());
}
args.key.as_mut().unwrap().zeroize();
SymKey::from_array(*res.slice())
}
}
}
None => {
match key_from_file {
Some(key) => SymKey::from_array(key),
None => {
log_warn!("No key provided, generating one");
let mut k = [0u8; 32];
getrandom::getrandom(&mut k).expect("getrandom failed");
let key = PrivKey::Ed25519PrivKey(k);
let mut master_key = key.to_string();
std::fs::remove_dir_all(storage_path.clone()).unwrap();
if args.save_key {
write(key_path.clone(), &master_key)
.map_err(|e| NgdError::CannotSaveKey(e.to_string()))?;
log_info!("The key has been saved to {}", key_path.to_str().unwrap());
} else {
// on purpose we don't log the key, just print it out to stdout, as it should not be saved in logger's files
println!("YOUR GENERATED KEY IS: {}", master_key);
log_err!("At your request, the key wasn't saved. If you want to save it to disk, use ---save-key");
log_err!("provide it again to the next start of ngd with --key option or NG_SERVER_KEY env variable");
log_err!("or use the below links to create a wallet and an admin account for this broker.");
log_err!("If you don't proceed to one of the above, during next startup, all your data will be deleted and a new key will be generated.");
}
invite_admin = true;
master_key.zeroize();
SymKey::from_array(*key.slice())
}
}
}
};
key_from_file.and_then(|mut key| {
key.zeroize();
None::<()>
});
wallet_key
};
match config.unwrap() {
DaemonConfig::V0(v0) => {
run_server_v0(
privkey,
pubkey,
SymKey::from_array(keys[2]),
v0,
path,
args.invite_admin,
)
.await?
run_server_v0(peer_from_file, pubkey, wallet_key, v0, path, invite_admin).await?
}
}

Loading…
Cancel
Save