add rustfmt and fmtall.sh

This commit is contained in:
creating2morrow 2023-05-09 17:28:07 -04:00
parent 438b22838e
commit e9ef5778d1
38 changed files with 1158 additions and 585 deletions

6
.rustfmt.toml Normal file
View file

@ -0,0 +1,6 @@
# cargo +nightly fmt
comment_width = 100
format_code_in_doc_comments = true
imports_granularity = "Crate"
imports_layout = "Vertical"
wrap_comments = true

View file

@ -20,11 +20,22 @@ NEVidebla-MESago (invisible message)
* install dependencies * install dependencies
* ubuntu example: `sudo apt update -y && sudo apt upgrade -y` * ubuntu example: `sudo apt update -y && sudo apt upgrade -y`
* `sudo apt install -y libssl-dev build-essential libgpgme-dev` * `sudo apt install -y libssl-dev build-essential libgpgme-dev`
* download and run i2prouter start (optional: setup to run on boot similar tor daemon)
* `git clone https://github/com/creating2morrow/nevmes` * `git clone https://github/com/creating2morrow/nevmes`
* `cd nevmes && ./scripts/build_all_and_run.sh "-- -h"` * `cd nevmes && ./scripts/build_all_and_run.sh "-- -h"`
* gui built with rust [egui](https://docs.rs/egui/latest/egui/) * gui built with rust [egui](https://docs.rs/egui/latest/egui/)
## Installation Mananger
* additional required software can be downloaded from the gui home or `Binaries` links below
* hashes are in core [lib.rs](./nevmes-core/src/lib.rs)
* download and run i2p
* `/i2p/i2prouter start`
* `/i2p/i2prouter install` (optional: setup to run on boot similar to tor daemon)
* download i2p-zero, put the path in the connection manager or cli `--i2p-zero-dir` flag
* download monero, update connection manager or cli
* `--monero-blockchain-dir`, where to put lmdb for monero (e.g. path/to/ssd)
* `--monero-location`, path to monero download
## Contributing and Releasing ## Contributing and Releasing
```bash ```bash
@ -36,7 +47,10 @@ NEVidebla-MESago (invisible message)
``` ```
* code on dev branch * code on dev branch
* run `./scripts/fmtall.sh` before committing
* pull request to dev branch * pull request to dev branch
* todo => `TODO(name): detailed work`
* docs on all `pub fn` and `pub struct`
* merge dev to vX.X.X * merge dev to vX.X.X
* merge vX.X.X to main * merge vX.X.X to main
* tag release v.X.X.X every friday (if stable changes) * tag release v.X.X.X every friday (if stable changes)
@ -63,7 +77,6 @@ NEVidebla-MESago (invisible message)
* nevmes-auth - `internal` auth server * nevmes-auth - `internal` auth server
* nevmes-contact - `internal` add contacts server * nevmes-contact - `internal` add contacts server
* nevmes-core - application core logic
* nevmes-gui - primary user interface * nevmes-gui - primary user interface
* nevmes-message - `internal` message tx/read etc. server * nevmes-message - `internal` message tx/read etc. server
* nevmes - `external` primary server for contact share, payment, message rx etc. * nevmes - `external` primary server for contact share, payment, message rx etc.
@ -74,16 +87,8 @@ NEVidebla-MESago (invisible message)
* [i2p-zero](https://github.com/i2p-zero/i2p-zero/releases/tag/v1.20) - (not included) tunnel creation * [i2p-zero](https://github.com/i2p-zero/i2p-zero/releases/tag/v1.20) - (not included) tunnel creation
* [i2p](https://geti2p.net/en/download) - http proxy (not included, *i2p-zero http proxy not working) * [i2p](https://geti2p.net/en/download) - http proxy (not included, *i2p-zero http proxy not working)
most of the complex logic stays in nevmes-core, exported from [lib.rs](./nevmes-core/src/lib.rs)
## Manual ## Manual
[the manual](./docs/man.md) [the manual](./docs/man.md)
## Known issues
* gui password and screen lock needs fixing up
* timeout out JWP payment approval screen with infinite loading
* prove payment edge where payment succeeds but jwp is empty, currently require new payment
* test framework (in progress)
* docs on all `fn` and `structs`
* i2pd installer on home screen?
* and more daemon info and wallet functionality (multisig)

View file

@ -0,0 +1,6 @@
# cargo +nightly fmt
comment_width = 100
format_code_in_doc_comments = true
imports_granularity = "Crate"
imports_layout = "Vertical"
wrap_comments = true

View file

@ -1,17 +1,20 @@
use rocket::http::Status; use rocket::{
use rocket::response::status::Custom; get,
use rocket::serde::json::Json; http::Status,
use rocket::get; response::status::Custom,
serde::json::Json,
};
use nevmes_core::{auth, models::*}; use nevmes_core::{
auth,
models::*,
};
/// Login with wallet signature /// Login with wallet signature
/// ///
/// Creates user on initial login /// Creates user on initial login
///
#[get("/login/<signature>/<aid>/<uid>")] #[get("/login/<signature>/<aid>/<uid>")]
pub async fn login pub async fn login(aid: String, uid: String, signature: String) -> Custom<Json<Authorization>> {
(aid: String, uid: String,signature: String) -> Custom<Json<Authorization>> {
let m_auth: Authorization = auth::verify_login(aid, uid, signature).await; let m_auth: Authorization = auth::verify_login(aid, uid, signature).await;
Custom(Status::Created, Json(m_auth)) Custom(Status::Created, Json(m_auth))
} }

View file

@ -14,6 +14,5 @@ async fn rocket() -> _ {
}; };
env_logger::init(); env_logger::init();
log::info!("nevmes-auth is online"); log::info!("nevmes-auth is online");
rocket::custom(&config) rocket::custom(&config).mount("/", routes![controller::login])
.mount("/", routes![controller::login])
} }

View file

@ -0,0 +1,6 @@
# cargo +nightly fmt
comment_width = 100
format_code_in_doc_comments = true
imports_granularity = "Crate"
imports_layout = "Vertical"
wrap_comments = true

View file

