add_invitation list_invitations in CLI

pull/19/head
Niko PLP 1 year ago
parent b9764e7983
commit 245db1807b
  1. 76
      Cargo.lock
  2. 3
      ngcli/Cargo.toml
  3. 129
      ngcli/src/main.rs
  4. 4
      ngd/src/cli.rs
  5. 1
      ngd/src/main.rs
  6. 88
      p2p-broker/src/broker_store/invitation.rs
  7. 2
      p2p-broker/src/server_ws.rs
  8. 31
      p2p-broker/src/storage.rs
  9. 2
      p2p-broker/src/types.rs
  10. 123
      p2p-net/src/actors/add_invitation.rs
  11. 130
      p2p-net/src/actors/list_invitations.rs
  12. 6
      p2p-net/src/actors/mod.rs
  13. 16
      p2p-net/src/broker.rs
  14. 12
      p2p-net/src/broker_storage.rs
  15. 57
      p2p-net/src/types.rs
  16. 1
      p2p-repo/Cargo.toml
  17. 1
      p2p-repo/src/kcv_store.rs
  18. 8
      p2p-repo/src/types.rs
  19. 23
      p2p-repo/src/utils.rs
  20. 7
      stores-lmdb/src/kcv_store.rs

76
Cargo.lock generated

@ -727,8 +727,11 @@ checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
"js-sys",
"num-traits", "num-traits",
"serde", "serde",
"time 0.1.45",
"wasm-bindgen",
"winapi", "winapi",
] ]
@ -1258,6 +1261,20 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
[[package]]
name = "duration-str"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9f037c488d179e21c87ef5fa9c331e8e62f5dddfa84618b41bb197da03edff1"
dependencies = [
"chrono",
"nom",
"rust_decimal",
"serde",
"thiserror",
"time 0.3.23",
]
[[package]] [[package]]
name = "ed25519" name = "ed25519"
version = "1.5.3" version = "1.5.3"
@ -2576,6 +2593,12 @@ dependencies = [
"unicase", "unicase",
] ]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.7.1" version = "0.7.1"
@ -2823,6 +2846,7 @@ dependencies = [
"base64-url", "base64-url",
"blake3", "blake3",
"clap", "clap",
"duration-str",
"ed25519-dalek", "ed25519-dalek",
"env_logger", "env_logger",
"futures", "futures",
@ -2917,6 +2941,16 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]] [[package]]
name = "nu-ansi-term" name = "nu-ansi-term"
version = "0.46.0" version = "0.46.0"
@ -3197,6 +3231,7 @@ dependencies = [
"serde_bare", "serde_bare",
"serde_bytes", "serde_bytes",
"slice_as_array", "slice_as_array",
"time 0.3.23",
"wasm-bindgen", "wasm-bindgen",
"web-time", "web-time",
"zeroize", "zeroize",
@ -3466,7 +3501,7 @@ dependencies = [
"line-wrap", "line-wrap",
"quick-xml", "quick-xml",
"serde", "serde",
"time", "time 0.3.23",
] ]
[[package]] [[package]]
@ -3918,6 +3953,16 @@ dependencies = [
"walkdir", "walkdir",
] ]
[[package]]
name = "rust_decimal"
version = "1.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0446843641c69436765a35a5a77088e28c2e6a12da93e84aa3ab1cd4aa5a042"
dependencies = [
"arrayvec",
"num-traits",
]
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.4.0" version = "0.4.0"
@ -4176,7 +4221,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serde_with_macros", "serde_with_macros",
"time", "time 0.3.23",
] ]
[[package]] [[package]]
@ -4655,7 +4700,7 @@ dependencies = [
"sha2 0.10.7", "sha2 0.10.7",
"tauri-utils", "tauri-utils",
"thiserror", "thiserror",
"time", "time 0.3.23",
"url", "url",
"uuid 1.3.4", "uuid 1.3.4",
"walkdir", "walkdir",
@ -4856,9 +4901,20 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.22" version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]]
name = "time"
version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"
dependencies = [ dependencies = [
"itoa 1.0.6", "itoa 1.0.6",
"serde", "serde",
@ -4874,9 +4930,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
[[package]] [[package]]
name = "time-macros" name = "time-macros"
version = "0.2.9" version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4"
dependencies = [ dependencies = [
"time-core", "time-core",
] ]
@ -5362,6 +5418,12 @@ version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"

@ -29,4 +29,5 @@ getrandom = "0.2.7"
blake3 = "1.3.1" blake3 = "1.3.1"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_bare = "0.5.0" serde_bare = "0.5.0"
serde_bytes = "0.11.7" serde_bytes = "0.11.7"
duration-str = "0.5.1"

@ -11,6 +11,7 @@
use ed25519_dalek::*; use ed25519_dalek::*;
use duration_str::parse;
use futures::{future, pin_mut, stream, SinkExt, StreamExt}; use futures::{future, pin_mut, stream, SinkExt, StreamExt};
use p2p_net::actors::*; use p2p_net::actors::*;
use p2p_repo::object::Object; use p2p_repo::object::Object;
@ -35,7 +36,9 @@ use p2p_net::types::*;
use p2p_repo::log::*; use p2p_repo::log::*;
use p2p_repo::types::*; use p2p_repo::types::*;
use p2p_repo::utils::{decode_key, generate_keypair, now_timestamp}; use p2p_repo::utils::{
decode_key, display_timestamp, generate_keypair, now_timestamp, timestamp_after,
};
use clap::{arg, command, value_parser, ArgAction, Command}; use clap::{arg, command, value_parser, ArgAction, Command};
@ -140,6 +143,22 @@ async fn main() -> Result<(), ProtocolError> {
Command::new("list-users") Command::new("list-users")
.about("list all users registered in the broker") .about("list all users registered in the broker")
.arg(arg!(-a --admin "only lists admin users. otherwise, lists only non admin users").required(false))) .arg(arg!(-a --admin "only lists admin users. otherwise, lists only non admin users").required(false)))
.subcommand(
Command::new("add-invitation")
.about("add an invitation to register on the server")
.arg(arg!([EXPIRES] "offset (from now) of time after which the invitation should expire. Format example: 1w 1d 1m. default unit is second").conflicts_with("forever"))
.arg(arg!(-a --admin "user registered with this invitation will have admin permissions").required(false))
.arg(arg!(-i --multi "many users can use this invitation to register themselves, until the invitation code is deleted by an admin").required(false).conflicts_with("admin").conflicts_with("unique"))
.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 <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 <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)))
.subcommand(
Command::new("list-invitations")
.about("list all invitations")
.arg(arg!(-a --admin "only lists admin invitations").required(false))
.arg(arg!(-m --multi "only lists multiple-use invitations").required(false))
.arg(arg!(-u --unique "only lists unique-use invitations").required(false)))
) )
.subcommand( .subcommand(
Command::new("gen-user") Command::new("gen-user")
@ -445,6 +464,114 @@ async fn main() -> Result<(), ProtocolError> {
} }
return res.map(|_| ()); return res.map(|_| ());
} }
Some(("add-invitation", sub2_matches)) => {
log_debug!("add-invitation");
let expires = sub2_matches.get_one::<String>("EXPIRES");
let expiry = if expires.is_some() {
let duration = parse(expires.unwrap().as_str()).unwrap();
timestamp_after(duration)
} else {
0
};
let admin = sub2_matches.get_flag("admin");
let multi = sub2_matches.get_flag("multi");
let _unique = sub2_matches.get_flag("unique");
let symkey = SymKey::random();
let invite_code = if admin {
InvitationCode::Admin(symkey.clone())
} else if multi {
InvitationCode::Multi(symkey.clone())
} else {
InvitationCode::Unique(symkey.clone())
};
let mut res = do_admin_call(
keys[1],
config_v0,
AddInvitation::V0(AddInvitationV0 {
invite_code,
expiry,
memo: sub2_matches.get_one::<String>("memo").map(|s| s.clone()),
}),
)
.await;
match res.as_mut() {
Err(e) => log_err!("An error occurred: {e}"),
Ok(AdminResponseContentV0::Invitation(invitation)) => {
invitation
.set_name(sub2_matches.get_one::<String>("name").map(|s| s.clone()));
log_debug!("{:?}", invitation);
println!("Invitation created successfully. please note carefully the following links. share one of them with the invited user(s)");
for link in invitation.get_urls() {
println!("The invitation link is: {}", link)
}
}
_ => {
log_err!("Invalid response");
return Err(ProtocolError::InvalidValue);
}
}
return res.map(|_| ());
}
Some(("list-invitations", sub2_matches)) => {
log_debug!("invitations");
let admin = sub2_matches.get_flag("admin");
let multi = sub2_matches.get_flag("multi");
let unique = sub2_matches.get_flag("unique");
let res = do_admin_call(
keys[1],
config_v0,
ListInvitations::V0(ListInvitationsV0 {
admin,
multi,
unique,
}),
)
.await;
match &res {
Err(e) => log_err!("An error occurred: {e}"),
Ok(AdminResponseContentV0::Invitations(list)) => {
println!(
"Found {} {}invitations",
list.len(),
if admin && multi && unique {
"".to_string()
} else {
let mut name = vec![];
if admin {
name.push("admin ");
}
if multi {
name.push("multi ");
}
if unique {
name.push("unique ");
}
name.join("or ")
}
);
for invite in list {
println!(
"{} expires {}. memo={}",
invite.0,
if invite.1 == 0 {
"never".to_string()
} else {
display_timestamp(&invite.1)
},
invite.2.as_ref().unwrap_or(&"".to_string())
);
}
}
_ => {
log_err!("Invalid response");
return Err(ProtocolError::InvalidValue);
}
}
return res.map(|_| ());
}
_ => panic!("shouldn't happen"), _ => panic!("shouldn't happen"),
}, },
_ => println!("Nothing to do."), _ => println!("Nothing to do."),

