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
* ubuntu example: `sudo apt update -y && sudo apt upgrade -y`
* `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`
* `cd nevmes && ./scripts/build_all_and_run.sh "-- -h"`
* 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
```bash
@ -36,7 +47,10 @@ NEVidebla-MESago (invisible message)
```
* code on dev branch
* run `./scripts/fmtall.sh` before committing
* 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 vX.X.X to main
* 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-contact - `internal` add contacts server
* nevmes-core - application core logic
* nevmes-gui - primary user interface
* nevmes-message - `internal` message tx/read etc. server
* 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](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
[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::response::status::Custom;
use rocket::serde::json::Json;
use rocket::get;
use rocket::{
get,
http::Status,
response::status::Custom,
serde::json::Json,
};
use nevmes_core::{auth, models::*};
use nevmes_core::{
auth,
models::*,
};
/// Login with wallet signature
///
/// Creates user on initial login
///
#[get("/login/<signature>/<aid>/<uid>")]
pub async fn login
(aid: String, uid: String,signature: String) -> Custom<Json<Authorization>> {
pub async fn login(aid: String, uid: String, signature: String) -> Custom<Json<Authorization>> {
let m_auth: Authorization = auth::verify_login(aid, uid, signature).await;
Custom(Status::Created, Json(m_auth))
}

View file

@ -14,6 +14,5 @@ async fn rocket() -> _ {
};
env_logger::init();
log::info!("nevmes-auth is online");
rocket::custom(&config)
.mount("/", routes![controller::login])
rocket::custom(&config).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::response::status::Custom;
use rocket::serde::json::Json;
use rocket::{get, post};
use rocket::{
get,
http::Status,
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
#[post("/", data="<req_contact>")]
pub async fn add_contact
(req_contact: Json<Contact>,_token: auth::BearerToken) -> Custom<Json<Contact>> {
#[post("/", data = "<req_contact>")]
pub async fn add_contact(
req_contact: Json<Contact>,
_token: auth::BearerToken,
) -> Custom<Json<Contact>> {
let res_contact = contact::create(&req_contact).await;
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))
}
/// Return all contacts
#[get("/")]
pub async fn get_contacts
(_token: auth::BearerToken) -> Custom<Json<Vec<Contact>>> {
pub async fn get_contacts(_token: auth::BearerToken) -> Custom<Json<Vec<Contact>>> {
let contacts = contact::find_all();
Custom(Status::Ok, Json(contacts))
}
/// trust contact
#[post("/<key>")]
pub async fn trust_contact
(key: String, _token: auth::BearerToken) -> Status {
pub async fn trust_contact(key: String, _token: auth::BearerToken) -> Status {
contact::trust_gpg(key);
Status::Ok
}
/// prove payment
#[get("/<contact>", data="<proof_req>")]
pub async fn prove_payment
(contact: String, proof_req: Json<proof::TxProof>, _token: auth::BearerToken
#[get("/<contact>", data = "<proof_req>")]
pub async fn prove_payment(
contact: String,
proof_req: Json<proof::TxProof>,
_token: auth::BearerToken,
) -> Custom<Json<reqres::Jwp>> {
let r_jwp = proof::prove_payment(contact, &proof_req).await;
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 log::{debug, error, info};
use log::{
debug,
error,
info,
};
use rocket::http::Status;
use rocket::outcome::Outcome;
use rocket::request::FromRequest;
use rocket::{request, Request};
use rocket::{
http::Status,
outcome::Outcome,
request,
request::FromRequest,
Request,
};
use hmac::{Hmac, Mac};
use hmac::{
Hmac,
Mac,
};
use jwt::*;
use sha2::Sha384;
use std::collections::BTreeMap;
@ -70,8 +88,7 @@ fn update_expiration(f_auth: Authorization, address: &String) -> Authorization {
}
/// Performs the signature verfication against stored auth
pub async fn verify_login
(aid: String, uid: String, signature: String) -> Authorization {
pub async fn verify_login(aid: String, uid: String, signature: String) -> Authorization {
let m_address: reqres::XmrRpcAddressResponse = monero::get_address().await;
let address = m_address.result.address;
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 s = db::Interface::open();
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));
return u_auth
db::Interface::write(
&s.env,
&s.handle,
&u_auth.aid,
&Authorization::to_db(&u_auth),
);
return u_auth;
} else if f_user.xmr_address != utils::empty_string() {
info!("returning user");
let m_access = verify_access(&address, &signature).await;
if !m_access { return Default::default() }
if !m_access {
return Default::default();
}
return f_auth;
} else {
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 dev = utils::ReleaseEnvironment::Development;
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 m_address: reqres::XmrRpcAddressResponse = monero::get_address().await;

View file

@ -1,7 +1,19 @@
// 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 log::{debug, error, info};
use std::error::Error;
/// 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));
if r == utils::empty_string() {
error!("contact not found");
return Default::default()
return Default::default();
}
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));
if r == utils::empty_string() {
error!("contact index not found");
return Default::default()
return Default::default();
}
let v_cid = r.split(",");
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);
let validate_address = monero::validate_address(&j.xmr_address).await;
j.cid.len() < utils::string_limit()
&& j.i2p_address.len() < utils::string_limit()
&& j.i2p_address.contains(".b32.i2p")
&& j.gpg_key.len() < utils::gpg_key_limit()
&& validate_address.result.valid
&& j.i2p_address.len() < utils::string_limit()
&& j.i2p_address.contains(".b32.i2p")
&& j.gpg_key.len() < utils::gpg_key_limit()
&& validate_address.result.valid
}
/// Send our information
@ -84,13 +96,20 @@ pub async fn share() -> Contact {
let gpg_key = gpg::export_key().unwrap_or(Vec::new());
let i2p_address = i2p::get_destination();
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 {
let all = find_all();
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);
}
@ -103,7 +122,9 @@ pub fn exists(from: &String) -> bool {
/// using the app already have some level of knowledge about each other.
///
/// 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
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 proxy = reqwest::Proxy::http(&host)?;
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) => {
let res = response.json::<reqres::Invoice>().await;
debug!("invoice request response: {:?}", res);
match res {
Ok(r) => {
Ok(r)
},
Ok(r) => Ok(r),
_ => 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 proxy = reqwest::Proxy::http(&host)?;
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) => {
let res = response.json::<Contact>().await;
debug!("share response: {:?}", res);
match res {
Ok(r) => {
Ok(r)
},
Ok(r) => Ok(r),
_ => Ok(Default::default()),
}
}

View file

@ -1,8 +1,16 @@
// db created and exported from here
extern crate lmdb_rs as lmdb;
use log::{debug, error};
use lmdb::{EnvBuilder, DbFlags, Environment, DbHandle};
use lmdb::{
DbFlags,
DbHandle,
EnvBuilder,
Environment,
};
use log::{
debug,
error,
};
use crate::utils;
@ -14,10 +22,17 @@ pub struct Interface {
impl Interface {
pub fn open() -> Self {
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";
if release_env != utils::ReleaseEnvironment::Development { env_str = "lmdb"; };
let env = EnvBuilder::new().open(format!("{}/{}", file_path, env_str), 0o777).unwrap();
if release_env != utils::ReleaseEnvironment::Development {
env_str = "lmdb";
};
let env = EnvBuilder::new()
.open(format!("{}/{}", file_path, env_str), 0o777)
.unwrap();
let handle = env.get_default_db(DbFlags::empty()).unwrap();
Interface { env, handle }
}
@ -26,12 +41,14 @@ impl Interface {
{
// get a database bound to this transaction
let db = txn.bind(&h);
let pair = vec![(k,v)];
for &(key, value) in pair.iter() { db.set(&key, &value).unwrap(); }
let pair = vec![(k, v)];
for &(key, value) in pair.iter() {
db.set(&key, &value).unwrap();
}
}
match txn.commit() {
Err(_) => error!("failed to commit!"),
Ok(_) => ()
Ok(_) => (),
}
}
pub fn read(e: &Environment, h: &DbHandle, k: &str) -> String {
@ -51,11 +68,11 @@ impl Interface {
{
// get a database bound to this transaction
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() {
Err(_) => error!("failed to commit!"),
Ok(_) => ()
Ok(_) => (),
}
}
}

View file

@ -1,8 +1,19 @@
use log::{debug, error, info};
use std::process::Command;
use crate::{
i2p,
utils,
};
use gpgme::*;
use std::{error::Error, fs::File, io::Write};
use crate::{utils, i2p};
use log::{
debug,
error,
info,
};
use std::{
error::Error,
fs::File,
io::Write,
process::Command,
};
/// 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 mode = None;
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);
Ok(())
}
@ -82,19 +94,23 @@ pub fn encrypt(name: String, body: &Vec<u8>) -> Result<Vec<u8>, Box<dyn Error>>
let proto = Protocol::OpenPgp;
let mut ctx = Context::from_protocol(proto)?;
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(|k| k.can_encrypt())
.collect();
let filename = format!("{}.nevmes", name);
let mut f = File::create(&filename)?;
f.write_all(body)?;
let mut input = File::open(&filename)
.map_err(|e| format!("can't open file `{}': {}", filename, e))?;
let mut input =
File::open(&filename).map_err(|e| format!("can't open file `{}': {}", filename, e))?;
let mut output = Vec::new();
ctx.encrypt(&keys, &mut input, &mut output)
.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);
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 mut f = File::create(&filename)?;
f.write_all(&body)?;
let mut input = File::open(&filename)
.map_err(|e| format!("can't open file `{}': {}", filename, e))?;
let mut input =
File::open(&filename).map_err(|e| format!("can't open file `{}': {}", filename, e))?;
let mut output = Vec::new();
ctx.decrypt(&mut input, &mut output)
.map_err(|e| format!("decrypting failed: {:?}", e))?;
@ -125,7 +141,8 @@ pub fn write_gen_batch() -> Result<(), Box<dyn Error>> {
Subkey-Curve: Curve25519
Name-Real: {}
Name-Email: {}
Expire-Date: 0", name, name
Expire-Date: 0",
name, name
);
let filename = format!("genkey-batch");
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)
.map_err(|e| format!("unable to find signing key: {:?}", e))?;
debug!("app key: {:?}", key.id());
k2s_ctx.add_signer(&key)
k2s_ctx
.add_signer(&key)
.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))?;
println!("Signed key for {}", key);

View file

@ -1,9 +1,24 @@
use std::{fs, env, process::Command};
use log::{debug, error, info, warn};
use serde::{Deserialize, Serialize};
use crate::{args, utils};
use crate::{
args,
utils,
};
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
@ -24,13 +39,13 @@ impl I2pStatus {
#[derive(Debug, Deserialize, Serialize)]
pub struct HttpProxyStatus {
pub open: bool
pub open: bool,
}
#[derive(Debug)]
pub enum ProxyStatus {
Opening,
Open
Open,
}
impl ProxyStatus {
@ -56,7 +71,9 @@ struct Tunnels {
impl Default for Tunnels {
fn default() -> Self {
Tunnels { tunnels: Vec::new() }
Tunnels {
tunnels: Vec::new(),
}
}
}
@ -103,7 +120,11 @@ fn create_tunnel() {
let args = args::Args::parse();
let path = args.i2p_zero_dir;
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()
.expect("i2p-zero failed to create a tunnel");
debug!("{:?}", output.stdout);
@ -125,17 +146,20 @@ pub fn get_destination() -> String {
env::var("USER").unwrap_or(String::from("user"))
);
// Don't panic if i2p-zero isn't installed
let contents = match fs::read_to_string(file_path)
{
Ok(file) => file,
_=> utils::empty_string(),
};
let contents = match fs::read_to_string(file_path) {
Ok(file) => file,
_ => utils::empty_string(),
};
if contents != utils::empty_string() {
let input = format!(r#"{contents}"#);
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());
return destination
return destination;
}
utils::empty_string()
}
@ -168,9 +192,9 @@ pub async fn check_connection() -> HttpProxyStatus {
}
}
_ => {
error!("i2p status check failure");
return HttpProxyStatus { open: false };
}
error!("i2p status check failure");
return HttpProxyStatus { open: false };
}
}
}
Err(e) => {

View file

@ -1,27 +1,30 @@
pub mod args; // command line arguments
pub mod auth; // internal auth repo/service layer
pub mod contact; // contact repo/service layer
pub mod db; // lmdb interface
pub mod gpg; // gpgme interface
pub mod i2p; // i2p repo/service layer
pub mod message; // message repo/service layer
pub mod models; // db structs
pub mod monero; // monero-wallet-rpc interface
pub mod proof; // external auth/payment proof module
pub mod reqres; // http request/responses
pub mod utils; // misc.
pub mod user; // user rep/service layer
pub mod args; // command line arguments
pub mod auth; // internal auth repo/service layer
pub mod contact; // contact repo/service layer
pub mod db; // lmdb interface
pub mod gpg; // gpgme interface
pub mod i2p; // i2p repo/service layer
pub mod message; // message repo/service layer
pub mod models; // db structs
pub mod monero; // monero-wallet-rpc interface
pub mod proof; // external auth/payment proof module
pub mod reqres; // http request/responses
pub mod user;
pub mod utils; // misc. // user rep/service layer
pub const NEVMES_JWP_SECRET_KEY: &str = "NEVMES_JWP_SECRET_KEY";
pub const NEVMES_JWT_SECRET_KEY: &str = "NEVMES_JWT_SECRET_KEY";
/// The latest monero release download
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
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
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

View file

@ -1,9 +1,21 @@
// Message repo/service layer
use crate::{contact, db, models::*, utils, reqres, i2p, gpg};
use std::error::Error;
use log::{debug, error, info};
use crate::{
contact,
db,
gpg,
i2p,
models::*,
reqres,
utils,
};
use log::{
debug,
error,
info,
};
use reqwest::StatusCode;
use rocket::serde::json::Json;
use std::error::Error;
/// Create a new 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();
// get contact public gpg key and encrypt the message
debug!("sending message: {:?}", &m);
let e_body = gpg::encrypt(
String::from(&m.to), &m.body).unwrap_or(Vec::new());
let e_body = gpg::encrypt(String::from(&m.to), &m.body).unwrap_or(Vec::new());
let new_message = Message {
mid: String::from(&f_mid),
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>) {
// make sure the message isn't something strange
let is_valid = validate_message(&m);
if !is_valid { return; }
if !is_valid {
return;
}
// don't allow messages from outside the contact list
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 new_message = Message {
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));
if r == utils::empty_string() {
error!("message not found");
return Default::default()
return Default::default();
}
Message::from_db(String::from(mid), r)
}
@ -120,32 +135,38 @@ pub fn find_all() -> Vec<Message> {
/// Tx message
async fn send_message(out: &Message, jwp: &str) -> Result<(), Box<dyn Error>> {
let host = utils::get_i2p_http_proxy();
let proxy = reqwest::Proxy::http(&host)?;
let client = reqwest::Client::builder().proxy(proxy).build();
// 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 {
return match client?.post(format!("http://{}/message/rx", out.to))
.header("proof", jwp).json(&out).send().await {
Ok(response) => {
let status = response.status();
debug!("send response: {:?}", status.as_str());
if status == StatusCode::OK || status == StatusCode::PAYMENT_REQUIRED {
remove_from_fts(String::from(&out.mid));
return Ok(())
} else {
Ok(())
}
}
Err(e) => {
error!("failed to send message due to: {:?}", e);
return match client?
.post(format!("http://{}/message/rx", out.to))
.header("proof", jwp)
.json(&out)
.send()
.await
{
Ok(response) => {
let status = response.status();
debug!("send response: {:?}", status.as_str());
if status == StatusCode::OK || status == StatusCode::PAYMENT_REQUIRED {
remove_from_fts(String::from(&out.mid));
return Ok(());
} else {
Ok(())
}
}
} else {
Err(e) => {
error!("failed to send message due to: {:?}", e);
Ok(())
}
};
} else {
send_to_retry(String::from(&out.mid)).await;
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 proxy = reqwest::Proxy::http(&host)?;
let client = reqwest::Client::builder().proxy(proxy).build();
match client?.get(format!("http://{}/xmr/rpc/version", contact))
.header("proof", jwp).send().await {
Ok(response) => {
let res = response.json::<reqres::XmrRpcVersionResponse>().await;
debug!("check is contact online by version response: {:?}", res);
match res {
Ok(r) => {
if r.result.version != 0 { Ok(true) } else { Ok(false) }
},
_ => Ok(false),
match client?
.get(format!("http://{}/xmr/rpc/version", contact))
.header("proof", jwp)
.send()
.await
{
Ok(response) => {
let res = response.json::<reqres::XmrRpcVersionResponse>().await;
debug!("check is contact online by version response: {:?}", res);
match res {
Ok(r) => {
if r.result.version != 0 {
Ok(true)
} else {
Ok(false)
}
}
}
Err(e) => {
error!("failed to send message due to: {:?}", e);
Ok(false)
_ => Ok(false),
}
}
Err(e) => {
error!("failed to send message due to: {:?}", e);
Ok(false)
}
}
}
/// stage message for async retry
@ -204,7 +233,10 @@ async fn send_to_retry(mid: String) {
if String::from(&r).contains(&String::from(&mid)) {
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);
// restart fts if not empty
let list_key = format!("fts");
@ -230,9 +262,20 @@ fn remove_from_fts(mid: String) {
debug!("fts is empty");
}
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(",");
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);
}
@ -284,15 +327,14 @@ pub async fn retry_fts() {
fn validate_message(j: &Json<Message>) -> bool {
info!("validating message: {}", &j.mid);
j.mid.len() < utils::string_limit()
&& j.body.len() < utils::message_limit()
&& j.to == i2p::get_destination()
&& j.uid .len() < utils::string_limit()
&& j.body.len() < utils::message_limit()
&& j.to == i2p::get_destination()
&& j.uid.len() < utils::string_limit()
}
fn is_fts_clear(r: String) -> bool {
let v_mid = r.split(",");
let v: Vec<String> = v_mid.map(|s| String::from(s)).collect();
debug!("fts contents: {:#?}", v);
v.len() >= 2 && v[v.len()-1] == utils::empty_string()
&& v[0] == utils::empty_string()
v.len() >= 2 && v[v.len() - 1] == utils::empty_string() && v[0] == utils::empty_string()
}

View file

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

View file

@ -1,14 +1,31 @@
use crate::{db, monero, reqres, utils};
use log::{error, info};
use crate::{
db,
monero,
reqres,
utils,
};
use log::{
error,
info,
};
use rocket::{
http::Status,
outcome::Outcome,
request,
request::FromRequest,
Request,
};
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 serde::{Deserialize, Serialize};
use serde::{
Deserialize,
Serialize,
};
use sha2::Sha512;
use std::collections::BTreeMap;
@ -43,7 +60,11 @@ pub async fn create_invoice() -> reqres::Invoice {
let address = c_address.result.address;
let pay_threshold = utils::get_payment_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
@ -94,7 +115,12 @@ pub async fn prove_payment(contact: String, txp: &TxProof) -> Result<reqres::Jwp
let host = utils::get_i2p_http_proxy();
let proxy = reqwest::Proxy::http(&host)?;
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) => {
let res = response.json::<reqres::Jwp>().await;
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::write(&s.env, &s.handle, &k, &r.jwp);
Ok(r)
},
}
_ => Ok(Default::default()),
}
}
@ -137,7 +163,11 @@ pub async fn prove_payment(contact: String, txp: &TxProof) -> Result<reqres::Jwp
#[derive(Debug)]
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)]
pub enum PaymentProofError {
@ -205,7 +235,10 @@ impl<'r> FromRequest<'r> for PaymentProof {
}
Err(e) => {
error!("jwp error: {:?}", e);
return Outcome::Failure((Status::PaymentRequired, PaymentProofError::Invalid));
return Outcome::Failure((
Status::PaymentRequired,
PaymentProofError::Invalid,
));
}
};
}
@ -224,17 +257,19 @@ async fn validate_proof(txp: &TxProof) -> TxProof {
let p = monero::check_tx_proof(txp).await;
let cth = utils::get_conf_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()
&& p.result.confirmations < cth && p.result.received >= pth;
&& p.result.confirmations < cth
&& p.result.received >= pth;
if lgtm {
return TxProof {
subaddress: String::from(&txp.subaddress),
hash: String::from(&txp.hash),
confirmations: p.result.confirmations,
message: String::from(&txp.message),
signature: String::from(&txp.signature)
}
signature: String::from(&txp.signature),
};
}
Default::default()
}
@ -248,6 +283,8 @@ async fn validate_subaddress(subaddress: &String) -> bool {
let m_address = monero::get_address().await;
let all_address = m_address.result.addresses;
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);
}