@ -1,41 +1,53 @@
use rocket::http::Status; use rocket::{
use rocket::response::status::Custom; get,
use rocket::serde::json::Json; http::Status,
use rocket::{get, post}; post,
response::status::Custom,
serde::json::Json,
};
use nevmes_core::{auth, contact, models::*, proof, utils, reqres}; use nevmes_core::{
auth,
contact,
models::*,
proof,
reqres,
utils,
};
/// Add contact /// Add contact
#[post("/", data="<req_contact>")] #[post("/", data = "<req_contact>")]
pub async fn add_contact pub async fn add_contact(
(req_contact: Json<Contact>,_token: auth::BearerToken) -> Custom<Json<Contact>> { req_contact: Json<Contact>,
_token: auth::BearerToken,
) -> Custom<Json<Contact>> {
let res_contact = contact::create(&req_contact).await; let res_contact = contact::create(&req_contact).await;
if res_contact.cid == utils::empty_string() { if res_contact.cid == utils::empty_string() {
return Custom(Status::BadRequest, Json(Default::default())) return Custom(Status::BadRequest, Json(Default::default()));
} }
Custom(Status::Ok, Json(res_contact)) Custom(Status::Ok, Json(res_contact))
} }
/// Return all contacts /// Return all contacts
#[get("/")] #[get("/")]
pub async fn get_contacts pub async fn get_contacts(_token: auth::BearerToken) -> Custom<Json<Vec<Contact>>> {
(_token: auth::BearerToken) -> Custom<Json<Vec<Contact>>> {
let contacts = contact::find_all(); let contacts = contact::find_all();
Custom(Status::Ok, Json(contacts)) Custom(Status::Ok, Json(contacts))
} }
/// trust contact /// trust contact
#[post("/<key>")] #[post("/<key>")]
pub async fn trust_contact pub async fn trust_contact(key: String, _token: auth::BearerToken) -> Status {
(key: String, _token: auth::BearerToken) -> Status {
contact::trust_gpg(key); contact::trust_gpg(key);
Status::Ok Status::Ok
} }
/// prove payment /// prove payment
#[get("/<contact>", data="<proof_req>")] #[get("/<contact>", data = "<proof_req>")]
pub async fn prove_payment pub async fn prove_payment(
(contact: String, proof_req: Json<proof::TxProof>, _token: auth::BearerToken contact: String,
proof_req: Json<proof::TxProof>,
_token: auth::BearerToken,
) -> Custom<Json<reqres::Jwp>> { ) -> Custom<Json<reqres::Jwp>> {
let r_jwp = proof::prove_payment(contact, &proof_req).await; let r_jwp = proof::prove_payment(contact, &proof_req).await;
Custom(Status::Ok, Json(r_jwp.unwrap())) Custom(Status::Ok, Json(r_jwp.unwrap()))

View file

@ -1,13 +1,31 @@
use crate::{args, models::*, db, monero, reqres, user, utils}; use crate::{
args,
db,
models::*,
monero,
reqres,
user,
utils,
};
use clap::Parser; use clap::Parser;
use log::{debug, error, info}; use log::{
debug,
error,
info,
};
use rocket::http::Status; use rocket::{
use rocket::outcome::Outcome; http::Status,
use rocket::request::FromRequest; outcome::Outcome,
use rocket::{request, Request}; request,
request::FromRequest,
Request,
};
use hmac::{Hmac, Mac}; use hmac::{
Hmac,
Mac,
};
use jwt::*; use jwt::*;
use sha2::Sha384; use sha2::Sha384;
use std::collections::BTreeMap; use std::collections::BTreeMap;
@ -70,8 +88,7 @@ fn update_expiration(f_auth: Authorization, address: &String) -> Authorization {
} }
/// Performs the signature verfication against stored auth /// Performs the signature verfication against stored auth
pub async fn verify_login pub async fn verify_login(aid: String, uid: String, signature: String) -> Authorization {
(aid: String, uid: String, signature: String) -> Authorization {
let m_address: reqres::XmrRpcAddressResponse = monero::get_address().await; let m_address: reqres::XmrRpcAddressResponse = monero::get_address().await;
let address = m_address.result.address; let address = m_address.result.address;
let f_auth: Authorization = find(&aid); let f_auth: Authorization = find(&aid);
@ -94,16 +111,23 @@ pub async fn verify_login
let u_auth = Authorization::update_uid(f_auth, String::from(&u.uid)); let u_auth = Authorization::update_uid(f_auth, String::from(&u.uid));
let s = db::Interface::open(); let s = db::Interface::open();
db::Interface::delete(&s.env, &s.handle, &u_auth.aid); db::Interface::delete(&s.env, &s.handle, &u_auth.aid);
db::Interface::write(&s.env, &s.handle, &u_auth.aid, &Authorization::to_db(&u_auth)); db::Interface::write(
return u_auth &s.env,
&s.handle,
&u_auth.aid,
&Authorization::to_db(&u_auth),
);
return u_auth;
} else if f_user.xmr_address != utils::empty_string() { } else if f_user.xmr_address != utils::empty_string() {
info!("returning user"); info!("returning user");
let m_access = verify_access(&address, &signature).await; let m_access = verify_access(&address, &signature).await;
if !m_access { return Default::default() } if !m_access {
return Default::default();
}
return f_auth; return f_auth;
} else { } else {
error!("error creating user"); error!("error creating user");
return Default::default() return Default::default();
} }
} }
@ -172,7 +196,7 @@ impl<'r> FromRequest<'r> for BearerToken {
let env = utils::get_release_env(); let env = utils::get_release_env();
let dev = utils::ReleaseEnvironment::Development; let dev = utils::ReleaseEnvironment::Development;
if env == dev { if env == dev {
return Outcome::Success(BearerToken(utils::empty_string())) return Outcome::Success(BearerToken(utils::empty_string()));
} }
let token = request.headers().get_one("token"); let token = request.headers().get_one("token");
let m_address: reqres::XmrRpcAddressResponse = monero::get_address().await; let m_address: reqres::XmrRpcAddressResponse = monero::get_address().await;

View file

@ -1,7 +1,19 @@
// Contact repo/service layer // Contact repo/service layer
use crate::{db, gpg, models::*, utils, reqres, monero, i2p}; use crate::{
db,
gpg,
i2p,
models::*,
monero,
reqres,
utils,
};
use log::{
debug,
error,
info,
};
use rocket::serde::json::Json; use rocket::serde::json::Json;
use log::{debug, error, info};
use std::error::Error; use std::error::Error;
/// Create a new contact /// Create a new contact
@ -42,7 +54,7 @@ pub fn find(cid: &String) -> Contact {
let r = db::Interface::read(&s.env, &s.handle, &String::from(cid)); let r = db::Interface::read(&s.env, &s.handle, &String::from(cid));
if r == utils::empty_string() { if r == utils::empty_string() {
error!("contact not found"); error!("contact not found");
return Default::default() return Default::default();
} }
Contact::from_db(String::from(cid), r) Contact::from_db(String::from(cid), r)
} }
@ -54,7 +66,7 @@ pub fn find_all() -> Vec<Contact> {
let r = db::Interface::read(&s.env, &s.handle, &String::from(list_key)); let r = db::Interface::read(&s.env, &s.handle, &String::from(list_key));
if r == utils::empty_string() { if r == utils::empty_string() {
error!("contact index not found"); error!("contact index not found");
return Default::default() return Default::default();
} }
let v_cid = r.split(","); let v_cid = r.split(",");
let v: Vec<String> = v_cid.map(|s| String::from(s)).collect(); let v: Vec<String> = v_cid.map(|s| String::from(s)).collect();
@ -72,10 +84,10 @@ async fn validate_contact(j: &Json<Contact>) -> bool {
info!("validating contact: {}", &j.cid); info!("validating contact: {}", &j.cid);
let validate_address = monero::validate_address(&j.xmr_address).await; let validate_address = monero::validate_address(&j.xmr_address).await;
j.cid.len() < utils::string_limit() j.cid.len() < utils::string_limit()
&& j.i2p_address.len() < utils::string_limit() && j.i2p_address.len() < utils::string_limit()
&& j.i2p_address.contains(".b32.i2p") && j.i2p_address.contains(".b32.i2p")
&& j.gpg_key.len() < utils::gpg_key_limit() && j.gpg_key.len() < utils::gpg_key_limit()
&& validate_address.result.valid && validate_address.result.valid
} }
/// Send our information /// Send our information
@ -84,26 +96,35 @@ pub async fn share() -> Contact {
let gpg_key = gpg::export_key().unwrap_or(Vec::new()); let gpg_key = gpg::export_key().unwrap_or(Vec::new());
let i2p_address = i2p::get_destination(); let i2p_address = i2p::get_destination();
let xmr_address = m_address.result.address; let xmr_address = m_address.result.address;
Contact { cid: utils::empty_string(),gpg_key,i2p_address,xmr_address } Contact {
cid: utils::empty_string(),
gpg_key,
i2p_address,
xmr_address,
}
} }
pub fn exists(from: &String) -> bool { pub fn exists(from: &String) -> bool {
let all = find_all(); let all = find_all();
let mut addresses: Vec<String> = Vec::new(); let mut addresses: Vec<String> = Vec::new();
for c in all { addresses.push(c.i2p_address); } for c in all {
addresses.push(c.i2p_address);
}
return addresses.contains(from); return addresses.contains(from);
} }
/// Sign for trusted nevmes contacts /// Sign for trusted nevmes contacts
/// ///
/// UI/UX should have some prompt about the implication of trusting keys /// UI/UX should have some prompt about the implication of trusting keys
/// ///
/// however that is beyond the scope of this app. nevmes assumes contacts /// however that is beyond the scope of this app. nevmes assumes contacts
/// ///
/// using the app already have some level of knowledge about each other. /// using the app already have some level of knowledge about each other.
/// ///
/// Without signing the key message encryption and sending is not possible. /// Without signing the key message encryption and sending is not possible.
pub fn trust_gpg(key: String) { gpg::sign_key(&key).unwrap(); } pub fn trust_gpg(key: String) {
gpg::sign_key(&key).unwrap();
}
/// Get invoice for jwp creation /// Get invoice for jwp creation
pub async fn request_invoice(contact: String) -> Result<reqres::Invoice, Box<dyn Error>> { pub async fn request_invoice(contact: String) -> Result<reqres::Invoice, Box<dyn Error>> {
@ -111,14 +132,16 @@ pub async fn request_invoice(contact: String) -> Result<reqres::Invoice, Box<dyn
let host = utils::get_i2p_http_proxy(); let host = utils::get_i2p_http_proxy();
let proxy = reqwest::Proxy::http(&host)?; let proxy = reqwest::Proxy::http(&host)?;
let client = reqwest::Client::builder().proxy(proxy).build(); let client = reqwest::Client::builder().proxy(proxy).build();
match client?.get(format!("http://{}/invoice", contact)).send().await { match client?
.get(format!("http://{}/invoice", contact))
.send()
.await
{
Ok(response) => { Ok(response) => {
let res = response.json::<reqres::Invoice>().await; let res = response.json::<reqres::Invoice>().await;
debug!("invoice request response: {:?}", res); debug!("invoice request response: {:?}", res);
match res { match res {
Ok(r) => { Ok(r) => Ok(r),
Ok(r)
},
_ => Ok(Default::default()), _ => Ok(Default::default()),
} }
} }
@ -135,14 +158,16 @@ pub async fn add_contact_request(contact: String) -> Result<Contact, Box<dyn Err
let host = utils::get_i2p_http_proxy(); let host = utils::get_i2p_http_proxy();
let proxy = reqwest::Proxy::http(&host)?; let proxy = reqwest::Proxy::http(&host)?;
let client = reqwest::Client::builder().proxy(proxy).build(); let client = reqwest::Client::builder().proxy(proxy).build();
match client?.get(format!("http://{}/share", contact)).send().await { match client?
.get(format!("http://{}/share", contact))
.send()
.await
{
Ok(response) => { Ok(response) => {
let res = response.json::<Contact>().await; let res = response.json::<Contact>().await;
debug!("share response: {:?}", res); debug!("share response: {:?}", res);
match res { match res {
Ok(r) => { Ok(r) => Ok(r),
Ok(r)
},
_ => Ok(Default::default()), _ => Ok(Default::default()),
} }
} }

View file

@ -1,8 +1,16 @@
// db created and exported from here // db created and exported from here
extern crate lmdb_rs as lmdb; extern crate lmdb_rs as lmdb;
use log::{debug, error}; use lmdb::{
use lmdb::{EnvBuilder, DbFlags, Environment, DbHandle}; DbFlags,
DbHandle,
EnvBuilder,
Environment,
};
use log::{
debug,
error,
};
use crate::utils; use crate::utils;
@ -14,10 +22,17 @@ pub struct Interface {
impl Interface { impl Interface {
pub fn open() -> Self { pub fn open() -> Self {
let release_env = utils::get_release_env(); let release_env = utils::get_release_env();
let file_path = format!("/home/{}/.nevmes/", std::env::var("USER").unwrap_or(String::from("user"))); let file_path = format!(
"/home/{}/.nevmes/",
std::env::var("USER").unwrap_or(String::from("user"))
);
let mut env_str: &str = "test-lmdb"; let mut env_str: &str = "test-lmdb";
if release_env != utils::ReleaseEnvironment::Development { env_str = "lmdb"; }; if release_env != utils::ReleaseEnvironment::Development {
let env = EnvBuilder::new().open(format!("{}/{}", file_path, env_str), 0o777).unwrap(); env_str = "lmdb";
};
let env = EnvBuilder::new()
.open(format!("{}/{}", file_path, env_str), 0o777)
.unwrap();
let handle = env.get_default_db(DbFlags::empty()).unwrap(); let handle = env.get_default_db(DbFlags::empty()).unwrap();
Interface { env, handle } Interface { env, handle }
} }
@ -25,13 +40,15 @@ impl Interface {
let txn = e.new_transaction().unwrap(); let txn = e.new_transaction().unwrap();
{ {
// get a database bound to this transaction // get a database bound to this transaction
let db = txn.bind(&h); let db = txn.bind(&h);
let pair = vec![(k,v)]; let pair = vec![(k, v)];
for &(key, value) in pair.iter() { db.set(&key, &value).unwrap(); } for &(key, value) in pair.iter() {
db.set(&key, &value).unwrap();
}
} }
match txn.commit() { match txn.commit() {
Err(_) => error!("failed to commit!"), Err(_) => error!("failed to commit!"),
Ok(_) => () Ok(_) => (),
} }
} }
pub fn read(e: &Environment, h: &DbHandle, k: &str) -> String { pub fn read(e: &Environment, h: &DbHandle, k: &str) -> String {
@ -50,12 +67,12 @@ impl Interface {
let txn = e.new_transaction().unwrap(); let txn = e.new_transaction().unwrap();
{ {
// get a database bound to this transaction // get a database bound to this transaction
let db = txn.bind(&h); let db = txn.bind(&h);
db.del::<>(&k).unwrap_or_else(|_| error!("failed to delete")); db.del(&k).unwrap_or_else(|_| error!("failed to delete"));
} }
match txn.commit() { match txn.commit() {
Err(_) => error!("failed to commit!"), Err(_) => error!("failed to commit!"),
Ok(_) => () Ok(_) => (),
} }
} }
} }

View file

@ -1,8 +1,19 @@
use log::{debug, error, info}; use crate::{
use std::process::Command; i2p,
utils,
};
use gpgme::*; use gpgme::*;
use std::{error::Error, fs::File, io::Write}; use log::{
use crate::{utils, i2p}; debug,
error,
info,
};
use std::{
error::Error,
fs::File,
io::Write,
process::Command,
};
/// Searches for key, returns empty string if none exists /// Searches for key, returns empty string if none exists
/// ///
@ -73,7 +84,8 @@ pub fn import_key(cid: String, key: Vec<u8>) -> Result<(), Box<dyn Error>> {
let mut data = Data::from_seekable_stream(input)?; let mut data = Data::from_seekable_stream(input)?;
let mode = None; let mode = None;
mode.map(|m| data.set_encoding(m)); mode.map(|m| data.set_encoding(m));
ctx.import(&mut data).map_err(|e| format!("import failed {:?}", e))?; ctx.import(&mut data)
.map_err(|e| format!("import failed {:?}", e))?;
utils::stage_cleanup(filename); utils::stage_cleanup(filename);
Ok(()) Ok(())
} }
@ -82,19 +94,23 @@ pub fn encrypt(name: String, body: &Vec<u8>) -> Result<Vec<u8>, Box<dyn Error>>
let proto = Protocol::OpenPgp; let proto = Protocol::OpenPgp;
let mut ctx = Context::from_protocol(proto)?; let mut ctx = Context::from_protocol(proto)?;
ctx.set_armor(true); ctx.set_armor(true);
let keys: Vec<Key> = ctx.find_keys([&name])? let keys: Vec<Key> = ctx
.find_keys([&name])?
.filter_map(|x| x.ok()) .filter_map(|x| x.ok())
.filter(|k| k.can_encrypt()) .filter(|k| k.can_encrypt())
.collect(); .collect();
let filename = format!("{}.nevmes", name); let filename = format!("{}.nevmes", name);
let mut f = File::create(&filename)?; let mut f = File::create(&filename)?;
f.write_all(body)?; f.write_all(body)?;
let mut input = File::open(&filename) let mut input =
.map_err(|e| format!("can't open file `{}': {}", filename, e))?; File::open(&filename).map_err(|e| format!("can't open file `{}': {}", filename, e))?;
let mut output = Vec::new(); let mut output = Vec::new();
ctx.encrypt(&keys, &mut input, &mut output) ctx.encrypt(&keys, &mut input, &mut output)
.map_err(|e| format!("encrypting failed: {:?}", e))?; .map_err(|e| format!("encrypting failed: {:?}", e))?;
debug!("encrypted message body: {}", String::from_utf8(output.iter().cloned().collect()).unwrap_or(utils::empty_string())); debug!(
"encrypted message body: {}",
String::from_utf8(output.iter().cloned().collect()).unwrap_or(utils::empty_string())
);
utils::stage_cleanup(filename); utils::stage_cleanup(filename);
Ok(output) Ok(output)
} }
@ -106,8 +122,8 @@ pub fn decrypt(mid: &String, body: &Vec<u8>) -> Result<Vec<u8>, Box<dyn Error>>
let filename = format!("{}.nevmes", mid); let filename = format!("{}.nevmes", mid);
let mut f = File::create(&filename)?; let mut f = File::create(&filename)?;
f.write_all(&body)?; f.write_all(&body)?;
let mut input = File::open(&filename) let mut input =
.map_err(|e| format!("can't open file `{}': {}", filename, e))?; File::open(&filename).map_err(|e| format!("can't open file `{}': {}", filename, e))?;
let mut output = Vec::new(); let mut output = Vec::new();
ctx.decrypt(&mut input, &mut output) ctx.decrypt(&mut input, &mut output)
.map_err(|e| format!("decrypting failed: {:?}", e))?; .map_err(|e| format!("decrypting failed: {:?}", e))?;
@ -125,7 +141,8 @@ pub fn write_gen_batch() -> Result<(), Box<dyn Error>> {
Subkey-Curve: Curve25519 Subkey-Curve: Curve25519
Name-Real: {} Name-Real: {}
Name-Email: {} Name-Email: {}
Expire-Date: 0", name, name Expire-Date: 0",
name, name
); );
let filename = format!("genkey-batch"); let filename = format!("genkey-batch");
let mut f = File::create(&filename)?; let mut f = File::create(&filename)?;
@ -157,11 +174,13 @@ pub fn sign_key(key: &str) -> Result<(), Box<dyn Error>> {
.get_secret_key(app_key) .get_secret_key(app_key)
.map_err(|e| format!("unable to find signing key: {:?}", e))?; .map_err(|e| format!("unable to find signing key: {:?}", e))?;
debug!("app key: {:?}", key.id()); debug!("app key: {:?}", key.id());
k2s_ctx.add_signer(&key) k2s_ctx
.add_signer(&key)
.map_err(|e| format!("add_signer() failed: {:?}", e))?; .map_err(|e| format!("add_signer() failed: {:?}", e))?;
} }
k2s_ctx.sign_key(&key_to_sign, None::<String>, Default::default()) k2s_ctx
.sign_key(&key_to_sign, None::<String>, Default::default())
.map_err(|e| format!("signing failed: {:?}", e))?; .map_err(|e| format!("signing failed: {:?}", e))?;
println!("Signed key for {}", key); println!("Signed key for {}", key);

View file

@ -1,9 +1,24 @@
use std::{fs, env, process::Command}; use crate::{
use log::{debug, error, info, warn}; args,
use serde::{Deserialize, Serialize}; utils,
use crate::{args, utils}; };
use clap::Parser; use clap::Parser;
use std::time::Duration; use log::{
debug,
error,
info,
warn,
};
use serde::{
Deserialize,
Serialize,
};
use std::{
env,
fs,
process::Command,
time::Duration,
};
// TODO(c2m): debug i2p-zero http proxy // TODO(c2m): debug i2p-zero http proxy
@ -24,13 +39,13 @@ impl I2pStatus {
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub struct HttpProxyStatus { pub struct HttpProxyStatus {
pub open: bool pub open: bool,
} }
#[derive(Debug)] #[derive(Debug)]
pub enum ProxyStatus { pub enum ProxyStatus {
Opening, Opening,
Open Open,
} }
impl ProxyStatus { impl ProxyStatus {
@ -56,7 +71,9 @@ struct Tunnels {
impl Default for Tunnels { impl Default for Tunnels {
fn default() -> Self { fn default() -> Self {
Tunnels { tunnels: Vec::new() } Tunnels {
tunnels: Vec::new(),
}
} }
} }
@ -87,14 +104,14 @@ pub async fn start() {
.expect("i2p-zero failed to start"); .expect("i2p-zero failed to start");
debug!("{:?}", output.stdout); debug!("{:?}", output.stdout);
find_tunnels().await; find_tunnels().await;
{ {
tokio::spawn(async move { tokio::spawn(async move {
let tick: std::sync::mpsc::Receiver<()> = schedule_recv::periodic_ms(600000); let tick: std::sync::mpsc::Receiver<()> = schedule_recv::periodic_ms(600000);
loop { loop {
tick.recv().unwrap(); tick.recv().unwrap();
check_connection().await; check_connection().await;
} }
}); });
} }
} }
@ -103,7 +120,11 @@ fn create_tunnel() {
let args = args::Args::parse(); let args = args::Args::parse();
let path = args.i2p_zero_dir; let path = args.i2p_zero_dir;
let output = Command::new(format!("{}/router/bin/tunnel-control.sh", path)) let output = Command::new(format!("{}/router/bin/tunnel-control.sh", path))
.args(["server.create", "127.0.0.1", &format!("{}",utils::get_app_port())]) .args([
"server.create",
"127.0.0.1",
&format!("{}", utils::get_app_port()),
])
.spawn() .spawn()
.expect("i2p-zero failed to create a tunnel"); .expect("i2p-zero failed to create a tunnel");
debug!("{:?}", output.stdout); debug!("{:?}", output.stdout);
@ -125,17 +146,20 @@ pub fn get_destination() -> String {
env::var("USER").unwrap_or(String::from("user")) env::var("USER").unwrap_or(String::from("user"))
); );
// Don't panic if i2p-zero isn't installed // Don't panic if i2p-zero isn't installed
let contents = match fs::read_to_string(file_path) let contents = match fs::read_to_string(file_path) {
{ Ok(file) => file,
Ok(file) => file, _ => utils::empty_string(),
_=> utils::empty_string(), };
};
if contents != utils::empty_string() { if contents != utils::empty_string() {
let input = format!(r#"{contents}"#); let input = format!(r#"{contents}"#);
let mut j: Tunnels = serde_json::from_str(&input).unwrap_or(Default::default()); let mut j: Tunnels = serde_json::from_str(&input).unwrap_or(Default::default());
let destination: String = j.tunnels.remove(0).dest.ok_or(utils::empty_string()) let destination: String = j
.tunnels
.remove(0)
.dest
.ok_or(utils::empty_string())
.unwrap_or(utils::empty_string()); .unwrap_or(utils::empty_string());
return destination return destination;
} }
utils::empty_string() utils::empty_string()
} }
@ -168,9 +192,9 @@ pub async fn check_connection() -> HttpProxyStatus {
} }
} }
_ => { _ => {
error!("i2p status check failure"); error!("i2p status check failure");
return HttpProxyStatus { open: false }; return HttpProxyStatus { open: false };
} }
} }
} }
Err(e) => { Err(e) => {

View file

@ -1,27 +1,30 @@
pub mod args; // command line arguments pub mod args; // command line arguments
pub mod auth; // internal auth repo/service layer pub mod auth; // internal auth repo/service layer
pub mod contact; // contact repo/service layer pub mod contact; // contact repo/service layer
pub mod db; // lmdb interface pub mod db; // lmdb interface
pub mod gpg; // gpgme interface pub mod gpg; // gpgme interface
pub mod i2p; // i2p repo/service layer pub mod i2p; // i2p repo/service layer
pub mod message; // message repo/service layer pub mod message; // message repo/service layer
pub mod models; // db structs pub mod models; // db structs
pub mod monero; // monero-wallet-rpc interface pub mod monero; // monero-wallet-rpc interface
pub mod proof; // external auth/payment proof module pub mod proof; // external auth/payment proof module
pub mod reqres; // http request/responses pub mod reqres; // http request/responses
pub mod utils; // misc. pub mod user;
pub mod user; // user rep/service layer pub mod utils; // misc. // user rep/service layer
pub const NEVMES_JWP_SECRET_KEY: &str = "NEVMES_JWP_SECRET_KEY"; pub const NEVMES_JWP_SECRET_KEY: &str = "NEVMES_JWP_SECRET_KEY";
pub const NEVMES_JWT_SECRET_KEY: &str = "NEVMES_JWT_SECRET_KEY"; pub const NEVMES_JWT_SECRET_KEY: &str = "NEVMES_JWT_SECRET_KEY";
/// The latest monero release download /// The latest monero release download
pub const MONERO_RELEASE_VERSION: &str = "monero-linux-x64-v0.18.2.2.tar.bz2"; pub const MONERO_RELEASE_VERSION: &str = "monero-linux-x64-v0.18.2.2.tar.bz2";
pub const MONERO_RELEASE_HASH: &str = "186800de18f67cca8475ce392168aabeb5709a8f8058b0f7919d7c693786d56b"; pub const MONERO_RELEASE_HASH: &str =
"186800de18f67cca8475ce392168aabeb5709a8f8058b0f7919d7c693786d56b";
/// The latest i2p-zero release version /// The latest i2p-zero release version
pub const I2P_ZERO_RELEASE_VERSION: &str = "v1.20"; pub const I2P_ZERO_RELEASE_VERSION: &str = "v1.20";
pub const I2P_ZERO_RELEASH_HASH: &str = "7e7216b281624ec464b55217284017576d109eaba7b35f7e4994ae2a78634de7"; pub const I2P_ZERO_RELEASH_HASH: &str =
"7e7216b281624ec464b55217284017576d109eaba7b35f7e4994ae2a78634de7";
/// The latest i2pd release version /// The latest i2pd release version
pub const I2P_RELEASE_VERSION: &str = "2.2.1"; pub const I2P_RELEASE_VERSION: &str = "2.2.1";
pub const I2P_RELEASE_HASH: &str = "c9879b8f69ea13c758672c2fa083dc2e0abb289e0fc9a55af98f9f1795f82659"; pub const I2P_RELEASE_HASH: &str =
"c9879b8f69ea13c758672c2fa083dc2e0abb289e0fc9a55af98f9f1795f82659";
// DO NOT EDIT BELOW THIS LINE // DO NOT EDIT BELOW THIS LINE

View file

@ -1,9 +1,21 @@
// Message repo/service layer // Message repo/service layer
use crate::{contact, db, models::*, utils, reqres, i2p, gpg}; use crate::{
use std::error::Error; contact,
use log::{debug, error, info}; db,
gpg,
i2p,
models::*,
reqres,
utils,
};
use log::{
debug,
error,
info,
};
use reqwest::StatusCode; use reqwest::StatusCode;
use rocket::serde::json::Json; use rocket::serde::json::Json;
use std::error::Error;
/// Create a new message /// Create a new message
pub async fn create(m: Json<Message>, jwp: String) -> Message { pub async fn create(m: Json<Message>, jwp: String) -> Message {
@ -12,8 +24,7 @@ pub async fn create(m: Json<Message>, jwp: String) -> Message {
let created = chrono::offset::Utc::now().timestamp(); let created = chrono::offset::Utc::now().timestamp();
// get contact public gpg key and encrypt the message // get contact public gpg key and encrypt the message
debug!("sending message: {:?}", &m); debug!("sending message: {:?}", &m);
let e_body = gpg::encrypt( let e_body = gpg::encrypt(String::from(&m.to), &m.body).unwrap_or(Vec::new());
String::from(&m.to), &m.body).unwrap_or(Vec::new());
let new_message = Message { let new_message = Message {
mid: String::from(&f_mid), mid: String::from(&f_mid),
uid: String::from(&m.uid), uid: String::from(&m.uid),
@ -45,10 +56,14 @@ pub async fn create(m: Json<Message>, jwp: String) -> Message {
pub async fn rx(m: Json<Message>) { pub async fn rx(m: Json<Message>) {
// make sure the message isn't something strange // make sure the message isn't something strange
let is_valid = validate_message(&m); let is_valid = validate_message(&m);
if !is_valid { return; } if !is_valid {
return;
}
// don't allow messages from outside the contact list // don't allow messages from outside the contact list
let is_in_contact_list = contact::exists(&m.from); let is_in_contact_list = contact::exists(&m.from);
if !is_in_contact_list { return; } if !is_in_contact_list {
return;
}
let f_mid: String = format!("m{}", utils::generate_rnd()); let f_mid: String = format!("m{}", utils::generate_rnd());
let new_message = Message { let new_message = Message {
mid: String::from(&f_mid), mid: String::from(&f_mid),
@ -79,7 +94,7 @@ pub fn find(mid: &String) -> Message {
let r = db::Interface::read(&s.env, &s.handle, &String::from(mid)); let r = db::Interface::read(&s.env, &s.handle, &String::from(mid));
if r == utils::empty_string() { if r == utils::empty_string() {
error!("message not found"); error!("message not found");
return Default::default() return Default::default();
} }
Message::from_db(String::from(mid), r) Message::from_db(String::from(mid), r)
} }
@ -120,32 +135,38 @@ pub fn find_all() -> Vec<Message> {
/// Tx message /// Tx message
async fn send_message(out: &Message, jwp: &str) -> Result<(), Box<dyn Error>> { async fn send_message(out: &Message, jwp: &str) -> Result<(), Box<dyn Error>> {
let host = utils::get_i2p_http_proxy(); let host = utils::get_i2p_http_proxy();
let proxy = reqwest::Proxy::http(&host)?; let proxy = reqwest::Proxy::http(&host)?;
let client = reqwest::Client::builder().proxy(proxy).build(); let client = reqwest::Client::builder().proxy(proxy).build();
// check if the contact is online // check if the contact is online
let is_online: bool = is_contact_online(&out.to, String::from(jwp)).await.unwrap_or(false); let is_online: bool = is_contact_online(&out.to, String::from(jwp))
.await
.unwrap_or(false);
if is_online { if is_online {
return match client?.post(format!("http://{}/message/rx", out.to)) return match client?
.header("proof", jwp).json(&out).send().await { .post(format!("http://{}/message/rx", out.to))
Ok(response) => { .header("proof", jwp)
let status = response.status(); .json(&out)
debug!("send response: {:?}", status.as_str()); .send()
if status == StatusCode::OK || status == StatusCode::PAYMENT_REQUIRED { .await
remove_from_fts(String::from(&out.mid)); {
return Ok(()) Ok(response) => {
} else { let status = response.status();
Ok(()) debug!("send response: {:?}", status.as_str());
} if status == StatusCode::OK || status == StatusCode::PAYMENT_REQUIRED {
} remove_from_fts(String::from(&out.mid));
Err(e) => { return Ok(());
error!("failed to send message due to: {:?}", e); } else {
Ok(()) Ok(())
} }
} }
} else { Err(e) => {
error!("failed to send message due to: {:?}", e);
Ok(())
}
};
} else {
send_to_retry(String::from(&out.mid)).await; send_to_retry(String::from(&out.mid)).await;
Ok(()) Ok(())
} }
@ -170,23 +191,31 @@ async fn is_contact_online(contact: &String, jwp: String) -> Result<bool, Box<dy
let host = utils::get_i2p_http_proxy(); let host = utils::get_i2p_http_proxy();
let proxy = reqwest::Proxy::http(&host)?; let proxy = reqwest::Proxy::http(&host)?;
let client = reqwest::Client::builder().proxy(proxy).build(); let client = reqwest::Client::builder().proxy(proxy).build();
match client?.get(format!("http://{}/xmr/rpc/version", contact)) match client?
.header("proof", jwp).send().await { .get(format!("http://{}/xmr/rpc/version", contact))
Ok(response) => { .header("proof", jwp)
let res = response.json::<reqres::XmrRpcVersionResponse>().await; .send()
debug!("check is contact online by version response: {:?}", res); .await
match res { {
Ok(r) => { Ok(response) => {
if r.result.version != 0 { Ok(true) } else { Ok(false) } let res = response.json::<reqres::XmrRpcVersionResponse>().await;
}, debug!("check is contact online by version response: {:?}", res);
_ => Ok(false), match res {
Ok(r) => {
if r.result.version != 0 {
Ok(true)
} else {
Ok(false)
}
} }
} _ => Ok(false),
Err(e) => {
error!("failed to send message due to: {:?}", e);
Ok(false)
} }
} }
Err(e) => {
error!("failed to send message due to: {:?}", e);
Ok(false)
}
}
} }
/// stage message for async retry /// stage message for async retry
@ -204,7 +233,10 @@ async fn send_to_retry(mid: String) {
if String::from(&r).contains(&String::from(&mid)) { if String::from(&r).contains(&String::from(&mid)) {
msg_list = r; msg_list = r;
} }
debug!("writing fts message index {} for id: {}", msg_list, list_key); debug!(
"writing fts message index {} for id: {}",
msg_list, list_key
);
db::Interface::write(&s.env, &s.handle, &String::from(list_key), &msg_list); db::Interface::write(&s.env, &s.handle, &String::from(list_key), &msg_list);
// restart fts if not empty // restart fts if not empty
let list_key = format!("fts"); let list_key = format!("fts");
@ -230,16 +262,27 @@ fn remove_from_fts(mid: String) {
debug!("fts is empty"); debug!("fts is empty");
} }
let pre_v_fts = r.split(","); let pre_v_fts = r.split(",");
let v: Vec<String> = pre_v_fts.map(|s| if s != &mid { String::from(s)} else { utils::empty_string()} ).collect(); let v: Vec<String> = pre_v_fts
.map(|s| {
if s != &mid {
String::from(s)
} else {
utils::empty_string()
}
})
.collect();
let msg_list = v.join(","); let msg_list = v.join(",");
debug!("writing fts message index {} for id: {}", msg_list, list_key); debug!(
"writing fts message index {} for id: {}",
msg_list, list_key
);
db::Interface::write(&s.env, &s.handle, &String::from(list_key), &msg_list); db::Interface::write(&s.env, &s.handle, &String::from(list_key), &msg_list);
} }
/// Triggered on app startup, retries to send fts every minute /// Triggered on app startup, retries to send fts every minute
/// ///
/// FTS thread terminates when empty and gets restarted on the next /// FTS thread terminates when empty and gets restarted on the next
/// ///
/// failed-to-send message. /// failed-to-send message.
pub async fn retry_fts() { pub async fn retry_fts() {
let tick: std::sync::mpsc::Receiver<()> = schedule_recv::periodic_ms(60000); let tick: std::sync::mpsc::Receiver<()> = schedule_recv::periodic_ms(60000);
@ -284,15 +327,14 @@ pub async fn retry_fts() {
fn validate_message(j: &Json<Message>) -> bool { fn validate_message(j: &Json<Message>) -> bool {
info!("validating message: {}", &j.mid); info!("validating message: {}", &j.mid);
j.mid.len() < utils::string_limit() j.mid.len() < utils::string_limit()
&& j.body.len() < utils::message_limit() && j.body.len() < utils::message_limit()
&& j.to == i2p::get_destination() && j.to == i2p::get_destination()
&& j.uid .len() < utils::string_limit() && j.uid.len() < utils::string_limit()
} }
fn is_fts_clear(r: String) -> bool { fn is_fts_clear(r: String) -> bool {
let v_mid = r.split(","); let v_mid = r.split(",");
let v: Vec<String> = v_mid.map(|s| String::from(s)).collect(); let v: Vec<String> = v_mid.map(|s| String::from(s)).collect();
debug!("fts contents: {:#?}", v); debug!("fts contents: {:#?}", v);
v.len() >= 2 && v[v.len()-1] == utils::empty_string() v.len() >= 2 && v[v.len() - 1] == utils::empty_string() && v[0] == utils::empty_string()
&& v[0] == utils::empty_string()
} }

View file

@ -1,5 +1,8 @@
use crate::utils; use crate::utils;
use rocket::serde::{Serialize, Deserialize}; use rocket::serde::{
Deserialize,
Serialize,
};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
@ -161,7 +164,14 @@ impl Message {
}; };
let from = v.remove(0); let from = v.remove(0);
let to = v.remove(0); let to = v.remove(0);
Message { mid: k, uid, body, created, from, to, } Message {
mid: k,
uid,
body,
created,
from,
to,
}
} }
} }

View file

@ -1,7 +1,20 @@
use crate::{args, reqres, utils::{self, get_release_env, ReleaseEnvironment}, proof}; use crate::{
args,
proof,
reqres,
utils::{
self,
get_release_env,
ReleaseEnvironment,
},
};
use clap::Parser; use clap::Parser;
use diqwest::WithDigestAuth; use diqwest::WithDigestAuth;
use log::{debug, error, info}; use log::{
debug,
error,
info,
};
use std::process::Command; use std::process::Command;
/// Current xmr ring size updated here. /// Current xmr ring size updated here.
@ -88,12 +101,14 @@ pub enum LockTimeLimit {
impl LockTimeLimit { impl LockTimeLimit {
pub fn value(&self) -> u64 { pub fn value(&self) -> u64 {
match *self { LockTimeLimit::Blocks => 20, } match *self {
LockTimeLimit::Blocks => 20,
}
} }
} }
/// Start monerod from the -`-monero-location` flag /// Start monerod from the -`-monero-location` flag
/// ///
/// default: /home/$USER/monero-xxx-xxx /// default: /home/$USER/monero-xxx-xxx
pub fn start_daemon() { pub fn start_daemon() {
info!("starting monerod"); info!("starting monerod");
@ -113,7 +128,7 @@ pub fn start_daemon() {
.args(args) .args(args)
.spawn() .spawn()
.expect("monerod failed to start"); .expect("monerod failed to start");
debug!("{:?}", output.stdout); debug!("{:?}", output.stdout);
} }
} }
@ -125,17 +140,22 @@ pub fn start_rpc() {
let login = get_rpc_creds(); let login = get_rpc_creds();
let daemon_address = get_rpc_daemon(); let daemon_address = get_rpc_daemon();
let rpc_login = format!("{}:{}", &login.username, &login.credential); let rpc_login = format!("{}:{}", &login.username, &login.credential);
let mut wallet_dir = format!("/home/{}/.nevmes/stagenet/wallet/", let mut wallet_dir = format!(
"/home/{}/.nevmes/stagenet/wallet/",
std::env::var("USER").unwrap_or(String::from("user")), std::env::var("USER").unwrap_or(String::from("user")),
); );
let release_env = get_release_env(); let release_env = get_release_env();
if release_env == ReleaseEnvironment::Development { if release_env == ReleaseEnvironment::Development {
let args = [ let args = [
"--rpc-bind-port", &port, "--rpc-bind-port",
"--wallet-dir", &wallet_dir, &port,
"--rpc-login", &rpc_login, "--wallet-dir",
"--daemon-address", &daemon_address, &wallet_dir,
"--stagenet" "--rpc-login",
&rpc_login,
"--daemon-address",
&daemon_address,
"--stagenet",
]; ];
let output = Command::new(format!("{}/monero-wallet-rpc", bin_dir)) let output = Command::new(format!("{}/monero-wallet-rpc", bin_dir))
.args(args) .args(args)
@ -143,11 +163,20 @@ pub fn start_rpc() {
.expect("monero-wallet-rpc failed to start"); .expect("monero-wallet-rpc failed to start");
debug!("{:?}", output.stdout); debug!("{:?}", output.stdout);
} else { } else {
wallet_dir = format!("/home/{}/.nevmes/wallet/", wallet_dir = format!(
"/home/{}/.nevmes/wallet/",
std::env::var("USER").unwrap_or(String::from("user")), std::env::var("USER").unwrap_or(String::from("user")),
); );
let args = ["--rpc-bind-port", &port, "--wallet-dir", &wallet_dir, let args = [
"--rpc-login", &rpc_login, "--daemon-address", &daemon_address]; "--rpc-bind-port",
&port,
"--wallet-dir",
&wallet_dir,
"--rpc-login",
&rpc_login,
"--daemon-address",
&daemon_address,
];
let output = Command::new(format!("{}/monero-wallet-rpc", bin_dir)) let output = Command::new(format!("{}/monero-wallet-rpc", bin_dir))
.args(args) .args(args)
.spawn() .spawn()
@ -161,7 +190,7 @@ fn get_rpc_port() -> String {
let rpc = String::from(args.monero_rpc_host); let rpc = String::from(args.monero_rpc_host);
let values = rpc.split(":"); let values = rpc.split(":");
let mut v: Vec<String> = values.map(|s| String::from(s)).collect(); let mut v: Vec<String> = values.map(|s| String::from(s)).collect();
let port = v.remove(2); let port = v.remove(2);
debug!("monero-wallet-rpc port: {}", port); debug!("monero-wallet-rpc port: {}", port);
port port
} }
@ -190,7 +219,10 @@ fn get_rpc_creds() -> RpcLogin {
let args = args::Args::parse(); let args = args::Args::parse();
let username = String::from(args.monero_rpc_username); let username = String::from(args.monero_rpc_username);
let credential = String::from(args.monero_rpc_cred); let credential = String::from(args.monero_rpc_cred);
RpcLogin { username, credential } RpcLogin {
username,
credential,
}
} }
fn get_rpc_daemon() -> String { fn get_rpc_daemon() -> String {
@ -209,8 +241,12 @@ pub async fn get_version() -> reqres::XmrRpcVersionResponse {
method: RpcFields::GetVersion.value(), method: RpcFields::GetVersion.value(),
}; };
let login: RpcLogin = get_rpc_creds(); let login: RpcLogin = get_rpc_creds();
match client.post(host).json(&req) match client
.send_with_digest_auth(&login.username, &login.credential).await { .post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => { Ok(response) => {
let res = response.json::<reqres::XmrRpcVersionResponse>().await; let res = response.json::<reqres::XmrRpcVersionResponse>().await;
debug!("get version response: {:?}", res); debug!("get version response: {:?}", res);
@ -248,8 +284,12 @@ pub async fn verify_signature(address: String, data: String, signature: String)
params, params,
}; };
let login: RpcLogin = get_rpc_creds(); let login: RpcLogin = get_rpc_creds();
match client.post(host).json(&req) match client
.send_with_digest_auth(&login.username, &login.credential).await { .post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => { Ok(response) => {
let res = response.json::<reqres::XmrRpcVerifyResponse>().await; let res = response.json::<reqres::XmrRpcVerifyResponse>().await;
debug!("verify response: {:?}", res); debug!("verify response: {:?}", res);
@ -284,8 +324,12 @@ pub async fn create_wallet(filename: String) -> bool {
params, params,
}; };
let login: RpcLogin = get_rpc_creds(); let login: RpcLogin = get_rpc_creds();
match client.post(host).json(&req) match client
.send_with_digest_auth(&login.username, &login.credential).await { .post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => { Ok(response) => {
// The result from wallet creation is empty // The result from wallet creation is empty
let res = response.text().await; let res = response.text().await;
@ -296,11 +340,11 @@ pub async fn create_wallet(filename: String) -> bool {
return false; return false;
} }
true true
}, }
_ => false, _ => false,
} }
} }
Err(_) => false Err(_) => false,
} }
} }
@ -309,9 +353,7 @@ pub async fn open_wallet(filename: String) -> bool {
info!("opening wallet for {}", &filename); info!("opening wallet for {}", &filename);
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let host = get_rpc_host(); let host = get_rpc_host();
let params = reqres::XmrRpcOpenWalletParams { let params = reqres::XmrRpcOpenWalletParams { filename };
filename,
};
let req = reqres::XmrRpcOpenRequest { let req = reqres::XmrRpcOpenRequest {
jsonrpc: RpcFields::JsonRpcVersion.value(), jsonrpc: RpcFields::JsonRpcVersion.value(),
id: RpcFields::Id.value(), id: RpcFields::Id.value(),
@ -320,8 +362,12 @@ pub async fn open_wallet(filename: String) -> bool {
}; };
debug!("open request: {:?}", req); debug!("open request: {:?}", req);
let login: RpcLogin = get_rpc_creds(); let login: RpcLogin = get_rpc_creds();
match client.post(host).json(&req) match client
.send_with_digest_auth(&login.username, &login.credential).await { .post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => { Ok(response) => {
// The result from wallet operation is empty // The result from wallet operation is empty
let res = response.text().await; let res = response.text().await;
@ -332,11 +378,11 @@ pub async fn open_wallet(filename: String) -> bool {
return false; return false;
} }
return true; return true;
}, }
_ => false, _ => false,
} }
} }
Err(_) => false Err(_) => false,
} }
} }
@ -345,9 +391,7 @@ pub async fn close_wallet(filename: String) -> bool {
info!("closing wallet for {}", &filename); info!("closing wallet for {}", &filename);
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let host = get_rpc_host(); let host = get_rpc_host();
let params = reqres::XmrRpcOpenWalletParams { let params = reqres::XmrRpcOpenWalletParams { filename };
filename,
};
let req = reqres::XmrRpcOpenRequest { let req = reqres::XmrRpcOpenRequest {
jsonrpc: RpcFields::JsonRpcVersion.value(), jsonrpc: RpcFields::JsonRpcVersion.value(),
id: RpcFields::Id.value(), id: RpcFields::Id.value(),
@ -355,8 +399,12 @@ pub async fn close_wallet(filename: String) -> bool {
params, params,
}; };
let login: RpcLogin = get_rpc_creds(); let login: RpcLogin = get_rpc_creds();
match client.post(host).json(&req) match client
.send_with_digest_auth(&login.username, &login.credential).await { .post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => { Ok(response) => {
// The result from wallet operation is empty // The result from wallet operation is empty
let res = response.text().await; let res = response.text().await;
@ -366,7 +414,7 @@ pub async fn close_wallet(filename: String) -> bool {
_ => false, _ => false,
} }
} }
Err(_) => false Err(_) => false,
} }
} }
@ -375,8 +423,11 @@ pub async fn get_balance() -> reqres::XmrRpcBalanceResponse {
info!("fetching wallet balance"); info!("fetching wallet balance");
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let host = get_rpc_host(); let host = get_rpc_host();
let params: reqres::XmrRpcBalanceParams = reqres::XmrRpcBalanceParams { let params: reqres::XmrRpcBalanceParams = reqres::XmrRpcBalanceParams {
account_index: 0, address_indices: vec![0], all_accounts: false, strict: false, account_index: 0,
address_indices: vec![0],
all_accounts: false,
strict: false,
}; };
let req = reqres::XmrRpcBalanceRequest { let req = reqres::XmrRpcBalanceRequest {
jsonrpc: RpcFields::JsonRpcVersion.value(), jsonrpc: RpcFields::JsonRpcVersion.value(),
@ -385,8 +436,12 @@ pub async fn get_balance() -> reqres::XmrRpcBalanceResponse {
params, params,
}; };
let login: RpcLogin = get_rpc_creds(); let login: RpcLogin = get_rpc_creds();
match client.post(host).json(&req) match client
.send_with_digest_auth(&login.username, &login.credential).await { .post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => { Ok(response) => {
let res = response.json::<reqres::XmrRpcBalanceResponse>().await; let res = response.json::<reqres::XmrRpcBalanceResponse>().await;
debug!("balance response: {:?}", res); debug!("balance response: {:?}", res);
@ -395,7 +450,7 @@ pub async fn get_balance() -> reqres::XmrRpcBalanceResponse {
_ => Default::default(), _ => Default::default(),
} }
} }
Err(_) => Default::default() Err(_) => Default::default(),
} }
} }
@ -404,9 +459,7 @@ pub async fn get_address() -> reqres::XmrRpcAddressResponse {
info!("fetching wallet address"); info!("fetching wallet address");
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let host = get_rpc_host(); let host = get_rpc_host();
let params: reqres::XmrRpcAddressParams = reqres::XmrRpcAddressParams { let params: reqres::XmrRpcAddressParams = reqres::XmrRpcAddressParams { account_index: 0 };
account_index: 0,
};
let req = reqres::XmrRpcAddressRequest { let req = reqres::XmrRpcAddressRequest {
jsonrpc: RpcFields::JsonRpcVersion.value(), jsonrpc: RpcFields::JsonRpcVersion.value(),
id: RpcFields::Id.value(), id: RpcFields::Id.value(),
@ -414,8 +467,12 @@ pub async fn get_address() -> reqres::XmrRpcAddressResponse {
params, params,
}; };
let login: RpcLogin = get_rpc_creds(); let login: RpcLogin = get_rpc_creds();
match client.post(host).json(&req) match client
.send_with_digest_auth(&login.username, &login.credential).await { .post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => { Ok(response) => {
let res = response.json::<reqres::XmrRpcAddressResponse>().await; let res = response.json::<reqres::XmrRpcAddressResponse>().await;
debug!("address response: {:?}", res); debug!("address response: {:?}", res);
@ -424,7 +481,7 @@ pub async fn get_address() -> reqres::XmrRpcAddressResponse {
_ => Default::default(), _ => Default::default(),
} }
} }
Err(_) => Default::default() Err(_) => Default::default(),
} }
} }
@ -433,8 +490,10 @@ pub async fn validate_address(address: &String) -> reqres::XmrRpcValidateAddress
info!("validating wallet address"); info!("validating wallet address");
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let host = get_rpc_host(); let host = get_rpc_host();
let params: reqres::XmrRpcValidateAddressParams = reqres::XmrRpcValidateAddressParams { let params: reqres::XmrRpcValidateAddressParams = reqres::XmrRpcValidateAddressParams {
address: String::from(address), any_net_type: false, allow_openalias: true, address: String::from(address),
any_net_type: false,
allow_openalias: true,
}; };
let req = reqres::XmrRpcValidateAddressRequest { let req = reqres::XmrRpcValidateAddressRequest {
jsonrpc: RpcFields::JsonRpcVersion.value(), jsonrpc: RpcFields::JsonRpcVersion.value(),
@ -443,17 +502,23 @@ pub async fn validate_address(address: &String) -> reqres::XmrRpcValidateAddress
params, params,
}; };
let login: RpcLogin = get_rpc_creds(); let login: RpcLogin = get_rpc_creds();
match client.post(host).json(&req) match client
.send_with_digest_auth(&login.username, &login.credential).await { .post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => { Ok(response) => {
let res = response.json::<reqres::XmrRpcValidateAddressResponse>().await; let res = response
.json::<reqres::XmrRpcValidateAddressResponse>()
.await;
debug!("validate_address response: {:?}", res); debug!("validate_address response: {:?}", res);
match res { match res {
Ok(res) => res, Ok(res) => res,
_ => Default::default(), _ => Default::default(),
} }
} }
Err(_) => Default::default() Err(_) => Default::default(),
} }
} }
// START Multisig // START Multisig
@ -469,8 +534,12 @@ pub async fn prepare_wallet() -> reqres::XmrRpcPrepareResponse {
method: RpcFields::Prepare.value(), method: RpcFields::Prepare.value(),
}; };
let login: RpcLogin = get_rpc_creds(); let login: RpcLogin = get_rpc_creds();
match client.post(host).json(&req) match client
.send_with_digest_auth(&login.username, &login.credential).await { .post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => { Ok(response) => {
let res = response.json::<reqres::XmrRpcPrepareResponse>().await; let res = response.json::<reqres::XmrRpcPrepareResponse>().await;
debug!("prepare response: {:?}", res); debug!("prepare response: {:?}", res);
@ -479,7 +548,7 @@ pub async fn prepare_wallet() -> reqres::XmrRpcPrepareResponse {
_ => Default::default(), _ => Default::default(),
} }
} }
Err(_) => Default::default() Err(_) => Default::default(),
} }
} }
@ -499,8 +568,12 @@ pub async fn make_wallet(info: Vec<String>) -> reqres::XmrRpcMakeResponse {
params, params,
}; };
let login: RpcLogin = get_rpc_creds(); let login: RpcLogin = get_rpc_creds();
match client.post(host).json(&req) match client
.send_with_digest_auth(&login.username, &login.credential).await { .post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => { Ok(response) => {
let res = response.json::<reqres::XmrRpcMakeResponse>().await; let res = response.json::<reqres::XmrRpcMakeResponse>().await;
debug!("make response: {:?}", res); debug!("make response: {:?}", res);
@ -509,7 +582,7 @@ pub async fn make_wallet(info: Vec<String>) -> reqres::XmrRpcMakeResponse {
_ => Default::default(), _ => Default::default(),
} }
} }
Err(_) => Default::default() Err(_) => Default::default(),
} }
} }
@ -528,8 +601,12 @@ pub async fn finalize_wallet(info: Vec<String>) -> reqres::XmrRpcFinalizeRespons
params, params,
}; };
let login: RpcLogin = get_rpc_creds(); let login: RpcLogin = get_rpc_creds();
match client.post(host).json(&req) match client
.send_with_digest_auth(&login.username, &login.credential).await { .post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => { Ok(response) => {
let res = response.json::<reqres::XmrRpcFinalizeResponse>().await; let res = response.json::<reqres::XmrRpcFinalizeResponse>().await;
debug!("finalize response: {:?}", res); debug!("finalize response: {:?}", res);
@ -538,7 +615,7 @@ pub async fn finalize_wallet(info: Vec<String>) -> reqres::XmrRpcFinalizeRespons
_ => Default::default(), _ => Default::default(),
} }
} }
Err(_) => Default::default() Err(_) => Default::default(),
} }
} }
@ -553,8 +630,12 @@ pub async fn export_multisig_info() -> reqres::XmrRpcExportResponse {
method: RpcFields::Export.value(), method: RpcFields::Export.value(),
}; };
let login: RpcLogin = get_rpc_creds(); let login: RpcLogin = get_rpc_creds();
match client.post(host).json(&req) match client
.send_with_digest_auth(&login.username, &login.credential).await { .post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => { Ok(response) => {
let res = response.json::<reqres::XmrRpcExportResponse>().await; let res = response.json::<reqres::XmrRpcExportResponse>().await;
debug!("export msig response: {:?}", res); debug!("export msig response: {:?}", res);
@ -563,7 +644,7 @@ pub async fn export_multisig_info() -> reqres::XmrRpcExportResponse {
_ => Default::default(), _ => Default::default(),
} }
} }
Err(_) => Default::default() Err(_) => Default::default(),
} }
} }
@ -572,9 +653,7 @@ pub async fn import_multisig_info(info: Vec<String>) -> reqres::XmrRpcImportResp
info!("import msig wallet"); info!("import msig wallet");
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let host = get_rpc_host(); let host = get_rpc_host();
let params = reqres::XmrRpcImportParams { let params = reqres::XmrRpcImportParams { info };
info,
};
let req = reqres::XmrRpcImportRequest { let req = reqres::XmrRpcImportRequest {
jsonrpc: RpcFields::JsonRpcVersion.value(), jsonrpc: RpcFields::JsonRpcVersion.value(),
id: RpcFields::Id.value(), id: RpcFields::Id.value(),
@ -582,8 +661,12 @@ pub async fn import_multisig_info(info: Vec<String>) -> reqres::XmrRpcImportResp
params, params,
}; };
let login: RpcLogin = get_rpc_creds(); let login: RpcLogin = get_rpc_creds();
match client.post(host).json(&req) match client
.send_with_digest_auth(&login.username, &login.credential).await { .post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => { Ok(response) => {
let res = response.json::<reqres::XmrRpcImportResponse>().await; let res = response.json::<reqres::XmrRpcImportResponse>().await;
debug!("import msig info response: {:?}", res); debug!("import msig info response: {:?}", res);
@ -592,7 +675,7 @@ pub async fn import_multisig_info(info: Vec<String>) -> reqres::XmrRpcImportResp
_ => Default::default(), _ => Default::default(),
} }
} }
Err(_) => Default::default() Err(_) => Default::default(),
} }
} }
@ -601,9 +684,7 @@ pub async fn sign_multisig(tx_data_hex: String) -> reqres::XmrRpcSignMultisigRes
info!("sign msig txset"); info!("sign msig txset");
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let host = get_rpc_host(); let host = get_rpc_host();
let params = reqres::XmrRpcSignMultisigParams { let params = reqres::XmrRpcSignMultisigParams { tx_data_hex };
tx_data_hex,
};
let req = reqres::XmrRpcSignMultisigRequest { let req = reqres::XmrRpcSignMultisigRequest {
jsonrpc: RpcFields::JsonRpcVersion.value(), jsonrpc: RpcFields::JsonRpcVersion.value(),
id: RpcFields::Id.value(), id: RpcFields::Id.value(),
@ -611,8 +692,12 @@ pub async fn sign_multisig(tx_data_hex: String) -> reqres::XmrRpcSignMultisigRes
params, params,
}; };
let login: RpcLogin = get_rpc_creds(); let login: RpcLogin = get_rpc_creds();
match client.post(host).json(&req) match client
.send_with_digest_auth(&login.username, &login.credential).await { .post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => { Ok(response) => {
let res = response.json::<reqres::XmrRpcSignMultisigResponse>().await; let res = response.json::<reqres::XmrRpcSignMultisigResponse>().await;
debug!("sign msig txset response: {:?}", res); debug!("sign msig txset response: {:?}", res);
@ -621,7 +706,7 @@ pub async fn sign_multisig(tx_data_hex: String) -> reqres::XmrRpcSignMultisigRes
_ => Default::default(), _ => Default::default(),
} }
} }
Err(_) => Default::default() Err(_) => Default::default(),
} }
} }
// END Multisig // END Multisig
@ -631,11 +716,11 @@ pub async fn check_tx_proof(txp: &proof::TxProof) -> reqres::XmrRpcCheckTxProofR
info!("check_tx_proof proof: {:?}", txp); info!("check_tx_proof proof: {:?}", txp);
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let host = get_rpc_host(); let host = get_rpc_host();
let params: reqres::XmrRpcCheckTxProofParams = reqres::XmrRpcCheckTxProofParams { let params: reqres::XmrRpcCheckTxProofParams = reqres::XmrRpcCheckTxProofParams {
address: String::from(&txp.subaddress), address: String::from(&txp.subaddress),
message: String::from(&txp.message), message: String::from(&txp.message),
signature: String::from(&txp.signature), signature: String::from(&txp.signature),
txid: String::from(&txp.hash), txid: String::from(&txp.hash),
}; };
let req = reqres::XmrRpcCheckTxProofRequest { let req = reqres::XmrRpcCheckTxProofRequest {
jsonrpc: RpcFields::JsonRpcVersion.value(), jsonrpc: RpcFields::JsonRpcVersion.value(),
@ -644,8 +729,12 @@ pub async fn check_tx_proof(txp: &proof::TxProof) -> reqres::XmrRpcCheckTxProofR
params, params,
}; };
let login: RpcLogin = get_rpc_creds(); let login: RpcLogin = get_rpc_creds();
match client.post(host).json(&req) match client
.send_with_digest_auth(&login.username, &login.credential).await { .post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => { Ok(response) => {
let res = response.json::<reqres::XmrRpcCheckTxProofResponse>().await; let res = response.json::<reqres::XmrRpcCheckTxProofResponse>().await;
debug!("check_tx_proof response: {:?}", res); debug!("check_tx_proof response: {:?}", res);
@ -654,7 +743,7 @@ pub async fn check_tx_proof(txp: &proof::TxProof) -> reqres::XmrRpcCheckTxProofR
_ => Default::default(), _ => Default::default(),
} }
} }
Err(_) => Default::default() Err(_) => Default::default(),
} }
} }
@ -663,10 +752,10 @@ pub async fn get_tx_proof(ptxp: proof::TxProof) -> reqres::XmrRpcGetTxProofRespo
info!("fetching proof: {:?}", &ptxp.hash); info!("fetching proof: {:?}", &ptxp.hash);
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let host = get_rpc_host(); let host = get_rpc_host();
let params: reqres::XmrRpcGetTxProofParams = reqres::XmrRpcGetTxProofParams { let params: reqres::XmrRpcGetTxProofParams = reqres::XmrRpcGetTxProofParams {
address: String::from(&ptxp.subaddress), address: String::from(&ptxp.subaddress),
message: String::from(&ptxp.message), message: String::from(&ptxp.message),
txid: String::from(&ptxp.hash), txid: String::from(&ptxp.hash),
}; };
let req = reqres::XmrRpcGetTxProofRequest { let req = reqres::XmrRpcGetTxProofRequest {
jsonrpc: RpcFields::JsonRpcVersion.value(), jsonrpc: RpcFields::JsonRpcVersion.value(),
@ -675,8 +764,12 @@ pub async fn get_tx_proof(ptxp: proof::TxProof) -> reqres::XmrRpcGetTxProofRespo
params, params,
}; };
let login: RpcLogin = get_rpc_creds(); let login: RpcLogin = get_rpc_creds();
match client.post(host).json(&req) match client
.send_with_digest_auth(&login.username, &login.credential).await { .post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => { Ok(response) => {
let res = response.json::<reqres::XmrRpcGetTxProofResponse>().await; let res = response.json::<reqres::XmrRpcGetTxProofResponse>().await;
debug!("get_tx_proof response: {:?}", res); debug!("get_tx_proof response: {:?}", res);
@ -685,7 +778,7 @@ pub async fn get_tx_proof(ptxp: proof::TxProof) -> reqres::XmrRpcGetTxProofRespo
_ => Default::default(), _ => Default::default(),
} }
} }
Err(_) => Default::default() Err(_) => Default::default(),
} }
} }
@ -694,8 +787,8 @@ pub async fn get_transfer_by_txid(txid: &str) -> reqres::XmrRpcGetTxByIdResponse
info!("fetching tx: {:?}", txid); info!("fetching tx: {:?}", txid);
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let host = get_rpc_host(); let host = get_rpc_host();
let params: reqres::XmrRpcGetTxByIdParams = reqres::XmrRpcGetTxByIdParams { let params: reqres::XmrRpcGetTxByIdParams = reqres::XmrRpcGetTxByIdParams {
txid: String::from(txid) txid: String::from(txid),
}; };
let req = reqres::XmrRpcGetTxByIdRequest { let req = reqres::XmrRpcGetTxByIdRequest {
jsonrpc: RpcFields::JsonRpcVersion.value(), jsonrpc: RpcFields::JsonRpcVersion.value(),
@ -704,8 +797,12 @@ pub async fn get_transfer_by_txid(txid: &str) -> reqres::XmrRpcGetTxByIdResponse
params, params,
}; };
let login: RpcLogin = get_rpc_creds(); let login: RpcLogin = get_rpc_creds();
match client.post(host).json(&req) match client
.send_with_digest_auth(&login.username, &login.credential).await { .post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => { Ok(response) => {
let res = response.json::<reqres::XmrRpcGetTxByIdResponse>().await; let res = response.json::<reqres::XmrRpcGetTxByIdResponse>().await;
debug!("get_transfer_by_txid response: {:?}", res); debug!("get_transfer_by_txid response: {:?}", res);
@ -714,7 +811,7 @@ pub async fn get_transfer_by_txid(txid: &str) -> reqres::XmrRpcGetTxByIdResponse
_ => Default::default(), _ => Default::default(),
} }
} }
Err(_) => Default::default() Err(_) => Default::default(),
} }
} }
@ -740,8 +837,12 @@ pub async fn transfer(d: reqres::Destination) -> reqres::XmrRpcTransferResponse
params, params,
}; };
let login: RpcLogin = get_rpc_creds(); let login: RpcLogin = get_rpc_creds();
match client.post(host).json(&req) match client
.send_with_digest_auth(&login.username, &login.credential).await { .post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => { Ok(response) => {
let res = response.json::<reqres::XmrRpcTransferResponse>().await; let res = response.json::<reqres::XmrRpcTransferResponse>().await;
debug!("{} response: {:?}", RpcFields::Transfer.value(), res); debug!("{} response: {:?}", RpcFields::Transfer.value(), res);
@ -750,7 +851,7 @@ pub async fn transfer(d: reqres::Destination) -> reqres::XmrRpcTransferResponse
_ => Default::default(), _ => Default::default(),
} }
} }
Err(_) => Default::default() Err(_) => Default::default(),
} }
} }
@ -767,8 +868,12 @@ pub async fn sweep_all(address: String) -> reqres::XmrRpcSweepAllResponse {
params, params,
}; };
let login: RpcLogin = get_rpc_creds(); let login: RpcLogin = get_rpc_creds();
match client.post(host).json(&req) match client
.send_with_digest_auth(&login.username, &login.credential).await { .post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => { Ok(response) => {
let res = response.json::<reqres::XmrRpcSweepAllResponse>().await; let res = response.json::<reqres::XmrRpcSweepAllResponse>().await;
debug!("{} response: {:?}", RpcFields::SweepAll.value(), res); debug!("{} response: {:?}", RpcFields::SweepAll.value(), res);
@ -777,7 +882,7 @@ pub async fn sweep_all(address: String) -> reqres::XmrRpcSweepAllResponse {
_ => Default::default(), _ => Default::default(),
} }
} }
Err(_) => Default::default() Err(_) => Default::default(),
} }
} }
@ -786,7 +891,8 @@ pub async fn create_address() -> reqres::XmrRpcCreateAddressResponse {
info!("creating new subaddress"); info!("creating new subaddress");
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let host = get_rpc_host(); let host = get_rpc_host();
let params: reqres::XmrRpcCreateAddressParams = reqres::XmrRpcCreateAddressParams { account_index: 0 }; let params: reqres::XmrRpcCreateAddressParams =
reqres::XmrRpcCreateAddressParams { account_index: 0 };
let req = reqres::XmrRpcCreateAddressRequest { let req = reqres::XmrRpcCreateAddressRequest {
jsonrpc: RpcFields::JsonRpcVersion.value(), jsonrpc: RpcFields::JsonRpcVersion.value(),
id: RpcFields::Id.value(), id: RpcFields::Id.value(),
@ -794,8 +900,12 @@ pub async fn create_address() -> reqres::XmrRpcCreateAddressResponse {
params, params,
}; };
let login: RpcLogin = get_rpc_creds(); let login: RpcLogin = get_rpc_creds();
match client.post(host).json(&req) match client
.send_with_digest_auth(&login.username, &login.credential).await { .post(host)
.json(&req)
.send_with_digest_auth(&login.username, &login.credential)
.await
{
Ok(response) => { Ok(response) => {
let res = response.json::<reqres::XmrRpcCreateAddressResponse>().await; let res = response.json::<reqres::XmrRpcCreateAddressResponse>().await;
debug!("{} response: {:?}", RpcFields::CreateAddress.value(), res); debug!("{} response: {:?}", RpcFields::CreateAddress.value(), res);
@ -804,7 +914,7 @@ pub async fn create_address() -> reqres::XmrRpcCreateAddressResponse {
_ => Default::default(), _ => Default::default(),
} }
} }
Err(_) => Default::default() Err(_) => Default::default(),
} }
} }
@ -829,6 +939,6 @@ pub async fn get_info() -> reqres::XmrDaemonGetInfoResponse {
_ => Default::default(), _ => Default::default(),
} }
} }
Err(_) => Default::default() Err(_) => Default::default(),
} }
} }