@ -112,6 +112,10 @@ pub(crate) struct Cli {
#[arg(long, conflicts_with("registration_off"))] #[arg(long, conflicts_with("registration_off"))]
pub registration_open: bool, pub registration_open: bool,
/// Registration URL used when creating invitation links, an optional url to redirect the user to, for accepting ToS and making payment, if any.
#[arg(long)]
pub registration_url: Option<String>,
/// Admin userID /// Admin userID
#[arg(long)] #[arg(long)]
pub admin: Option<String>, pub admin: Option<String>,

@ -935,6 +935,7 @@ async fn main_inner() -> Result<(), ()> {
overlays_configs: vec![overlays_config], overlays_configs: vec![overlays_config],
registration, registration,
admin_user, admin_user,
registration_url: args.registration_url,
})); }));
if args.print_config { if args.print_config {

@ -17,6 +17,7 @@ use std::time::SystemTime;
use p2p_net::types::*; use p2p_net::types::*;
use p2p_repo::kcv_store::KCVStore; use p2p_repo::kcv_store::KCVStore;
use p2p_repo::store::*; use p2p_repo::store::*;
use p2p_repo::types::SymKey;
use p2p_repo::types::Timestamp; use p2p_repo::types::Timestamp;
use p2p_repo::utils::now_timestamp; use p2p_repo::utils::now_timestamp;
use serde_bare::from_slice; use serde_bare::from_slice;
@ -33,9 +34,13 @@ impl<'a> Invitation<'a> {
// propertie's invitation suffixes // propertie's invitation suffixes
const TYPE: u8 = b"t"[0]; const TYPE: u8 = b"t"[0];
const EXPIRE: u8 = b"e"[0]; //const EXPIRE: u8 = b"e"[0];
const ALL_PROPERTIES: [u8; 2] = [Self::TYPE, Self::EXPIRE]; const PREFIX_EXPIRE: u8 = b"e"[0];
// propertie's expiry suffixes
const INVITATION: u8 = b"i"[0];
const ALL_PROPERTIES: [u8; 1] = [Self::TYPE];
const SUFFIX_FOR_EXIST_CHECK: u8 = Self::TYPE; const SUFFIX_FOR_EXIST_CHECK: u8 = Self::TYPE;
@ -52,12 +57,13 @@ impl<'a> Invitation<'a> {
pub fn create( pub fn create(
id: &InvitationCode, id: &InvitationCode,
expiry: u32, expiry: u32,
memo: &Option<String>,
store: &'a dyn KCVStore, store: &'a dyn KCVStore,
) -> Result<Invitation<'a>, StorageError> { ) -> Result<Invitation<'a>, StorageError> {
let (code_type, code) = match id { let (code_type, code) = match id {
InvitationCode::Unique(c) => (0, c.slice()), InvitationCode::Unique(c) => (0u8, c.slice()),
InvitationCode::Multi(c) => (1, c.slice()), InvitationCode::Multi(c) => (1u8, c.slice()),
InvitationCode::Admin(c) => (2, c.slice()), InvitationCode::Admin(c) => (2u8, c.slice()),
}; };
let acc = Invitation { let acc = Invitation {
id: code.clone(), id: code.clone(),
@ -66,23 +72,65 @@ impl<'a> Invitation<'a> {
if acc.exists() { if acc.exists() {
return Err(StorageError::BackendError); return Err(StorageError::BackendError);
} }
let mut value = to_vec(&(code_type, expiry, memo.clone()))?;
store.write_transaction(&|tx| { store.write_transaction(&|tx| {
tx.put( tx.put(Self::PREFIX, &to_vec(code)?, Some(Self::TYPE), &value)?;
Self::PREFIX,
&to_vec(code)?,
Some(Self::TYPE),
&to_vec(&code_type)?,
)?;
tx.put(
Self::PREFIX,
&to_vec(code)?,
Some(Self::EXPIRE),
&to_vec(&expiry)?,
)?;
Ok(()) Ok(())
})?; })?;
Ok(acc) Ok(acc)
} }
pub fn get_all_invitations(
store: &'a dyn KCVStore,
mut admin: bool,
mut unique: bool,
mut multi: bool,
) -> Result<Vec<(InvitationCode, u32, Option<String>)>, StorageError> {
let size = to_vec(&[0u8; 32])?.len();
let mut res: Vec<(InvitationCode, u32, Option<String>)> = vec![];
if !admin && !unique && !multi {
admin = true;
unique = true;
multi = true;
}
for invite in store.get_all_keys_and_values(Self::PREFIX, size, None)? {
if invite.0.len() == size + 2 {
let code: [u8; 32] = from_slice(&invite.0[1..invite.0.len() - 1])?;
if invite.0[size + 1] == Self::TYPE {
let code_type: (u8, u32, Option<String>) = from_slice(&invite.1)?;
let inv_code = match code_type {
(0, ex, memo) => {
if unique {
Some((InvitationCode::Unique(SymKey::ChaCha20Key(code)), ex, memo))
} else {
None
}
}
(1, ex, memo) => {
if multi {
Some((InvitationCode::Multi(SymKey::ChaCha20Key(code)), ex, memo))
} else {
None
}
}
(2, ex, memo) => {
if admin {
Some((InvitationCode::Admin(SymKey::ChaCha20Key(code)), ex, memo))
} else {
None
}
}
_ => panic!("invalid code type value"),
};
if inv_code.is_some() {
res.push(inv_code.unwrap());
}
}
}
}
Ok(res)
}
pub fn exists(&self) -> bool { pub fn exists(&self) -> bool {
self.store self.store
.get( .get(
@ -99,9 +147,9 @@ impl<'a> Invitation<'a> {
pub fn is_expired(&self) -> Result<bool, StorageError> { pub fn is_expired(&self) -> Result<bool, StorageError> {
let expire_ser = self let expire_ser = self
.store .store
.get(Self::PREFIX, &to_vec(&self.id)?, Some(Self::EXPIRE))?; .get(Self::PREFIX, &to_vec(&self.id)?, Some(Self::TYPE))?;
let expire: u32 = from_slice(&expire_ser)?; let expire: (u8, u32, Option<String>) = from_slice(&expire_ser)?;
if expire < now_timestamp() { if expire.1 < now_timestamp() {
return Ok(true); return Ok(true);
} }
Ok(false) Ok(false)

@ -787,7 +787,9 @@ pub async fn run_server_v0(
overlays_configs: config.overlays_configs, overlays_configs: config.overlays_configs,
registration: config.registration, registration: config.registration,
admin_user: config.admin_user, admin_user: config.admin_user,
registration_url: config.registration_url,
peer_id, peer_id,
bootstrap,
}; };
broker.set_server_config(server_config); broker.set_server_config(server_config);
} }

@ -59,7 +59,12 @@ impl LmdbBrokerStorage {
let accounts_storage = LmdbKCVStore::open(&accounts_path, accounts_key.slice().clone()); let accounts_storage = LmdbKCVStore::open(&accounts_path, accounts_key.slice().clone());
let symkey = SymKey::random(); let symkey = SymKey::random();
let invite_code = InvitationCode::Admin(symkey.clone()); let invite_code = InvitationCode::Admin(symkey.clone());
let _ = Invitation::create(&invite_code, 0, &accounts_storage)?; let _ = Invitation::create(
&invite_code,
0,
&Some("admin user automatically invited at first startup".to_string()),
&accounts_storage,
)?;
let invitation = p2p_net::types::Invitation::V0(InvitationV0 { let invitation = p2p_net::types::Invitation::V0(InvitationV0 {
code: Some(symkey), code: Some(symkey),
name: Some("your NG Box, as admin".into()), name: Some("your NG Box, as admin".into()),
@ -110,4 +115,28 @@ impl BrokerStorage for LmdbBrokerStorage {
log_debug!("list_users that are admin == {admins}"); log_debug!("list_users that are admin == {admins}");
Ok(Account::get_all_users(admins, &self.accounts_storage)?) Ok(Account::get_all_users(admins, &self.accounts_storage)?)
} }
fn list_invitations(
&self,
admin: bool,
unique: bool,
multi: bool,
) -> Result<Vec<(InvitationCode, u32, Option<String>)>, ProtocolError> {
log_debug!("list_invitations admin={admin} unique={unique} multi={multi}");
Ok(Invitation::get_all_invitations(
&self.accounts_storage,
admin,
unique,
multi,
)?)
}
fn add_invitation(
&self,
invite_code: &InvitationCode,
expiry: u32,
memo: &Option<String>,
) -> Result<(), ProtocolError> {
log_debug!("add_invitation {invite_code} expiry {expiry}");
Invitation::create(invite_code, expiry, memo, &self.accounts_storage)?;
Ok(())
}
} }

@ -21,6 +21,8 @@ pub struct DaemonConfigV0 {
pub registration: RegistrationConfig, pub registration: RegistrationConfig,
pub admin_user: Option<PubKey>, pub admin_user: Option<PubKey>,
pub registration_url: Option<String>,
} }
/// Daemon config /// Daemon config

@ -0,0 +1,123 @@
/*
* Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
* All rights reserved.
* Licensed under the Apache License, Version 2.0
* <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
* or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
* at your option. All files in the project carrying such
* notice may not be copied, modified, or distributed except
* according to those terms.
*/
use crate::broker::{ServerConfig, BROKER};
use crate::connection::NoiseFSM;
use crate::types::*;
use crate::{actor::*, errors::ProtocolError, types::ProtocolMessage};
use async_std::sync::Mutex;
use p2p_repo::log::*;
use p2p_repo::types::PubKey;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use super::StartProtocol;
/// Add invitation
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AddInvitationV0 {
pub invite_code: InvitationCode,
pub expiry: u32,
pub memo: Option<String>,
}
/// Add invitation
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum AddInvitation {
V0(AddInvitationV0),
}
impl AddInvitation {
pub fn code(&self) -> &InvitationCode {
match self {
AddInvitation::V0(o) => &o.invite_code,
}
}
pub fn expiry(&self) -> u32 {
match self {
AddInvitation::V0(o) => o.expiry,
}
}
pub fn memo(&self) -> &Option<String> {
match self {
AddInvitation::V0(o) => &o.memo,
}
}
pub fn get_actor(&self) -> Box<dyn EActor> {
Actor::<AddInvitation, AdminResponse>::new_responder()
}
}
impl TryFrom<ProtocolMessage> for AddInvitation {
type Error = ProtocolError;
fn try_from(msg: ProtocolMessage) -> Result<Self, Self::Error> {
if let ProtocolMessage::Start(StartProtocol::Admin(AdminRequest::V0(AdminRequestV0 {
content: AdminRequestContentV0::AddInvitation(a),
..
}))) = msg
{
Ok(a)
} else {
log_debug!("INVALID {:?}", msg);
Err(ProtocolError::InvalidValue)
}
}
}
impl From<AddInvitation> for ProtocolMessage {
fn from(msg: AddInvitation) -> ProtocolMessage {
unimplemented!();
}
}
impl From<AddInvitation> for AdminRequestContentV0 {
fn from(msg: AddInvitation) -> AdminRequestContentV0 {
AdminRequestContentV0::AddInvitation(msg)
}
}
impl Actor<'_, AddInvitation, AdminResponse> {}
#[async_trait::async_trait]
impl EActor for Actor<'_, AddInvitation, AdminResponse> {
async fn respond(
&mut self,
msg: ProtocolMessage,
fsm: Arc<Mutex<NoiseFSM>>,
) -> Result<(), ProtocolError> {
let req = AddInvitation::try_from(msg)?;
let broker = BROKER.read().await;
broker
.get_storage()?
.add_invitation(req.code(), req.expiry(), req.memo())?;
let invitation = crate::types::Invitation::V0(InvitationV0::new(
broker.get_bootstrap()?.clone(),
Some(req.code().get_symkey()),
None,
broker.get_registration_url().map(|s| s.clone()),
));
let response: AdminResponseV0 = invitation.into();
fsm.lock().await.send(response.into()).await?;
Ok(())
}
}
impl From<Invitation> for AdminResponseV0 {
fn from(res: Invitation) -> AdminResponseV0 {
AdminResponseV0 {
id: 0,
result: 0,
content: AdminResponseContentV0::Invitation(res),
padding: vec![],
}
}
}

@ -0,0 +1,130 @@
/*
* Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
* All rights reserved.
* Licensed under the Apache License, Version 2.0
* <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
* or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
* at your option. All files in the project carrying such
* notice may not be copied, modified, or distributed except
* according to those terms.
*/
use crate::broker::BROKER;
use crate::connection::NoiseFSM;
use crate::types::*;
use crate::{actor::*, errors::ProtocolError, types::ProtocolMessage};
use async_std::sync::Mutex;
use p2p_repo::log::*;
use p2p_repo::types::PubKey;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use super::StartProtocol;
/// List invitations registered on this broker
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct ListInvitationsV0 {
/// should list only the admin invitations.
pub admin: bool,
/// should list only the unique invitations.
pub unique: bool,
/// should list only the multi invitations.
pub multi: bool,
}
/// List invitations registered on this broker
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum ListInvitations {
V0(ListInvitationsV0),
}
impl ListInvitations {
pub fn admin(&self) -> bool {
match self {
Self::V0(o) => o.admin,
}
}
pub fn unique(&self) -> bool {
match self {
Self::V0(o) => o.unique,
}
}
pub fn multi(&self) -> bool {
match self {
Self::V0(o) => o.multi,
}
}
pub fn get_actor(&self) -> Box<dyn EActor> {
Actor::<ListInvitations, AdminResponse>::new_responder()
}
}
impl TryFrom<ProtocolMessage> for ListInvitations {
type Error = ProtocolError;
fn try_from(msg: ProtocolMessage) -> Result<Self, Self::Error> {
if let ProtocolMessage::Start(StartProtocol::Admin(AdminRequest::V0(AdminRequestV0 {
content: AdminRequestContentV0::ListInvitations(a),
..
}))) = msg
{
Ok(a)
} else {
//log_debug!("INVALID {:?}", msg);
Err(ProtocolError::InvalidValue)
}
}
}
impl From<ListInvitations> for ProtocolMessage {
fn from(msg: ListInvitations) -> ProtocolMessage {
unimplemented!();
}
}
impl From<ListInvitations> for AdminRequestContentV0 {
fn from(msg: ListInvitations) -> AdminRequestContentV0 {
AdminRequestContentV0::ListInvitations(msg)
}
}
impl Actor<'_, ListInvitations, AdminResponse> {}
#[async_trait::async_trait]
impl EActor for Actor<'_, ListInvitations, AdminResponse> {
async fn respond(
&mut self,
msg: ProtocolMessage,
fsm: Arc<Mutex<NoiseFSM>>,
) -> Result<(), ProtocolError> {
let req = ListInvitations::try_from(msg)?;
let res = BROKER.read().await.get_storage()?.list_invitations(
req.admin(),
req.unique(),
req.multi(),
);
let response: AdminResponseV0 = res.into();
fsm.lock().await.send(response.into()).await?;
Ok(())
}
}
impl From<Result<Vec<(InvitationCode, u32, Option<String>)>, ProtocolError>> for AdminResponseV0 {
fn from(
res: Result<Vec<(InvitationCode, u32, Option<String>)>, ProtocolError>,
) -> AdminResponseV0 {
match res {
Err(e) => AdminResponseV0 {
id: 0,
result: e.into(),
content: AdminResponseContentV0::EmptyResponse,
padding: vec![],
},
Ok(vec) => AdminResponseV0 {
id: 0,
result: 0,
content: AdminResponseContentV0::Invitations(vec),
padding: vec![],
},
}
}
}

@ -15,3 +15,9 @@ pub use del_user::*;
pub mod list_users; pub mod list_users;
pub use list_users::*; pub use list_users::*;
pub mod add_invitation;
pub use add_invitation::*;
pub mod list_invitations;
pub use list_invitations::*;

@ -65,6 +65,9 @@ pub struct ServerConfig {
pub registration: RegistrationConfig, pub registration: RegistrationConfig,
pub admin_user: Option<PubKey>, pub admin_user: Option<PubKey>,
pub peer_id: PubKey, pub peer_id: PubKey,
// when creating invitation links, an optional url to redirect the user to can be used, for accepting ToS and making payment, if any.
pub registration_url: Option<String>,
pub bootstrap: BootstrapContent,
} }
pub static BROKER: Lazy<Arc<RwLock<Broker>>> = Lazy::new(|| Arc::new(RwLock::new(Broker::new()))); pub static BROKER: Lazy<Arc<RwLock<Broker>>> = Lazy::new(|| Arc::new(RwLock::new(Broker::new())));
@ -108,6 +111,19 @@ impl<'a> Broker<'a> {
self.config.as_ref() self.config.as_ref()
} }
pub fn get_registration_url(&self) -> Option<&String> {
self.config
.as_ref()
.and_then(|c| c.registration_url.as_ref())
}
pub fn get_bootstrap(&self) -> Result<&BootstrapContent, ProtocolError> {
self.config
.as_ref()
.map(|c| &c.bootstrap)
.ok_or(ProtocolError::BrokerError)
}
pub fn set_storage(&mut self, storage: impl BrokerStorage + 'a) { pub fn set_storage(&mut self, storage: impl BrokerStorage + 'a) {
//log_debug!("set_storage"); //log_debug!("set_storage");
self.storage = Some(Box::new(storage)); self.storage = Some(Box::new(storage));

@ -16,4 +16,16 @@ pub trait BrokerStorage: Send + Sync + std::fmt::Debug {
fn get_user(&self, user_id: PubKey) -> Result<bool, ProtocolError>; fn get_user(&self, user_id: PubKey) -> Result<bool, ProtocolError>;
fn add_user(&self, user_id: PubKey, is_admin: bool) -> Result<(), ProtocolError>; fn add_user(&self, user_id: PubKey, is_admin: bool) -> Result<(), ProtocolError>;
fn list_users(&self, admins: bool) -> Result<Vec<PubKey>, ProtocolError>; fn list_users(&self, admins: bool) -> Result<Vec<PubKey>, ProtocolError>;
fn list_invitations(
&self,
admin: bool,
unique: bool,
multi: bool,
) -> Result<Vec<(InvitationCode, u32, Option<String>)>, ProtocolError>;
fn add_invitation(
&self,
invite_code: &InvitationCode,
expiry: u32,
memo: &Option<String>,
) -> Result<(), ProtocolError>;
} }

@ -527,6 +527,24 @@ pub enum InvitationCode {
Multi(SymKey), Multi(SymKey),
} }
impl InvitationCode {
pub fn get_symkey(&self) -> SymKey {
match self {
Self::Unique(s) | Self::Admin(s) | Self::Multi(s) => s.clone(),
}
}
}
impl fmt::Display for InvitationCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Unique(k) => write!(f, "unique {}", k),
Self::Admin(k) => write!(f, "admin {}", k),
Self::Multi(k) => write!(f, "multi {}", k),
}
}
}
/// Invitation to create an account at a broker. Version 0 /// Invitation to create an account at a broker. Version 0
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct InvitationV0 { pub struct InvitationV0 {
@ -542,6 +560,29 @@ pub struct InvitationV0 {
pub url: Option<String>, pub url: Option<String>,
} }
impl InvitationV0 {
pub fn set_bootstrap(&mut self, content: BootstrapContent) {
match content {
BootstrapContent::V0(v0) => self.bootstrap = v0,
}
}
pub fn new(
bootstrap_content: BootstrapContent,
code: Option<SymKey>,
name: Option<String>,
url: Option<String>,
) -> Self {
match bootstrap_content {
BootstrapContent::V0(v0) => InvitationV0 {
bootstrap: v0,
code,
name,
url,
},
}
}
}
impl Invitation { impl Invitation {
pub fn new_v0( pub fn new_v0(
bootstrap: BootstrapContentV0, bootstrap: BootstrapContentV0,
@ -594,6 +635,14 @@ impl Invitation {
} }
} }
pub fn set_name(&mut self, name: Option<String>) {
if name.is_some() {
match self {
Invitation::V0(v0) => v0.name = Some(name.unwrap()),
}
}
}
/// first URL in the list is the ngone one /// first URL in the list is the ngone one
pub fn get_urls(&self) -> Vec<String> { pub fn get_urls(&self) -> Vec<String> {
match self { match self {
@ -1753,6 +1802,8 @@ pub enum AdminRequestContentV0 {
AddUser(AddUser), AddUser(AddUser),
DelUser(DelUser), DelUser(DelUser),
ListUsers(ListUsers), ListUsers(ListUsers),
ListInvitations(ListInvitations),
AddInvitation(AddInvitation),
} }
impl AdminRequestContentV0 { impl AdminRequestContentV0 {
pub fn type_id(&self) -> TypeId { pub fn type_id(&self) -> TypeId {
@ -1760,6 +1811,8 @@ impl AdminRequestContentV0 {
Self::AddUser(a) => a.type_id(), Self::AddUser(a) => a.type_id(),
Self::DelUser(a) => a.type_id(), Self::DelUser(a) => a.type_id(),
Self::ListUsers(a) => a.type_id(), Self::ListUsers(a) => a.type_id(),
Self::ListInvitations(a) => a.type_id(),
Self::AddInvitation(a) => a.type_id(),
} }
} }
pub fn get_actor(&self) -> Box<dyn EActor> { pub fn get_actor(&self) -> Box<dyn EActor> {
@ -1767,6 +1820,8 @@ impl AdminRequestContentV0 {
Self::AddUser(a) => a.get_actor(), Self::AddUser(a) => a.get_actor(),
Self::DelUser(a) => a.get_actor(), Self::DelUser(a) => a.get_actor(),
Self::ListUsers(a) => a.get_actor(), Self::ListUsers(a) => a.get_actor(),
Self::ListInvitations(a) => a.get_actor(),
Self::AddInvitation(a) => a.get_actor(),
} }
} }
} }
@ -1849,6 +1904,8 @@ impl From<AdminRequest> for ProtocolMessage {
pub enum AdminResponseContentV0 { pub enum AdminResponseContentV0 {
EmptyResponse, EmptyResponse,
Users(Vec<PubKey>), Users(Vec<PubKey>),
Invitations(Vec<(InvitationCode, u32, Option<String>)>),
Invitation(Invitation),
} }
/// Response to an `AdminRequest` V0 /// Response to an `AdminRequest` V0

@ -28,6 +28,7 @@ wasm-bindgen = "0.2"
slice_as_array = "1.1.0" slice_as_array = "1.1.0"
curve25519-dalek = "3.2.0" curve25519-dalek = "3.2.0"
zeroize = { version = "1.6.0", features = ["zeroize_derive"] } zeroize = { version = "1.6.0", features = ["zeroize_derive"] }
time = { version= "0.3.23", features = ["formatting"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
debug_print = "1.0.0" debug_print = "1.0.0"

@ -69,6 +69,7 @@ pub trait ReadTransaction {
value: &Vec<u8>, value: &Vec<u8>,
) -> Result<(), StorageError>; ) -> Result<(), StorageError>;
/// retrieves all the keys and values with the given prefix and key_size. if no suffix is specified, then all (including none) the suffices are returned
fn get_all_keys_and_values( fn get_all_keys_and_values(
&self, &self,
prefix: u8, prefix: u8,

@ -69,6 +69,14 @@ impl SymKey {
} }
} }
impl fmt::Display for SymKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ChaCha20Key(k) => write!(f, "{}", base64_url::encode(k)),
}
}
}
impl TryFrom<&[u8]> for SymKey { impl TryFrom<&[u8]> for SymKey {
type Error = NgError; type Error = NgError;
fn try_from(buf: &[u8]) -> Result<Self, NgError> { fn try_from(buf: &[u8]) -> Result<Self, NgError> {

@ -18,7 +18,8 @@ use ed25519_dalek::*;
use futures::channel::mpsc; use futures::channel::mpsc;
use rand::rngs::OsRng; use rand::rngs::OsRng;
use rand::RngCore; use rand::RngCore;
use web_time::{SystemTime, UNIX_EPOCH}; use time::OffsetDateTime;
use web_time::{Duration, SystemTime, UNIX_EPOCH};
use zeroize::Zeroize; use zeroize::Zeroize;
pub fn ed_keypair_from_priv_bytes(secret_key: [u8; 32]) -> (PrivKey, PubKey) { pub fn ed_keypair_from_priv_bytes(secret_key: [u8; 32]) -> (PrivKey, PubKey) {
@ -166,4 +167,24 @@ pub fn now_timestamp() -> Timestamp {
.unwrap() .unwrap()
} }
/// returns a new NextGraph Timestamp equivalent to the duration after now.
pub fn timestamp_after(duration: Duration) -> Timestamp {
(((SystemTime::now().duration_since(UNIX_EPOCH).unwrap() + duration).as_secs()
- EPOCH_AS_UNIX_TIMESTAMP)
/ 60)
.try_into()
.unwrap()
}
/// displays the NextGraph Timestamp in UTC.
pub fn display_timestamp(ts: &Timestamp) -> String {
let st = SystemTime::UNIX_EPOCH
+ Duration::from_secs(EPOCH_AS_UNIX_TIMESTAMP)
+ Duration::from_secs(*ts as u64 * 60u64);
let dt: OffsetDateTime = st.into();
dt.format(&time::format_description::parse("[day]/[month]/[year] [hour]:[minute] UTC").unwrap())
.unwrap()
}
pub type Receiver<T> = mpsc::UnboundedReceiver<T>; pub type Receiver<T> = mpsc::UnboundedReceiver<T>;

@ -251,7 +251,8 @@ impl ReadTransaction for LmdbKCVStore {
let vec_key_start = vec![0u8; key_size]; let vec_key_start = vec![0u8; key_size];
let vec_key_end = vec![255u8; key_size]; let vec_key_end = vec![255u8; key_size];
let property_start = Self::compute_property(prefix, &vec_key_start, suffix); let property_start = Self::compute_property(prefix, &vec_key_start, suffix);
let property_end = Self::compute_property(prefix, &vec_key_end, suffix); let property_end =
Self::compute_property(prefix, &vec_key_end, Some(suffix.unwrap_or(255u8)));
let lock = self.environment.read().unwrap(); let lock = self.environment.read().unwrap();
let reader = lock.read().unwrap(); let reader = lock.read().unwrap();
let mut iter = self let mut iter = self
@ -270,8 +271,8 @@ impl ReadTransaction for LmdbKCVStore {
{ {
continue; continue;
} }
} else if val.0.len() > (key_size + 1) { // } else if val.0.len() > (key_size + 1) {
continue; // continue;
} }
vector.push((val.0.to_vec(), val.1.to_bytes().unwrap())); vector.push((val.0.to_vec(), val.1.to_bytes().unwrap()));
} }

Loading…
Cancel
Save