View file

@ -1,5 +1,8 @@
use crate::utils;
use serde::{Deserialize, Serialize};
use serde::{
Deserialize,
Serialize,
};
// All http requests and responses are here
@ -363,10 +366,7 @@ pub struct SubAddressIndex {
impl Default for SubAddressIndex {
fn default() -> Self {
SubAddressIndex {
major: 0,
minor: 0,
}
SubAddressIndex { major: 0, minor: 0 }
}
}
@ -820,13 +820,12 @@ impl Default for XmrRpcCreateAddressResponse {
address_index: 0,
address_indices: Vec::new(),
addresses: Vec::new(),
}
},
}
}
}
// END XMR Structs
/// Container for the message decryption
#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]

View file

@ -1,7 +1,15 @@
// 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 log::{debug, error, info};
// 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));
if r == utils::empty_string() {
error!("user not found");
return Default::default()
return Default::default();
}
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 log::{
debug,
error,
info,
warn,
};
use rand_core::RngCore;
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;
/// Enum for selecting hash validation
#[derive(PartialEq)]
enum ExternalSoftware { I2P, I2PZero, XMR }
enum ExternalSoftware {
I2P,
I2PZero,
XMR,
}
/// Handles the state for the installation manager popup
pub struct Installations {
@ -87,15 +106,23 @@ impl ReleaseEnvironment {
pub fn start_core(conn: &Connections) {
let env = if !conn.mainnet { "dev" } else { "prod" };
let args = [
"--monero-location", &conn.monero_location,
"--monero-blockchain-dir", &conn.blockchain_dir,
"--monero-rpc-host", &conn.rpc_host,
"--monero-rpc-daemon", &conn.daemon_host,
"--monero-rpc-username", &conn.rpc_username,
"--monero-rpc-cred", &conn.rpc_credential,
"--i2p-zero-dir", &conn.i2p_zero_dir,
"-r", env,
];
"--monero-location",
&conn.monero_location,
"--monero-blockchain-dir",
&conn.blockchain_dir,
"--monero-rpc-host",
&conn.rpc_host,
"--monero-rpc-daemon",
&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")
.args(args)
.spawn()
@ -107,7 +134,9 @@ pub fn start_core(conn: &Connections) {
pub fn is_using_remote_node() -> bool {
let args = args::Args::parse();
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
}
@ -196,12 +225,20 @@ pub fn message_to_json(m: &models::Message) -> Json<models::Message> {
}
/// Instead of putting `String::from("")`
pub fn empty_string() -> String { String::from("") }
pub fn empty_string() -> String {
String::from("")
}
// DoS prevention
pub const fn string_limit() -> usize { 512 }
pub const fn gpg_key_limit() -> usize { 4096 }
pub const fn message_limit() -> usize { 9999 }
pub const fn string_limit() -> usize {
512
}
pub const fn gpg_key_limit() -> usize {
4096
}
pub const fn message_limit() -> usize {
9999
}
/// Generate application gpg keys at startup if none exist
async fn gen_app_gpg() {
@ -219,15 +256,19 @@ async fn gen_app_gpg() {
/// Handles panic! for missing wallet directory
fn create_wallet_dir() {
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 s_output = std::process::Command::new("mkdir")
.args(["-p", &format!("{}/stagenet/wallet", file_path)])
.spawn().expect("failed to create dir");
.spawn()
.expect("failed to create dir");
debug!("{:?}", s_output);
let m_output = std::process::Command::new("mkdir")
.args(["-p", &format!("{}/wallet", file_path)])
.spawn().expect("failed to create dir");
.spawn()
.expect("failed to create dir");
debug!("{:?}", m_output);
}
@ -243,8 +284,7 @@ async fn gen_app_wallet() {
} else {
m_wallet = monero::open_wallet(String::from(filename)).await;
if m_wallet {
let m_address: reqres::XmrRpcAddressResponse =
monero::get_address().await;
let m_address: reqres::XmrRpcAddressResponse = monero::get_address().await;
info!("app wallet address: {}", m_address.result.address)
}
}
@ -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);
if r == utils::empty_string() {
error!("JWT key not found");
return Default::default()
return Default::default();
}
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);
if r == utils::empty_string() {
error!("JWP key not found");
return Default::default()
return Default::default();
}
r
}
@ -302,21 +342,30 @@ fn start_micro_servers() {
info!("starting auth server");
let mut auth_path = "nevmes-auth/target/debug/nevmes_auth";
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)
.spawn().expect("failed to start auth server");
.spawn()
.expect("failed to start auth server");
debug!("{:?}", a_output.stdout);
info!("starting contact server");
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)
.spawn().expect("failed to start contact server");
.spawn()
.expect("failed to start contact server");
debug!("{:?}", c_output.stdout);
info!("starting message server");
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)
.spawn().expect("failed to start message server");
.spawn()
.expect("failed to start message server");
debug!("{:?}", m_output.stdout);
}
@ -327,9 +376,12 @@ fn start_gui() {
info!("starting gui");
let mut gui_path = "nevmes-gui/target/debug/nevmes_gui";
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)
.spawn().expect("failed to start gui");
.spawn()
.expect("failed to start gui");
debug!("{:?}", g_output.stdout);
}
}
@ -338,10 +390,16 @@ fn start_gui() {
pub async fn start_up() {
info!("nevmes is starting up");
let args = args::Args::parse();
if args.remote_access { start_micro_servers(); }
if args.clear_fts { clear_fts(); }
if args.remote_access {
start_micro_servers();
}
if args.clear_fts {
clear_fts();
}
gen_signing_keys();
if !is_using_remote_node() { monero::start_daemon(); }
if !is_using_remote_node() {
monero::start_daemon();
}
create_wallet_dir();
// wait for daemon for a bit
tokio::time::sleep(std::time::Duration::new(5, 0)).await;
@ -354,7 +412,11 @@ pub async fn start_up() {
gen_app_gpg().await;
let env: String = get_release_env().value();
start_gui();
{ tokio::spawn(async { message::retry_fts().await; }); }
{
tokio::spawn(async {
message::retry_fts().await;
});
}
info!("{} - nevmes is online", env);
}
@ -380,10 +442,10 @@ pub fn kill_child_processes(cm: bool) {
debug!("{:?}", nevmes_output.stdout);
}
let rpc_output = std::process::Command::new("killall")
.arg("monero-wallet-rpc")
.spawn()
.expect("monero-wallet-rpc failed to stop");
debug!("{:?}", rpc_output.stdout);
.arg("monero-wallet-rpc")
.spawn()
.expect("monero-wallet-rpc failed to stop");
debug!("{:?}", rpc_output.stdout);
let i2pz_output = std::process::Command::new("pkill")
.arg("i2p-zero")
.spawn()
@ -393,7 +455,9 @@ pub fn kill_child_processes(cm: bool) {
/// We can restart fts from since it gets terminated when empty
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.
@ -428,7 +492,10 @@ pub async fn install_software(installations: Installations) -> bool {
info!("installing i2p");
let i2p_version = crate::I2P_RELEASE_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")
.args(["-O#", &link])
.status();
@ -441,8 +508,8 @@ pub async fn install_software(installations: Installations) -> bool {
.spawn()
.expect("i2p gui installation failed");
debug!("{:?}", jar_output.stdout);
},
_=> error!("i2p download failed")
}
_ => error!("i2p download failed"),
}
valid_i2p_hash = validate_installation_hash(ExternalSoftware::I2P, &i2p_jar);
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");
let i2p_version = crate::I2P_ZERO_RELEASE_VERSION;
let i2p_zero_zip = format!("i2p-zero-linux.{}.zip", i2p_version);
let link = format!("https://github.com/i2p-zero/i2p-zero/releases/download/{}/{}",
i2p_version, i2p_zero_zip);
let link = format!(
"https://github.com/i2p-zero/i2p-zero/releases/download/{}/{}",
i2p_version, i2p_zero_zip
);
let curl = std::process::Command::new("curl")
.args(["-LO#", &link])
.status();
@ -465,15 +534,18 @@ pub async fn install_software(installations: Installations) -> bool {
.spawn()
.expect("i2p unzip failed");
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);
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
if installations.xmr {
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")
.args(["-O#", &link])
.status();
@ -486,10 +558,13 @@ pub async fn install_software(installations: Installations) -> bool {
.spawn()
.expect("monero tar extraction failed");
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;
}
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 {
debug!("validating hash");
let expected_hash = if sw == ExternalSoftware::I2P {
String::from(crate::I2P_RELEASE_HASH)
} else if sw == ExternalSoftware::I2PZero {
String::from(crate::I2P_ZERO_RELEASH_HASH)
} else {
String::from(crate::MONERO_RELEASE_HASH)
};
String::from(crate::I2P_RELEASE_HASH)
} else if sw == ExternalSoftware::I2PZero {
String::from(crate::I2P_ZERO_RELEASH_HASH)
} else {
String::from(crate::MONERO_RELEASE_HASH)
};
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 split1 = str_sha.split(" ");
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 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
// 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);
});
ui.label("\n");
use egui_extras::{Column, TableBuilder};
use egui_extras::{
Column,
TableBuilder,
};
let table = TableBuilder::new(ui)
.striped(true)
.resizable(true)
@ -679,7 +688,10 @@ fn send_payment_req(
if check_txp.result.good && check_txp.result.confirmations > 0 {
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;
}
write_gui_db(

View file

@ -3,8 +3,13 @@
use eframe::egui;
use nevmes_core::*;
use std::sync::mpsc::{Receiver, Sender};
use std::time::Duration;
use std::{
sync::mpsc::{
Receiver,
Sender,
},
time::Duration,
};
pub struct HomeApp {
connections: utils::Connections,
@ -73,9 +78,11 @@ impl Default for HomeApp {
let s_i2p_status = false;
let s_can_refresh = false;
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 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 {
connections,
core_timeout_rx,
@ -140,7 +147,9 @@ impl eframe::App for HomeApp {
}
if let Ok(install) = self.installation_rx.try_recv() {
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;
}
if let Ok(timeout) = self.core_timeout_rx.try_recv() {
@ -251,7 +260,11 @@ impl eframe::App for HomeApp {
if !self.is_loading {
if ui.button("Install").clicked() {
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
(tx: Sender<bool>, ctx: egui::Context) {
fn start_core_timeout(tx: Sender<bool>, ctx: egui::Context) {
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");
let _ = tx.send(true);
ctx.request_repaint();
});
}
fn install_software_req
(tx: Sender<bool>, ctx: egui::Context, installations: &utils::Installations) {
fn install_software_req(
tx: Sender<bool>,
ctx: egui::Context,
installations: &utils::Installations,
) {
let req_install: utils::Installations = utils::Installations {
i2p: installations.i2p,
i2p_zero: installations.i2p_zero,

View file

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

View file

@ -1,5 +1,8 @@
use nevmes_core::*;
use std::sync::mpsc::{Sender, Receiver};
use std::sync::mpsc::{
Receiver,
Sender,
};
pub struct MailBoxApp {
decrypted_message: String,
@ -26,11 +29,12 @@ impl Default for MailBoxApp {
impl eframe::App for MailBoxApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// Hook into async channel threads
//-----------------------------------------------------------------------------------
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
@ -59,85 +63,99 @@ impl eframe::App for MailBoxApp {
self.messages = message::find_all();
}
ui.label("\n");
use egui_extras::{Column, TableBuilder};
use egui_extras::{
Column,
TableBuilder,
};
let table = TableBuilder::new(ui)
.striped(true)
.resizable(true)
.cell_layout(egui::Layout::left_to_right(egui::Align::Center))
.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::remainder())
.min_scrolled_height(0.0);
let table = TableBuilder::new(ui)
.striped(true)
.resizable(true)
.cell_layout(egui::Layout::left_to_right(egui::Align::Center))
.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::remainder())
.min_scrolled_height(0.0);
table
.header(20.0, |mut header| {
header.col(|ui| {
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())
}
});
});
});
table
.header(20.0, |mut header| {
header.col(|ui| {
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(),
)
}
});
});
});
}
});
});
}
}
fn refresh_on_delete_req
(tx: Sender<bool>, ctx: egui::Context) {
fn refresh_on_delete_req(tx: Sender<bool>, ctx: egui::Context) {
tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
log::error!("refreshing messages....");

View file

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

View file

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

View file

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

View file

@ -8,7 +8,7 @@ mod wrap_app;
/// key for fetching the login credential hash
pub const CREDENTIAL_KEY: &str = "NEVMES_GUI_KEY";
/// 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
pub const CRED_CHECK_INTERVAL: u64 = 5;
/// monero estimated block time in seconds

View file

@ -1,6 +1,9 @@
use nevmes_core::*;
use sha2::{Sha512, Digest};
use crate::CREDENTIAL_KEY;
use nevmes_core::*;
use sha2::{
Digest,
Sha512,
};
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct LoginApp {
@ -12,7 +15,10 @@ impl Default for LoginApp {
fn default() -> Self {
let credential = utils::empty_string();
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.
// The future doesn't have to do anything. In this example, it just sleeps forever.
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 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_rx: Receiver<bool>,
// end async notifications
}
impl Default for State {
@ -169,7 +169,9 @@ impl eframe::App for WrapApp {
}
}
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"))]
@ -195,7 +197,11 @@ impl eframe::App for WrapApp {
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 {
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.show_selected_app(ctx, frame);
@ -210,7 +216,6 @@ impl eframe::App for WrapApp {
fn on_exit(&mut self, _gl: Option<&glow::Context>) {
utils::kill_child_processes(false);
}
}
impl WrapApp {
@ -275,23 +280,26 @@ impl WrapApp {
tokio::spawn(async move {
log::debug!("locking screen");
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();
});
}
/*
TODO(c2m): SECURITY!:
Ok, so this here is by far the greatest security loophole.
An attacker could reset the credential in the db to any value,
besides setting the wallet password on initial load, better change
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
someone has access to the machine it sucks because nevmes gpg key
doesn't have a passphrase.
*/
TODO(c2m): SECURITY!:
Ok, so this here is by far the greatest security loophole.
An attacker could reset the credential in the db to any value,
besides setting the wallet password on initial load, better change
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
someone has access to the machine it sucks because nevmes gpg key
doesn't have a passphrase.
*/
/// Validate that a credential was set by the user;
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);
if r == utils::empty_string() {
log::debug!("credential not found");
let _= tx.send(false);
let _ = tx.send(false);
ctx.request_repaint();
} else {
let _= tx.send(true);
let _ = tx.send(true);
ctx.request_repaint();
break;
}
@ -327,7 +335,7 @@ fn send_inc_lock_timer_req(tx: Sender<bool>, ctx: egui::Context) {
log::debug!("starting the lock screen timer");
loop {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
let _= tx.send(true);
let _ = tx.send(true);
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::response::status::Custom;
use rocket::serde::json::Json;
use rocket::{get, post};
use rocket::{
get,
http::Status,
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
#[post("/", data="<m_req>")]
pub async fn send_message
(m_req: Json<Message>, token: proof::PaymentProof) -> Custom<Json<Message>> {
#[post("/", data = "<m_req>")]
pub async fn send_message(
m_req: Json<Message>,
token: proof::PaymentProof,
) -> Custom<Json<Message>> {
let res: Message = message::create(m_req, token.get_jwp()).await;
Custom(Status::Ok, Json(res))
}
/// Return all messages
#[get("/")]
pub async fn get_messages
(_token: auth::BearerToken) -> Custom<Json<Vec<Message>>> {
pub async fn get_messages(_token: auth::BearerToken) -> Custom<Json<Vec<Message>>> {
let messages = message::find_all();
Custom(Status::Ok, Json(messages))
}
/// decrypt a message body
#[get("/<mid>")]
pub async fn decrypt
(mid: String, _token: auth::BearerToken
pub async fn decrypt(
mid: String,
_token: auth::BearerToken,
) -> Custom<Json<reqres::DecryptedMessageBody>> {
let d_message = message::decrypt_body(mid);
Custom(Status::Ok, Json(d_message))

View file

@ -1,8 +1,8 @@
#[macro_use]
extern crate rocket;
use nevmes_message::*;
use nevmes_core::*;
use nevmes_message::*;
// 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::response::status::Custom;
use rocket::serde::json::Json;
use rocket::{get, post};
use rocket::{
get,
http::Status,
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

View file

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