View file

@ -1,14 +1,31 @@
use crate::{db, monero, reqres, utils}; use crate::{
use log::{error, info}; db,
monero,
reqres,
utils,
};
use log::{
error,
info,
};
use rocket::{
http::Status,
outcome::Outcome,
request,
request::FromRequest,
Request,
};
use std::error::Error; use std::error::Error;
use rocket::http::Status;
use rocket::outcome::Outcome;
use rocket::request::FromRequest;
use rocket::{request, Request};
use hmac::{Hmac, Mac}; use hmac::{
Hmac,
Mac,
};
use jwt::*; use jwt::*;
use serde::{Deserialize, Serialize}; use serde::{
Deserialize,
Serialize,
};
use sha2::Sha512; use sha2::Sha512;
use std::collections::BTreeMap; use std::collections::BTreeMap;
@ -34,7 +51,7 @@ impl Default for TxProof {
} }
/// Provide neccessary information for contacts to /// Provide neccessary information for contacts to
/// ///
/// provide proof of payment. /// provide proof of payment.
pub async fn create_invoice() -> reqres::Invoice { pub async fn create_invoice() -> reqres::Invoice {
info!("creating invoice"); info!("creating invoice");
@ -43,20 +60,24 @@ pub async fn create_invoice() -> reqres::Invoice {
let address = c_address.result.address; let address = c_address.result.address;
let pay_threshold = utils::get_payment_threshold(); let pay_threshold = utils::get_payment_threshold();
let conf_threshold = utils::get_conf_threshold(); let conf_threshold = utils::get_conf_threshold();
reqres::Invoice { address, conf_threshold, pay_threshold } reqres::Invoice {
address,
conf_threshold,
pay_threshold,
}
} }
/// Technically the same process as creating a JWT /// Technically the same process as creating a JWT
/// ///
/// except that the claims must contain the information /// except that the claims must contain the information
/// ///
/// necessary to verify the payment. Confirmations cannot /// necessary to verify the payment. Confirmations cannot
/// ///
/// be zero or above some specified threshold. Setting higher /// be zero or above some specified threshold. Setting higher
/// ///
/// payment values and lower confirmations works as a spam /// payment values and lower confirmations works as a spam
/// ///
/// disincentivizing mechanism. /// disincentivizing mechanism.
pub async fn create_jwp(proof: &TxProof) -> String { pub async fn create_jwp(proof: &TxProof) -> String {
info!("creating jwp"); info!("creating jwp");
// validate the proof // validate the proof
@ -94,7 +115,12 @@ pub async fn prove_payment(contact: String, txp: &TxProof) -> Result<reqres::Jwp
let host = utils::get_i2p_http_proxy(); let host = utils::get_i2p_http_proxy();
let proxy = reqwest::Proxy::http(&host)?; let proxy = reqwest::Proxy::http(&host)?;
let client = reqwest::Client::builder().proxy(proxy).build(); let client = reqwest::Client::builder().proxy(proxy).build();
match client?.post(format!("http://{}/prove", contact)).json(txp).send().await { match client?
.post(format!("http://{}/prove", contact))
.json(txp)
.send()
.await
{
Ok(response) => { Ok(response) => {
let res = response.json::<reqres::Jwp>().await; let res = response.json::<reqres::Jwp>().await;
log::debug!("prove payment response: {:?}", res); log::debug!("prove payment response: {:?}", res);
@ -106,7 +132,7 @@ pub async fn prove_payment(contact: String, txp: &TxProof) -> Result<reqres::Jwp
db::Interface::delete(&s.env, &s.handle, &k); db::Interface::delete(&s.env, &s.handle, &k);
db::Interface::write(&s.env, &s.handle, &k, &r.jwp); db::Interface::write(&s.env, &s.handle, &k, &r.jwp);
Ok(r) Ok(r)
}, }
_ => Ok(Default::default()), _ => Ok(Default::default()),
} }
} }
@ -117,27 +143,31 @@ pub async fn prove_payment(contact: String, txp: &TxProof) -> Result<reqres::Jwp
} }
} }
/// # PaymentProof /// # PaymentProof
/// ///
/// is a JWP (JSON Web Proof) with the contents: /// is a JWP (JSON Web Proof) with the contents:
/// ///
/// `subaddress`: a subaddress belonging to this nevmes instance /// `subaddress`: a subaddress belonging to this nevmes instance
/// ///
/// `created`: UTC timestamp the proof was created. /// `created`: UTC timestamp the proof was created.
/// <i>Future use</i> Potential offline payments. /// <i>Future use</i> Potential offline payments.
/// ///
/// `expire`: blocks approved for /// `expire`: blocks approved for
/// <i>Future use</i>. Potential offline payments. /// <i>Future use</i>. Potential offline payments.
/// ///
/// `hash`: hash of the payment /// `hash`: hash of the payment
/// ///
/// `message`: (optional) default: empty string /// `message`: (optional) default: empty string
/// ///
/// `signature`: validates proof of payment /// `signature`: validates proof of payment
#[derive(Debug)] #[derive(Debug)]
pub struct PaymentProof(String); pub struct PaymentProof(String);
impl PaymentProof { pub fn get_jwp(self) -> String { self.0 } } impl PaymentProof {
pub fn get_jwp(self) -> String {
self.0
}
}
#[derive(Debug)] #[derive(Debug)]
pub enum PaymentProofError { pub enum PaymentProofError {
@ -205,7 +235,10 @@ impl<'r> FromRequest<'r> for PaymentProof {
} }
Err(e) => { Err(e) => {
error!("jwp error: {:?}", e); error!("jwp error: {:?}", e);
return Outcome::Failure((Status::PaymentRequired, PaymentProofError::Invalid)); return Outcome::Failure((
Status::PaymentRequired,
PaymentProofError::Invalid,
));
} }
}; };
} }
@ -224,30 +257,34 @@ async fn validate_proof(txp: &TxProof) -> TxProof {
let p = monero::check_tx_proof(txp).await; let p = monero::check_tx_proof(txp).await;
let cth = utils::get_conf_threshold(); let cth = utils::get_conf_threshold();
let pth = utils::get_payment_threshold(); let pth = utils::get_payment_threshold();
let lgtm = p.result.good && !p.result.in_pool let lgtm = p.result.good
&& !p.result.in_pool
&& unlock_time < monero::LockTimeLimit::Blocks.value() && unlock_time < monero::LockTimeLimit::Blocks.value()
&& p.result.confirmations < cth && p.result.received >= pth; && p.result.confirmations < cth
&& p.result.received >= pth;
if lgtm { if lgtm {
return TxProof { return TxProof {
subaddress: String::from(&txp.subaddress), subaddress: String::from(&txp.subaddress),
hash: String::from(&txp.hash), hash: String::from(&txp.hash),
confirmations: p.result.confirmations, confirmations: p.result.confirmations,
message: String::from(&txp.message), message: String::from(&txp.message),
signature: String::from(&txp.signature) signature: String::from(&txp.signature),
} };
} }
Default::default() Default::default()
} }
/// Validate that the subaddress in the proof was /// Validate that the subaddress in the proof was
/// ///
/// created by us. TODO(?): Use xmr rpc call `get_address_index` /// created by us. TODO(?): Use xmr rpc call `get_address_index`
/// ///
/// for faster lookups (check minor > 0) /// for faster lookups (check minor > 0)
async fn validate_subaddress(subaddress: &String) -> bool { async fn validate_subaddress(subaddress: &String) -> bool {
let m_address = monero::get_address().await; let m_address = monero::get_address().await;
let all_address = m_address.result.addresses; let all_address = m_address.result.addresses;
let mut address_list: Vec<String> = Vec::new(); let mut address_list: Vec<String> = Vec::new();
for s_address in all_address { address_list.push(s_address.address); } for s_address in all_address {
address_list.push(s_address.address);
}
return address_list.contains(&subaddress); return address_list.contains(&subaddress);
} }

View file

@ -1,5 +1,8 @@
use crate::utils; use crate::utils;
use serde::{Deserialize, Serialize}; use serde::{
Deserialize,
Serialize,
};
// All http requests and responses are here // All http requests and responses are here
@ -363,10 +366,7 @@ pub struct SubAddressIndex {
impl Default for SubAddressIndex { impl Default for SubAddressIndex {
fn default() -> Self { fn default() -> Self {
SubAddressIndex { SubAddressIndex { major: 0, minor: 0 }
major: 0,
minor: 0,
}
} }
} }
@ -820,13 +820,12 @@ impl Default for XmrRpcCreateAddressResponse {
address_index: 0, address_index: 0,
address_indices: Vec::new(), address_indices: Vec::new(),
addresses: Vec::new(), addresses: Vec::new(),
} },
} }
} }
} }
// END XMR Structs // END XMR Structs
/// Container for the message decryption /// Container for the message decryption
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
@ -864,7 +863,7 @@ impl Default for Invoice {
} }
/// Not to be confused with the PaymentProof guard. /// Not to be confused with the PaymentProof guard.
/// ///
/// This is the response when proving payment /// This is the response when proving payment
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]

View file

@ -1,7 +1,15 @@
// User repo/service layer // User repo/service layer
use crate::{db, models::*, utils}; use crate::{
db,
models::*,
utils,
};
use log::{
debug,
error,
info,
};
use rocket::serde::json::Json; use rocket::serde::json::Json;
use log::{debug, error, info};
// This module is only used for remote access // This module is only used for remote access
@ -26,7 +34,7 @@ pub fn find(uid: &String) -> User {
let r = db::Interface::read(&s.env, &s.handle, &String::from(uid)); let r = db::Interface::read(&s.env, &s.handle, &String::from(uid));
if r == utils::empty_string() { if r == utils::empty_string() {
error!("user not found"); error!("user not found");
return Default::default() return Default::default();
} }
User::from_db(String::from(uid), r) User::from_db(String::from(uid), r)
} }

View file

@ -1,13 +1,32 @@
use rand_core::RngCore; use crate::{
args,
db,
gpg,
i2p,
message,
models,
monero,
reqres,
utils,
};
use clap::Parser; use clap::Parser;
use log::{
debug,
error,
info,
warn,
};
use rand_core::RngCore;
use rocket::serde::json::Json; use rocket::serde::json::Json;
use crate::{args, db, i2p, message, models, monero, gpg, utils, reqres };
use log::{info, debug, error, warn};
use std::time::Duration; use std::time::Duration;
/// Enum for selecting hash validation /// Enum for selecting hash validation
#[derive(PartialEq)] #[derive(PartialEq)]
enum ExternalSoftware { I2P, I2PZero, XMR } enum ExternalSoftware {
I2P,
I2PZero,
XMR,
}
/// Handles the state for the installation manager popup /// Handles the state for the installation manager popup
pub struct Installations { pub struct Installations {
@ -87,15 +106,23 @@ impl ReleaseEnvironment {
pub fn start_core(conn: &Connections) { pub fn start_core(conn: &Connections) {
let env = if !conn.mainnet { "dev" } else { "prod" }; let env = if !conn.mainnet { "dev" } else { "prod" };
let args = [ let args = [
"--monero-location", &conn.monero_location, "--monero-location",
"--monero-blockchain-dir", &conn.blockchain_dir, &conn.monero_location,
"--monero-rpc-host", &conn.rpc_host, "--monero-blockchain-dir",
"--monero-rpc-daemon", &conn.daemon_host, &conn.blockchain_dir,
"--monero-rpc-username", &conn.rpc_username, "--monero-rpc-host",
"--monero-rpc-cred", &conn.rpc_credential, &conn.rpc_host,
"--i2p-zero-dir", &conn.i2p_zero_dir, "--monero-rpc-daemon",
"-r", env, &conn.daemon_host,
]; "--monero-rpc-username",
&conn.rpc_username,
"--monero-rpc-cred",
&conn.rpc_credential,
"--i2p-zero-dir",
&conn.i2p_zero_dir,
"-r",
env,
];
let output = std::process::Command::new("./nevmes") let output = std::process::Command::new("./nevmes")
.args(args) .args(args)
.spawn() .spawn()
@ -107,7 +134,9 @@ pub fn start_core(conn: &Connections) {
pub fn is_using_remote_node() -> bool { pub fn is_using_remote_node() -> bool {
let args = args::Args::parse(); let args = args::Args::parse();
let r = args.remote_node; let r = args.remote_node;
if r { warn!("using a remote node may harm privacy"); } if r {
warn!("using a remote node may harm privacy");
}
r r
} }
@ -172,10 +201,10 @@ pub fn get_payment_threshold() -> u128 {
} }
/// convert contact to json so only core module does the work /// convert contact to json so only core module does the work
pub fn contact_to_json(c: &models::Contact) -> Json<models::Contact> { pub fn contact_to_json(c: &models::Contact) -> Json<models::Contact> {
let r_contact: models::Contact = models::Contact { let r_contact: models::Contact = models::Contact {
cid: String::from(&c.cid), cid: String::from(&c.cid),
i2p_address: String::from(&c.i2p_address), i2p_address: String::from(&c.i2p_address),
xmr_address: String::from(&c.xmr_address), xmr_address: String::from(&c.xmr_address),
gpg_key: c.gpg_key.iter().cloned().collect(), gpg_key: c.gpg_key.iter().cloned().collect(),
}; };
@ -183,25 +212,33 @@ pub fn contact_to_json(c: &models::Contact) -> Json<models::Contact> {
} }
/// convert message to json so only core module does the work /// convert message to json so only core module does the work
pub fn message_to_json(m: &models::Message) -> Json<models::Message> { pub fn message_to_json(m: &models::Message) -> Json<models::Message> {
let r_message: models::Message = models::Message { let r_message: models::Message = models::Message {
body: m.body.iter().cloned().collect(), body: m.body.iter().cloned().collect(),
mid: String::from(&m.mid), mid: String::from(&m.mid),
uid: utils::empty_string(), uid: utils::empty_string(),
created: m.created, created: m.created,
from: String::from(&m.from), from: String::from(&m.from),
to: String::from(&m.to), to: String::from(&m.to),
}; };
Json(r_message) Json(r_message)
} }
/// Instead of putting `String::from("")` /// Instead of putting `String::from("")`
pub fn empty_string() -> String { String::from("") } pub fn empty_string() -> String {
String::from("")
}
// DoS prevention // DoS prevention
pub const fn string_limit() -> usize { 512 } pub const fn string_limit() -> usize {
pub const fn gpg_key_limit() -> usize { 4096 } 512
pub const fn message_limit() -> usize { 9999 } }
pub const fn gpg_key_limit() -> usize {
4096
}
pub const fn message_limit() -> usize {
9999
}
/// Generate application gpg keys at startup if none exist /// Generate application gpg keys at startup if none exist
async fn gen_app_gpg() { async fn gen_app_gpg() {
@ -219,15 +256,19 @@ async fn gen_app_gpg() {
/// Handles panic! for missing wallet directory /// Handles panic! for missing wallet directory
fn create_wallet_dir() { fn create_wallet_dir() {
let file_path = format!("/home/{}/.nevmes", let file_path = format!(
std::env::var("USER").unwrap_or(String::from("user"))); "/home/{}/.nevmes",
std::env::var("USER").unwrap_or(String::from("user"))
);
let s_output = std::process::Command::new("mkdir") let s_output = std::process::Command::new("mkdir")
.args(["-p", &format!("{}/stagenet/wallet", file_path)]) .args(["-p", &format!("{}/stagenet/wallet", file_path)])
.spawn().expect("failed to create dir"); .spawn()
.expect("failed to create dir");
debug!("{:?}", s_output); debug!("{:?}", s_output);
let m_output = std::process::Command::new("mkdir") let m_output = std::process::Command::new("mkdir")
.args(["-p", &format!("{}/wallet", file_path)]) .args(["-p", &format!("{}/wallet", file_path)])
.spawn().expect("failed to create dir"); .spawn()
.expect("failed to create dir");
debug!("{:?}", m_output); debug!("{:?}", m_output);
} }
@ -238,13 +279,12 @@ async fn gen_app_wallet() {
let mut m_wallet = monero::open_wallet(String::from(filename)).await; let mut m_wallet = monero::open_wallet(String::from(filename)).await;
if !m_wallet { if !m_wallet {
m_wallet = monero::create_wallet(String::from(filename)).await; m_wallet = monero::create_wallet(String::from(filename)).await;
if !m_wallet { if !m_wallet {
error!("failed to create wallet") error!("failed to create wallet")
} else { } else {
m_wallet = monero::open_wallet(String::from(filename)).await; m_wallet = monero::open_wallet(String::from(filename)).await;
if m_wallet { if m_wallet {
let m_address: reqres::XmrRpcAddressResponse = let m_address: reqres::XmrRpcAddressResponse = monero::get_address().await;
monero::get_address().await;
info!("app wallet address: {}", m_address.result.address) info!("app wallet address: {}", m_address.result.address)
} }
} }
@ -269,7 +309,7 @@ fn gen_signing_keys() {
} }
/// TODO(c2m): add a button to gui to call this /// TODO(c2m): add a button to gui to call this
/// ///
/// dont' forget to generate new keys as well /// dont' forget to generate new keys as well
pub fn revoke_signing_keys() { pub fn revoke_signing_keys() {
let s = db::Interface::open(); let s = db::Interface::open();
@ -282,7 +322,7 @@ pub fn get_jwt_secret_key() -> String {
let r = db::Interface::read(&s.env, &s.handle, crate::NEVMES_JWT_SECRET_KEY); let r = db::Interface::read(&s.env, &s.handle, crate::NEVMES_JWT_SECRET_KEY);
if r == utils::empty_string() { if r == utils::empty_string() {
error!("JWT key not found"); error!("JWT key not found");
return Default::default() return Default::default();
} }
r r
} }
@ -292,7 +332,7 @@ pub fn get_jwp_secret_key() -> String {
let r = db::Interface::read(&s.env, &s.handle, crate::NEVMES_JWP_SECRET_KEY); let r = db::Interface::read(&s.env, &s.handle, crate::NEVMES_JWP_SECRET_KEY);
if r == utils::empty_string() { if r == utils::empty_string() {
error!("JWP key not found"); error!("JWP key not found");
return Default::default() return Default::default();
} }
r r
} }
@ -302,21 +342,30 @@ fn start_micro_servers() {
info!("starting auth server"); info!("starting auth server");
let mut auth_path = "nevmes-auth/target/debug/nevmes_auth"; let mut auth_path = "nevmes-auth/target/debug/nevmes_auth";
let env = get_release_env(); let env = get_release_env();
if env == ReleaseEnvironment::Production { auth_path = "nevmes_auth"; } if env == ReleaseEnvironment::Production {
auth_path = "nevmes_auth";
}
let a_output = std::process::Command::new(auth_path) let a_output = std::process::Command::new(auth_path)
.spawn().expect("failed to start auth server"); .spawn()
.expect("failed to start auth server");
debug!("{:?}", a_output.stdout); debug!("{:?}", a_output.stdout);
info!("starting contact server"); info!("starting contact server");
let mut contact_path = "nevmes-contact/target/debug/nevmes_contact"; let mut contact_path = "nevmes-contact/target/debug/nevmes_contact";
if env == ReleaseEnvironment::Production { contact_path = "nevmes_contact"; } if env == ReleaseEnvironment::Production {
contact_path = "nevmes_contact";
}
let c_output = std::process::Command::new(contact_path) let c_output = std::process::Command::new(contact_path)
.spawn().expect("failed to start contact server"); .spawn()
.expect("failed to start contact server");
debug!("{:?}", c_output.stdout); debug!("{:?}", c_output.stdout);
info!("starting message server"); info!("starting message server");
let mut message_path = "nevmes-message/target/debug/nevmes_message"; let mut message_path = "nevmes-message/target/debug/nevmes_message";
if env == ReleaseEnvironment::Production { message_path = "nevmes_message"; } if env == ReleaseEnvironment::Production {
message_path = "nevmes_message";
}
let m_output = std::process::Command::new(message_path) let m_output = std::process::Command::new(message_path)
.spawn().expect("failed to start message server"); .spawn()
.expect("failed to start message server");
debug!("{:?}", m_output.stdout); debug!("{:?}", m_output.stdout);
} }
@ -327,9 +376,12 @@ fn start_gui() {
info!("starting gui"); info!("starting gui");
let mut gui_path = "nevmes-gui/target/debug/nevmes_gui"; let mut gui_path = "nevmes-gui/target/debug/nevmes_gui";
let env = get_release_env(); let env = get_release_env();
if env == ReleaseEnvironment::Production { gui_path = "nevmes-gui"; } if env == ReleaseEnvironment::Production {
gui_path = "nevmes-gui";
}
let g_output = std::process::Command::new(gui_path) let g_output = std::process::Command::new(gui_path)
.spawn().expect("failed to start gui"); .spawn()
.expect("failed to start gui");
debug!("{:?}", g_output.stdout); debug!("{:?}", g_output.stdout);
} }
} }
@ -338,10 +390,16 @@ fn start_gui() {
pub async fn start_up() { pub async fn start_up() {
info!("nevmes is starting up"); info!("nevmes is starting up");
let args = args::Args::parse(); let args = args::Args::parse();
if args.remote_access { start_micro_servers(); } if args.remote_access {
if args.clear_fts { clear_fts(); } start_micro_servers();
}
if args.clear_fts {
clear_fts();
}
gen_signing_keys(); gen_signing_keys();
if !is_using_remote_node() { monero::start_daemon(); } if !is_using_remote_node() {
monero::start_daemon();
}
create_wallet_dir(); create_wallet_dir();
// wait for daemon for a bit // wait for daemon for a bit
tokio::time::sleep(std::time::Duration::new(5, 0)).await; tokio::time::sleep(std::time::Duration::new(5, 0)).await;
@ -354,12 +412,16 @@ pub async fn start_up() {
gen_app_gpg().await; gen_app_gpg().await;
let env: String = get_release_env().value(); let env: String = get_release_env().value();
start_gui(); start_gui();
{ tokio::spawn(async { message::retry_fts().await; }); } {
tokio::spawn(async {
message::retry_fts().await;
});
}
info!("{} - nevmes is online", env); info!("{} - nevmes is online", env);
} }
/// Called by gui for cleaning up monerod, rpc, etc. /// Called by gui for cleaning up monerod, rpc, etc.
/// ///
/// pass true from gui connection manager so not to kill nevmes /// pass true from gui connection manager so not to kill nevmes
pub fn kill_child_processes(cm: bool) { pub fn kill_child_processes(cm: bool) {
info!("stopping child processes"); info!("stopping child processes");
@ -380,10 +442,10 @@ pub fn kill_child_processes(cm: bool) {
debug!("{:?}", nevmes_output.stdout); debug!("{:?}", nevmes_output.stdout);
} }
let rpc_output = std::process::Command::new("killall") let rpc_output = std::process::Command::new("killall")
.arg("monero-wallet-rpc") .arg("monero-wallet-rpc")
.spawn() .spawn()
.expect("monero-wallet-rpc failed to stop"); .expect("monero-wallet-rpc failed to stop");
debug!("{:?}", rpc_output.stdout); debug!("{:?}", rpc_output.stdout);
let i2pz_output = std::process::Command::new("pkill") let i2pz_output = std::process::Command::new("pkill")
.arg("i2p-zero") .arg("i2p-zero")
.spawn() .spawn()
@ -393,7 +455,9 @@ pub fn kill_child_processes(cm: bool) {
/// We can restart fts from since it gets terminated when empty /// We can restart fts from since it gets terminated when empty
pub fn restart_retry_fts() { pub fn restart_retry_fts() {
tokio::spawn(async move { message::retry_fts().await; }); tokio::spawn(async move {
message::retry_fts().await;
});
} }
/// Called on app startup if `--clear-fts` flag is passed. /// Called on app startup if `--clear-fts` flag is passed.
@ -414,11 +478,11 @@ pub fn stage_cleanup(f: String) {
} }
/// Handle the request from user to additional software /// Handle the request from user to additional software
/// ///
/// from gui startup. Power users will most like install /// from gui startup. Power users will most like install
/// ///
/// software on their own. Note that software pull is over /// software on their own. Note that software pull is over
/// ///
/// clearnet. TODO(c2m): trusted download locations over i2p. /// clearnet. TODO(c2m): trusted download locations over i2p.
pub async fn install_software(installations: Installations) -> bool { pub async fn install_software(installations: Installations) -> bool {
let mut valid_i2p_hash = true; let mut valid_i2p_hash = true;
@ -428,7 +492,10 @@ pub async fn install_software(installations: Installations) -> bool {
info!("installing i2p"); info!("installing i2p");
let i2p_version = crate::I2P_RELEASE_VERSION; let i2p_version = crate::I2P_RELEASE_VERSION;
let i2p_jar = format!("i2pinstall_{}.jar", i2p_version); let i2p_jar = format!("i2pinstall_{}.jar", i2p_version);
let link = format!("https://download.i2p2.no/releases/{}/{}", i2p_version, i2p_jar); let link = format!(
"https://download.i2p2.no/releases/{}/{}",
i2p_version, i2p_jar
);
let curl = std::process::Command::new("curl") let curl = std::process::Command::new("curl")
.args(["-O#", &link]) .args(["-O#", &link])
.status(); .status();
@ -441,8 +508,8 @@ pub async fn install_software(installations: Installations) -> bool {
.spawn() .spawn()
.expect("i2p gui installation failed"); .expect("i2p gui installation failed");
debug!("{:?}", jar_output.stdout); debug!("{:?}", jar_output.stdout);
}, }
_=> error!("i2p download failed") _ => error!("i2p download failed"),
} }
valid_i2p_hash = validate_installation_hash(ExternalSoftware::I2P, &i2p_jar); valid_i2p_hash = validate_installation_hash(ExternalSoftware::I2P, &i2p_jar);
tokio::time::sleep(std::time::Duration::from_secs(1)).await; tokio::time::sleep(std::time::Duration::from_secs(1)).await;
@ -451,8 +518,10 @@ pub async fn install_software(installations: Installations) -> bool {
info!("installing i2p-zero"); info!("installing i2p-zero");
let i2p_version = crate::I2P_ZERO_RELEASE_VERSION; let i2p_version = crate::I2P_ZERO_RELEASE_VERSION;
let i2p_zero_zip = format!("i2p-zero-linux.{}.zip", i2p_version); let i2p_zero_zip = format!("i2p-zero-linux.{}.zip", i2p_version);
let link = format!("https://github.com/i2p-zero/i2p-zero/releases/download/{}/{}", let link = format!(
i2p_version, i2p_zero_zip); "https://github.com/i2p-zero/i2p-zero/releases/download/{}/{}",
i2p_version, i2p_zero_zip
);
let curl = std::process::Command::new("curl") let curl = std::process::Command::new("curl")
.args(["-LO#", &link]) .args(["-LO#", &link])
.status(); .status();
@ -465,15 +534,18 @@ pub async fn install_software(installations: Installations) -> bool {
.spawn() .spawn()
.expect("i2p unzip failed"); .expect("i2p unzip failed");
debug!("{:?}", unzip_output.stdout); debug!("{:?}", unzip_output.stdout);
}, }
_=> error!("i2p-zero download failed") _ => error!("i2p-zero download failed"),
} }
valid_i2p_zero_hash = validate_installation_hash(ExternalSoftware::I2PZero, &i2p_zero_zip); valid_i2p_zero_hash = validate_installation_hash(ExternalSoftware::I2PZero, &i2p_zero_zip);
tokio::time::sleep(std::time::Duration::from_secs(1)).await; tokio::time::sleep(std::time::Duration::from_secs(1)).await;
} }
if installations.xmr { if installations.xmr {
info!("installing monero"); info!("installing monero");
let link = format!("https://downloads.getmonero.org/cli/{}", crate::MONERO_RELEASE_VERSION); let link = format!(
"https://downloads.getmonero.org/cli/{}",
crate::MONERO_RELEASE_VERSION
);
let curl = std::process::Command::new("curl") let curl = std::process::Command::new("curl")
.args(["-O#", &link]) .args(["-O#", &link])
.status(); .status();
@ -486,12 +558,15 @@ pub async fn install_software(installations: Installations) -> bool {
.spawn() .spawn()
.expect("monero tar extraction failed"); .expect("monero tar extraction failed");
debug!("{:?}", tar_output.stdout); debug!("{:?}", tar_output.stdout);
}, }
_=> error!("monero download failed") _ => error!("monero download failed"),
} }
valid_xmr_hash = validate_installation_hash(ExternalSoftware::XMR, &String::from(crate::MONERO_RELEASE_VERSION)); valid_xmr_hash = validate_installation_hash(
ExternalSoftware::XMR,
&String::from(crate::MONERO_RELEASE_VERSION),
);
tokio::time::sleep(std::time::Duration::from_secs(1)).await; tokio::time::sleep(std::time::Duration::from_secs(1)).await;
} }
valid_i2p_hash && valid_i2p_zero_hash && valid_xmr_hash valid_i2p_hash && valid_i2p_zero_hash && valid_xmr_hash
} }
@ -499,14 +574,16 @@ pub async fn install_software(installations: Installations) -> bool {
fn validate_installation_hash(sw: ExternalSoftware, filename: &String) -> bool { fn validate_installation_hash(sw: ExternalSoftware, filename: &String) -> bool {
debug!("validating hash"); debug!("validating hash");
let expected_hash = if sw == ExternalSoftware::I2P { let expected_hash = if sw == ExternalSoftware::I2P {
String::from(crate::I2P_RELEASE_HASH) String::from(crate::I2P_RELEASE_HASH)
} else if sw == ExternalSoftware::I2PZero { } else if sw == ExternalSoftware::I2PZero {
String::from(crate::I2P_ZERO_RELEASH_HASH) String::from(crate::I2P_ZERO_RELEASH_HASH)
} else { } else {
String::from(crate::MONERO_RELEASE_HASH) String::from(crate::MONERO_RELEASE_HASH)
}; };
let sha_output = std::process::Command::new("sha256sum") let sha_output = std::process::Command::new("sha256sum")
.arg(filename).output().expect("hash validation failed"); .arg(filename)
.output()
.expect("hash validation failed");
let str_sha = String::from_utf8(sha_output.stdout).unwrap(); let str_sha = String::from_utf8(sha_output.stdout).unwrap();
let split1 = str_sha.split(" "); let split1 = str_sha.split(" ");
let mut v: Vec<String> = split1.map(|s| String::from(s)).collect(); let mut v: Vec<String> = split1.map(|s| String::from(s)).collect();

6
nevmes-gui/.rustfmt.toml Normal file
View file

@ -0,0 +1,6 @@
# cargo +nightly fmt
comment_width = 100
format_code_in_doc_comments = true
imports_granularity = "Crate"
imports_layout = "Vertical"
wrap_comments = true

View file

@ -1,7 +1,13 @@
use nevmes_core::*; use nevmes_core::*;
use std::sync::mpsc::{Receiver, Sender}; use std::sync::mpsc::{
Receiver,
Sender,
};
use crate::{ADD_CONTACT_TIMEOUT_SECS, BLOCK_TIME_IN_SECS_EST}; use crate::{
ADD_CONTACT_TIMEOUT_SECS,
BLOCK_TIME_IN_SECS_EST,
};
// TODO(c2m): better error handling with and error_tx/error_rx channel // TODO(c2m): better error handling with and error_tx/error_rx channel
// hook into the error thread and show toast messages as required // hook into the error thread and show toast messages as required
@ -454,7 +460,10 @@ impl eframe::App for AddressBookApp {
.labelled_by(find_contact_label.id); .labelled_by(find_contact_label.id);
}); });
ui.label("\n"); ui.label("\n");
use egui_extras::{Column, TableBuilder}; use egui_extras::{
Column,
TableBuilder,
};
let table = TableBuilder::new(ui) let table = TableBuilder::new(ui)
.striped(true) .striped(true)
.resizable(true) .resizable(true)
@ -679,7 +688,10 @@ fn send_payment_req(
if check_txp.result.good && check_txp.result.confirmations > 0 { if check_txp.result.good && check_txp.result.confirmations > 0 {
break; break;
} }
tokio::time::sleep(std::time::Duration::from_secs(BLOCK_TIME_IN_SECS_EST as u64)).await; tokio::time::sleep(std::time::Duration::from_secs(
BLOCK_TIME_IN_SECS_EST as u64,
))
.await;
retry_count += 1; retry_count += 1;
} }
write_gui_db( write_gui_db(

View file

@ -3,8 +3,13 @@
use eframe::egui; use eframe::egui;
use nevmes_core::*; use nevmes_core::*;
use std::sync::mpsc::{Receiver, Sender}; use std::{
use std::time::Duration; sync::mpsc::{
Receiver,
Sender,
},
time::Duration,
};
pub struct HomeApp { pub struct HomeApp {
connections: utils::Connections, connections: utils::Connections,
@ -73,9 +78,11 @@ impl Default for HomeApp {
let s_i2p_status = false; let s_i2p_status = false;
let s_can_refresh = false; let s_can_refresh = false;
let c_xmr_logo = std::fs::read("./assets/xmr.png").unwrap_or(Vec::new()); let c_xmr_logo = std::fs::read("./assets/xmr.png").unwrap_or(Vec::new());
let logo_xmr = egui_extras::RetainedImage::from_image_bytes("./assets/xmr.png", &c_xmr_logo).unwrap(); let logo_xmr =
egui_extras::RetainedImage::from_image_bytes("./assets/xmr.png", &c_xmr_logo).unwrap();
let c_i2p_logo = std::fs::read("./assets/i2p.png").unwrap_or(Vec::new()); let c_i2p_logo = std::fs::read("./assets/i2p.png").unwrap_or(Vec::new());
let logo_i2p = egui_extras::RetainedImage::from_image_bytes("./assets/i2p.png", &c_i2p_logo).unwrap(); let logo_i2p =
egui_extras::RetainedImage::from_image_bytes("./assets/i2p.png", &c_i2p_logo).unwrap();
Self { Self {
connections, connections,
core_timeout_rx, core_timeout_rx,
@ -140,7 +147,9 @@ impl eframe::App for HomeApp {
} }
if let Ok(install) = self.installation_rx.try_recv() { if let Ok(install) = self.installation_rx.try_recv() {
self.is_installing = !install; self.is_installing = !install;
if !install && self.is_loading { self.has_install_failed = true } if !install && self.is_loading {
self.has_install_failed = true
}
self.is_loading = false; self.is_loading = false;
} }
if let Ok(timeout) = self.core_timeout_rx.try_recv() { if let Ok(timeout) = self.core_timeout_rx.try_recv() {
@ -226,7 +235,7 @@ impl eframe::App for HomeApp {
self.is_loading = false; self.is_loading = false;
} }
}); });
// Installation Manager window // Installation Manager window
//----------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------
let mut is_installing = self.is_installing; let mut is_installing = self.is_installing;
@ -251,7 +260,11 @@ impl eframe::App for HomeApp {
if !self.is_loading { if !self.is_loading {
if ui.button("Install").clicked() { if ui.button("Install").clicked() {
self.is_loading = true; self.is_loading = true;
install_software_req(self.installation_tx.clone(), ctx.clone(), &self.installations); install_software_req(
self.installation_tx.clone(),
ctx.clone(),
&self.installations,
);
} }
} }
} }
@ -389,18 +402,23 @@ fn send_reset_refresh(tx: Sender<bool>, ctx: egui::Context, init: bool) {
}); });
} }
fn start_core_timeout fn start_core_timeout(tx: Sender<bool>, ctx: egui::Context) {
(tx: Sender<bool>, ctx: egui::Context) {
tokio::spawn(async move { tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_secs(crate::START_CORE_TIMEOUT_SECS)).await; tokio::time::sleep(std::time::Duration::from_secs(
crate::START_CORE_TIMEOUT_SECS,
))
.await;
log::error!("start nevmes-core timeout"); log::error!("start nevmes-core timeout");
let _ = tx.send(true); let _ = tx.send(true);
ctx.request_repaint(); ctx.request_repaint();
}); });
} }
fn install_software_req fn install_software_req(
(tx: Sender<bool>, ctx: egui::Context, installations: &utils::Installations) { tx: Sender<bool>,
ctx: egui::Context,
installations: &utils::Installations,
) {
let req_install: utils::Installations = utils::Installations { let req_install: utils::Installations = utils::Installations {
i2p: installations.i2p, i2p: installations.i2p,
i2p_zero: installations.i2p_zero, i2p_zero: installations.i2p_zero,

View file

@ -1,6 +1,9 @@
use crate::CREDENTIAL_KEY; use crate::CREDENTIAL_KEY;
use nevmes_core::*; use nevmes_core::*;
use sha2::{Digest, Sha512}; use sha2::{
Digest,
Sha512,
};
/// TODO(c2m): Create a more secure locking mechanism /// TODO(c2m): Create a more secure locking mechanism
/// ///

View file

@ -1,5 +1,8 @@
use nevmes_core::*; use nevmes_core::*;
use std::sync::mpsc::{Sender, Receiver}; use std::sync::mpsc::{
Receiver,
Sender,
};
pub struct MailBoxApp { pub struct MailBoxApp {
decrypted_message: String, decrypted_message: String,
@ -26,11 +29,12 @@ impl Default for MailBoxApp {
impl eframe::App for MailBoxApp { impl eframe::App for MailBoxApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// Hook into async channel threads // Hook into async channel threads
//----------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------
if let Ok(refresh) = self.refresh_on_delete_rx.try_recv() { if let Ok(refresh) = self.refresh_on_delete_rx.try_recv() {
if refresh { self.message_init = false; } if refresh {
self.message_init = false;
}
} }
// initial message load // initial message load
@ -59,85 +63,99 @@ impl eframe::App for MailBoxApp {
self.messages = message::find_all(); self.messages = message::find_all();
} }
ui.label("\n"); ui.label("\n");
use egui_extras::{Column, TableBuilder}; use egui_extras::{
Column,
TableBuilder,
};
let table = TableBuilder::new(ui) let table = TableBuilder::new(ui)
.striped(true) .striped(true)
.resizable(true) .resizable(true)
.cell_layout(egui::Layout::left_to_right(egui::Align::Center)) .cell_layout(egui::Layout::left_to_right(egui::Align::Center))
.column(Column::auto()) .column(Column::auto())
.column(Column::initial(100.0).at_least(40.0).clip(true)) .column(Column::initial(100.0).at_least(40.0).clip(true))
.column(Column::initial(100.0).at_least(40.0).clip(true)) .column(Column::initial(100.0).at_least(40.0).clip(true))
.column(Column::initial(100.0).at_least(40.0).clip(true)) .column(Column::initial(100.0).at_least(40.0).clip(true))
.column(Column::remainder()) .column(Column::remainder())
.min_scrolled_height(0.0); .min_scrolled_height(0.0);
table table
.header(20.0, |mut header| { .header(20.0, |mut header| {
header.col(|ui| { header.col(|ui| {
ui.strong("Date"); ui.strong("Date");
});
header.col(|ui| {
ui.strong("From");
});
header.col(|ui| {
ui.strong("To");
});
header.col(|ui| {
ui.strong("Message");
});
header.col(|ui| {
ui.strong("");
});
})
.body(|mut body|
for m in &self.messages {
let row_height = 200.0;
body.row(row_height, |mut row| {
row.col(|ui| {
let h = chrono::NaiveDateTime::from_timestamp_opt(m.created, 0).unwrap().to_string();
ui.label(format!("{}", h));
});
row.col(|ui| {
ui.label(format!("{}", m.from));
});
row.col(|ui| {
ui.label(format!("{}", m.to));
});
row.col(|ui| {
ui.label(format!("{}",
String::from_utf8(m.body.iter().cloned().collect()).unwrap()));
});
row.col(|ui| {
ui.style_mut().wrap = Some(false);
ui.horizontal(|ui| {
if m.from != i2p::get_destination() {
if ui.button("Decrypt").clicked() {
let mut d = message::decrypt_body(m.mid.clone());
let mut bytes = hex::decode(d.body.into_bytes())
.unwrap_or(Vec::new());
self.decrypted_message = String::from_utf8(bytes)
.unwrap_or(utils::empty_string());
self.is_showing_decryption = true;
d = Default::default();
bytes = Vec::new();
log::debug!("cleared decryption bytes: {:?} string: {}", bytes, d.body);
}
}
if ui.button("Delete").clicked() {
message::delete(&m.mid);
refresh_on_delete_req(self.refresh_on_delete_tx.clone(), ctx.clone())
}
});
});
});
}); });
header.col(|ui| {
ui.strong("From");
});
header.col(|ui| {
ui.strong("To");
});
header.col(|ui| {
ui.strong("Message");
});
header.col(|ui| {
ui.strong("");
});
})
.body(|mut body| {
for m in &self.messages {
let row_height = 200.0;
body.row(row_height, |mut row| {
row.col(|ui| {
let h = chrono::NaiveDateTime::from_timestamp_opt(m.created, 0)
.unwrap()
.to_string();
ui.label(format!("{}", h));
});
row.col(|ui| {
ui.label(format!("{}", m.from));
});
row.col(|ui| {
ui.label(format!("{}", m.to));
});
row.col(|ui| {
ui.label(format!(
"{}",
String::from_utf8(m.body.iter().cloned().collect()).unwrap()
));
});
row.col(|ui| {
ui.style_mut().wrap = Some(false);
ui.horizontal(|ui| {
if m.from != i2p::get_destination() {
if ui.button("Decrypt").clicked() {
let mut d = message::decrypt_body(m.mid.clone());
let mut bytes = hex::decode(d.body.into_bytes())
.unwrap_or(Vec::new());
self.decrypted_message = String::from_utf8(bytes)
.unwrap_or(utils::empty_string());
self.is_showing_decryption = true;
d = Default::default();
bytes = Vec::new();
log::debug!(
"cleared decryption bytes: {:?} string: {}",
bytes,
d.body
);
}
}
if ui.button("Delete").clicked() {
message::delete(&m.mid);
refresh_on_delete_req(
self.refresh_on_delete_tx.clone(),
ctx.clone(),
)
}
});
});
});
}
});
}); });
} }
} }
fn refresh_on_delete_req fn refresh_on_delete_req(tx: Sender<bool>, ctx: egui::Context) {
(tx: Sender<bool>, ctx: egui::Context) {
tokio::spawn(async move { tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_secs(1)).await; tokio::time::sleep(std::time::Duration::from_secs(1)).await;
log::error!("refreshing messages...."); log::error!("refreshing messages....");

View file

@ -1,14 +1,13 @@
mod home;
mod address_book; mod address_book;
mod home;
mod lock_screen; mod lock_screen;
mod mailbox; mod mailbox;
mod settings; mod settings;
mod wallet; mod wallet;
pub use address_book::AddressBookApp; pub use address_book::AddressBookApp;
pub use lock_screen::LockScreenApp;
pub use home::HomeApp; pub use home::HomeApp;
pub use lock_screen::LockScreenApp;
pub use mailbox::MailBoxApp; pub use mailbox::MailBoxApp;
pub use settings::SettingsApp; pub use settings::SettingsApp;
pub use wallet::WalletApp; pub use wallet::WalletApp;

View file

@ -1,5 +1,8 @@
use nevmes_core::*; use nevmes_core::*;
use sha2::{Digest, Sha512}; use sha2::{
Digest,
Sha512,
};
use crate::CREDENTIAL_KEY; use crate::CREDENTIAL_KEY;
@ -21,10 +24,10 @@ impl eframe::App for SettingsApp {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
ctx.settings_ui(ui); ctx.settings_ui(ui);
ui.label("\n\n"); ui.label("\n\n");
ui.heading("Reset Credential"); ui.heading("Reset Credential");
ui.label( ui.label(
"____________________________________________________________________________\n", "____________________________________________________________________________\n",
); );
ui.horizontal(|ui| { ui.horizontal(|ui| {
let sweep_label = ui.label("new credential: \t"); let sweep_label = ui.label("new credential: \t");
ui.text_edit_singleline(&mut self.credential) ui.text_edit_singleline(&mut self.credential)

View file

@ -1,7 +1,10 @@
use nevmes_core::*;
use image::Luma; use image::Luma;
use nevmes_core::*;
use qrcode::QrCode; use qrcode::QrCode;
use std::sync::mpsc::{Receiver, Sender}; use std::sync::mpsc::{
Receiver,
Sender,
};
pub struct WalletApp { pub struct WalletApp {
pub init: bool, pub init: bool,
@ -90,8 +93,8 @@ impl eframe::App for WalletApp {
self.init = true; self.init = true;
self.is_qr_set = true; self.is_qr_set = true;
let contents = std::fs::read(&file_path).unwrap_or(Vec::new()); let contents = std::fs::read(&file_path).unwrap_or(Vec::new());
self.qr = egui_extras::RetainedImage::from_image_bytes( self.qr =
"qr.png", &contents,).unwrap(); egui_extras::RetainedImage::from_image_bytes("qr.png", &contents).unwrap();
ctx.request_repaint(); ctx.request_repaint();
} }
self.qr.show(ui); self.qr.show(ui);
@ -102,9 +105,9 @@ impl eframe::App for WalletApp {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Address"); ui.heading("Address");
ui.label( ui.label(
"____________________________________________________________________________\n", "____________________________________________________________________________\n",
); );
ui.horizontal(|ui| { ui.horizontal(|ui| {
if ui.button("Show QR").clicked() { if ui.button("Show QR").clicked() {
self.is_showing_qr = true; self.is_showing_qr = true;
@ -120,7 +123,11 @@ impl eframe::App for WalletApp {
ui.text_edit_singleline(&mut self.sweep_address) ui.text_edit_singleline(&mut self.sweep_address)
.labelled_by(sweep_label.id); .labelled_by(sweep_label.id);
if ui.button("Sweep").clicked() { if ui.button("Sweep").clicked() {
send_sweep_all_req(self.xmr_sweep_all_tx.clone(), ctx.clone(), self.sweep_address.clone()); send_sweep_all_req(
self.xmr_sweep_all_tx.clone(),
ctx.clone(),
self.sweep_address.clone(),
);
self.sweep_address = utils::empty_string(); self.sweep_address = utils::empty_string();
self.is_showing_sweep_result = true; self.is_showing_sweep_result = true;
self.is_loading = true; self.is_loading = true;
@ -138,7 +145,11 @@ fn send_address_req(tx: Sender<reqres::XmrRpcAddressResponse>, ctx: egui::Contex
}); });
} }
fn send_sweep_all_req(tx: Sender<reqres::XmrRpcSweepAllResponse>, ctx: egui::Context, address: String) { fn send_sweep_all_req(
tx: Sender<reqres::XmrRpcSweepAllResponse>,
ctx: egui::Context,
address: String,
) {
tokio::spawn(async move { tokio::spawn(async move {
let result: reqres::XmrRpcSweepAllResponse = monero::sweep_all(address).await; let result: reqres::XmrRpcSweepAllResponse = monero::sweep_all(address).await;
let _ = tx.send(result); let _ = tx.send(result);

View file

@ -8,7 +8,7 @@ mod wrap_app;
/// key for fetching the login credential hash /// key for fetching the login credential hash
pub const CREDENTIAL_KEY: &str = "NEVMES_GUI_KEY"; pub const CREDENTIAL_KEY: &str = "NEVMES_GUI_KEY";
/// TODO(c2m): configurable lock screen timeout /// TODO(c2m): configurable lock screen timeout
pub const LOCK_SCREEN_TIMEOUT_SECS: u64 = 60*5; pub const LOCK_SCREEN_TIMEOUT_SECS: u64 = 60 * 5;
/// interval to search for credential on initial gui load /// interval to search for credential on initial gui load
pub const CRED_CHECK_INTERVAL: u64 = 5; pub const CRED_CHECK_INTERVAL: u64 = 5;
/// monero estimated block time in seconds /// monero estimated block time in seconds

View file

@ -1,6 +1,9 @@
use nevmes_core::*;
use sha2::{Sha512, Digest};
use crate::CREDENTIAL_KEY; use crate::CREDENTIAL_KEY;
use nevmes_core::*;
use sha2::{
Digest,
Sha512,
};
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct LoginApp { pub struct LoginApp {
@ -12,7 +15,10 @@ impl Default for LoginApp {
fn default() -> Self { fn default() -> Self {
let credential = utils::empty_string(); let credential = utils::empty_string();
let is_cred_generated = false; let is_cred_generated = false;
LoginApp { credential, is_cred_generated } LoginApp {
credential,
is_cred_generated,
}
} }
} }
@ -44,4 +50,3 @@ impl eframe::App for LoginApp {
}); });
} }
} }

View file

@ -32,7 +32,15 @@ fn main() -> Result<(), eframe::Error> {
// Execute the runtime in its own thread. // Execute the runtime in its own thread.
// The future doesn't have to do anything. In this example, it just sleeps forever. // The future doesn't have to do anything. In this example, it just sleeps forever.
std::thread::spawn(move || { std::thread::spawn(move || {
rt.block_on(async { loop { tokio::time::sleep(Duration::from_secs(3600)).await; } }) rt.block_on(async {
loop {
tokio::time::sleep(Duration::from_secs(3600)).await;
}
})
}); });
eframe::run_native("nevmes-gui-v0.3.0-alpha", options, Box::new(|cc| Box::new(nevmes_gui::WrapApp::new(cc))),) eframe::run_native(
"nevmes-gui-v0.3.0-alpha",
options,
Box::new(|cc| Box::new(nevmes_gui::WrapApp::new(cc))),
)
} }

View file

@ -2,9 +2,10 @@
use eframe::glow; use eframe::glow;
use nevmes_core::*; use nevmes_core::*;
use std::sync::mpsc::{Receiver, Sender}; use std::sync::mpsc::{
Receiver,
Sender,
};
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -65,7 +66,6 @@ pub struct State {
lock_timer_tx: Sender<bool>, lock_timer_tx: Sender<bool>,
lock_timer_rx: Receiver<bool>, lock_timer_rx: Receiver<bool>,
// end async notifications // end async notifications
} }
impl Default for State { impl Default for State {
@ -160,7 +160,7 @@ impl eframe::App for WrapApp {
} }
if let Ok(lock) = self.state.is_screen_locked_rx.try_recv() { if let Ok(lock) = self.state.is_screen_locked_rx.try_recv() {
self.state.is_screen_locked = lock; self.state.is_screen_locked = lock;
if lock { if lock {
let lock_screen = &mut self.state.lock_screen; let lock_screen = &mut self.state.lock_screen;
if self.state.lock_timer >= crate::LOCK_SCREEN_TIMEOUT_SECS { if self.state.lock_timer >= crate::LOCK_SCREEN_TIMEOUT_SECS {
lock_screen.set_lock(); lock_screen.set_lock();
@ -169,7 +169,9 @@ impl eframe::App for WrapApp {
} }
} }
if let Ok(lock_timer) = self.state.lock_timer_rx.try_recv() { if let Ok(lock_timer) = self.state.lock_timer_rx.try_recv() {
if lock_timer { self.state.lock_timer += 1 } if lock_timer {
self.state.lock_timer += 1
}
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
@ -195,7 +197,11 @@ impl eframe::App for WrapApp {
send_inc_lock_timer_req(self.state.lock_timer_tx.clone(), ctx.clone()); send_inc_lock_timer_req(self.state.lock_timer_tx.clone(), ctx.clone());
} }
if (!self.state.is_screen_locking && self.state.is_cred_set) || app_initializing { if (!self.state.is_screen_locking && self.state.is_cred_set) || app_initializing {
self.send_lock_refresh(self.state.is_screen_locked_tx.clone(), ctx.clone(), app_initializing); self.send_lock_refresh(
self.state.is_screen_locked_tx.clone(),
ctx.clone(),
app_initializing,
);
self.state.is_screen_locking = true; self.state.is_screen_locking = true;
} }
self.show_selected_app(ctx, frame); self.show_selected_app(ctx, frame);
@ -210,7 +216,6 @@ impl eframe::App for WrapApp {
fn on_exit(&mut self, _gl: Option<&glow::Context>) { fn on_exit(&mut self, _gl: Option<&glow::Context>) {
utils::kill_child_processes(false); utils::kill_child_processes(false);
} }
} }
impl WrapApp { impl WrapApp {
@ -274,24 +279,27 @@ impl WrapApp {
fn send_lock_refresh(&mut self, tx: Sender<bool>, ctx: egui::Context, init: bool) { fn send_lock_refresh(&mut self, tx: Sender<bool>, ctx: egui::Context, init: bool) {
tokio::spawn(async move { tokio::spawn(async move {
log::debug!("locking screen"); log::debug!("locking screen");
if !init { if !init {
tokio::time::sleep(std::time::Duration::from_secs(crate::LOCK_SCREEN_TIMEOUT_SECS)).await; tokio::time::sleep(std::time::Duration::from_secs(
crate::LOCK_SCREEN_TIMEOUT_SECS,
))
.await;
} }
let _= tx.send(true); let _ = tx.send(true);
ctx.request_repaint(); ctx.request_repaint();
}); });
} }
/* /*
TODO(c2m): SECURITY!: TODO(c2m): SECURITY!:
Ok, so this here is by far the greatest security loophole. Ok, so this here is by far the greatest security loophole.
An attacker could reset the credential in the db to any value, An attacker could reset the credential in the db to any value,
besides setting the wallet password on initial load, better change besides setting the wallet password on initial load, better change
the key for storing the random 32 byte credential to be some strong the key for storing the random 32 byte credential to be some strong
user entry and then reset wallet password with that. But anyways if user entry and then reset wallet password with that. But anyways if
someone has access to the machine it sucks because nevmes gpg key someone has access to the machine it sucks because nevmes gpg key
doesn't have a passphrase. doesn't have a passphrase.
*/ */
/// Validate that a credential was set by the user; /// Validate that a credential was set by the user;
fn check_credential_key(&mut self, tx: Sender<bool>, ctx: egui::Context) { fn check_credential_key(&mut self, tx: Sender<bool>, ctx: egui::Context) {
@ -303,10 +311,10 @@ impl WrapApp {
let r = db::Interface::read(&s.env, &s.handle, crate::CREDENTIAL_KEY); let r = db::Interface::read(&s.env, &s.handle, crate::CREDENTIAL_KEY);
if r == utils::empty_string() { if r == utils::empty_string() {
log::debug!("credential not found"); log::debug!("credential not found");
let _= tx.send(false); let _ = tx.send(false);
ctx.request_repaint(); ctx.request_repaint();
} else { } else {
let _= tx.send(true); let _ = tx.send(true);
ctx.request_repaint(); ctx.request_repaint();
break; break;
} }
@ -316,19 +324,19 @@ impl WrapApp {
} }
/// When the pointer goes 'active' (i.e. pushing a button, dragging /// When the pointer goes 'active' (i.e. pushing a button, dragging
/// ///
/// a slider, etc) reset it. Otherwise this function runs forever /// a slider, etc) reset it. Otherwise this function runs forever
/// ///
/// incrementing by one every second. Once this timer matches the /// incrementing by one every second. Once this timer matches the
/// ///
/// `LOCK_SCREEN_TIMEOUT_SECS` constant the lock screen will trigger. /// `LOCK_SCREEN_TIMEOUT_SECS` constant the lock screen will trigger.
fn send_inc_lock_timer_req(tx: Sender<bool>, ctx: egui::Context) { fn send_inc_lock_timer_req(tx: Sender<bool>, ctx: egui::Context) {
tokio::spawn(async move { tokio::spawn(async move {
log::debug!("starting the lock screen timer"); log::debug!("starting the lock screen timer");
loop { loop {
tokio::time::sleep(std::time::Duration::from_secs(1)).await; tokio::time::sleep(std::time::Duration::from_secs(1)).await;
let _= tx.send(true); let _ = tx.send(true);
ctx.request_repaint(); ctx.request_repaint();
} }
}); });
} }

View file

@ -0,0 +1,6 @@
# cargo +nightly fmt
comment_width = 100
format_code_in_doc_comments = true
imports_granularity = "Crate"
imports_layout = "Vertical"
wrap_comments = true

View file

@ -1,30 +1,41 @@
use rocket::http::Status; use rocket::{
use rocket::response::status::Custom; get,
use rocket::serde::json::Json; http::Status,
use rocket::{get, post}; post,
response::status::Custom,
serde::json::Json,
};
use nevmes_core::{auth, message, models::*, proof, reqres}; use nevmes_core::{
auth,
message,
models::*,
proof,
reqres,
};
/// Send message /// Send message
#[post("/", data="<m_req>")] #[post("/", data = "<m_req>")]
pub async fn send_message pub async fn send_message(
(m_req: Json<Message>, token: proof::PaymentProof) -> Custom<Json<Message>> { m_req: Json<Message>,
token: proof::PaymentProof,
) -> Custom<Json<Message>> {
let res: Message = message::create(m_req, token.get_jwp()).await; let res: Message = message::create(m_req, token.get_jwp()).await;
Custom(Status::Ok, Json(res)) Custom(Status::Ok, Json(res))
} }
/// Return all messages /// Return all messages
#[get("/")] #[get("/")]
pub async fn get_messages pub async fn get_messages(_token: auth::BearerToken) -> Custom<Json<Vec<Message>>> {
(_token: auth::BearerToken) -> Custom<Json<Vec<Message>>> {
let messages = message::find_all(); let messages = message::find_all();
Custom(Status::Ok, Json(messages)) Custom(Status::Ok, Json(messages))
} }
/// decrypt a message body /// decrypt a message body
#[get("/<mid>")] #[get("/<mid>")]
pub async fn decrypt pub async fn decrypt(
(mid: String, _token: auth::BearerToken mid: String,
_token: auth::BearerToken,
) -> Custom<Json<reqres::DecryptedMessageBody>> { ) -> Custom<Json<reqres::DecryptedMessageBody>> {
let d_message = message::decrypt_body(mid); let d_message = message::decrypt_body(mid);
Custom(Status::Ok, Json(d_message)) Custom(Status::Ok, Json(d_message))

View file

@ -1,8 +1,8 @@
#[macro_use] #[macro_use]
extern crate rocket; extern crate rocket;
use nevmes_message::*;
use nevmes_core::*; use nevmes_core::*;
use nevmes_message::*;
// The only changes in here should be mounting new controller methods // The only changes in here should be mounting new controller methods

8
scripts/fmtall.sh Executable file
View file

@ -0,0 +1,8 @@
#!/bin/bash
# Run from the nevmes root
cd nevmes-auth && cargo +nightly fmt
cd ../nevmes-contact && cargo +nightly fmt
cd ../nevmes-core && cargo +nightly fmt
cd ../nevmes-gui && cargo +nightly fmt
cd ../nevmes-message && cargo +nightly fmt
cd ../ && cargo +nightly fmt

View file

@ -1,9 +1,20 @@
use rocket::http::Status; use rocket::{
use rocket::response::status::Custom; get,
use rocket::serde::json::Json; http::Status,
use rocket::{get, post}; post,
response::status::Custom,
serde::json::Json,
};
use nevmes_core::{contact, i2p, message, models, monero, proof, reqres}; use nevmes_core::{
contact,
i2p,
message,
models,
monero,
proof,
reqres,
};
// JSON APIs exposed over i2p // JSON APIs exposed over i2p

View file

@ -1,28 +1,42 @@
#[macro_use] #[macro_use]
extern crate rocket; extern crate rocket;
use rocket::http::Status; use rocket::{
use rocket::serde::json::Json; http::Status,
use rocket::response::status::Custom; response::status::Custom,
serde::json::Json,
};
use nevmes::*; use nevmes::*;
use nevmes_core::*; use nevmes_core::*;
#[catch(402)] #[catch(402)]
fn payment_required() -> Custom<Json<reqres::ErrorResponse>> { fn payment_required() -> Custom<Json<reqres::ErrorResponse>> {
Custom(Status::PaymentRequired, Custom(
Json(reqres::ErrorResponse { error: String::from("Payment required") })) Status::PaymentRequired,
Json(reqres::ErrorResponse {
error: String::from("Payment required"),
}),
)
} }
#[catch(404)] #[catch(404)]
fn not_found() -> Custom<Json<reqres::ErrorResponse>> { fn not_found() -> Custom<Json<reqres::ErrorResponse>> {
Custom(Status::NotFound, Custom(
Json(reqres::ErrorResponse { error: String::from("Resource does not exist") })) Status::NotFound,
Json(reqres::ErrorResponse {
error: String::from("Resource does not exist"),
}),
)
} }
#[catch(500)] #[catch(500)]
fn internal_error() -> Custom<Json<reqres::ErrorResponse>> { fn internal_error() -> Custom<Json<reqres::ErrorResponse>> {
Custom(Status::InternalServerError, Custom(
Json(reqres::ErrorResponse { error: String::from("Internal server error") })) Status::InternalServerError,
Json(reqres::ErrorResponse {
error: String::from("Internal server error"),
}),
)
} }
// The only changes below here should be mounting new controller methods // The only changes below here should be mounting new controller methods