mirror of
https://github.com/creating2morrow/neveko.git
synced 2024-12-22 03:29:22 +00:00
add rustfmt and fmtall.sh
This commit is contained in:
parent
438b22838e
commit
e9ef5778d1
38 changed files with 1158 additions and 585 deletions
6
.rustfmt.toml
Normal file
6
.rustfmt.toml
Normal 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
|
29
README.md
29
README.md
|
@ -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)
|
||||
|
|
6
nevmes-auth/.rustfmt.toml
Normal file
6
nevmes-auth/.rustfmt.toml
Normal 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
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
6
nevmes-contact/.rustfmt.toml
Normal file
6
nevmes-contact/.rustfmt.toml
Normal 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
|
|
@ -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()))
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,26 +96,35 @@ 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);
|
||||
}
|
||||
|
||||
/// Sign for trusted nevmes contacts
|
||||
///
|
||||
///
|
||||
/// UI/UX should have some prompt about the implication of trusting keys
|
||||
///
|
||||
///
|
||||
/// however that is beyond the scope of this app. nevmes assumes contacts
|
||||
///
|
||||
///
|
||||
/// 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()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
|
@ -25,13 +40,15 @@ impl Interface {
|
|||
let txn = e.new_transaction().unwrap();
|
||||
{
|
||||
// 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 db = txn.bind(&h);
|
||||
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 {
|
||||
|
@ -50,12 +67,12 @@ impl Interface {
|
|||
let txn = e.new_transaction().unwrap();
|
||||
{
|
||||
// get a database bound to this transaction
|
||||
let db = txn.bind(&h);
|
||||
db.del::<>(&k).unwrap_or_else(|_| error!("failed to delete"));
|
||||
let db = txn.bind(&h);
|
||||
db.del(&k).unwrap_or_else(|_| error!("failed to delete"));
|
||||
}
|
||||
match txn.commit() {
|
||||
Err(_) => error!("failed to commit!"),
|
||||
Ok(_) => ()
|
||||
Ok(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,14 +104,14 @@ pub async fn start() {
|
|||
.expect("i2p-zero failed to start");
|
||||
debug!("{:?}", output.stdout);
|
||||
find_tunnels().await;
|
||||
{
|
||||
tokio::spawn(async move {
|
||||
{
|
||||
tokio::spawn(async move {
|
||||
let tick: std::sync::mpsc::Receiver<()> = schedule_recv::periodic_ms(600000);
|
||||
loop {
|
||||
tick.recv().unwrap();
|
||||
check_connection().await;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,16 +262,27 @@ 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);
|
||||
}
|
||||
|
||||
/// Triggered on app startup, retries to send fts every minute
|
||||
///
|
||||
///
|
||||
/// FTS thread terminates when empty and gets restarted on the next
|
||||
///
|
||||
///
|
||||
/// failed-to-send message.
|
||||
pub async fn retry_fts() {
|
||||
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 {
|
||||
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()
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,12 +101,14 @@ pub enum LockTimeLimit {
|
|||
|
||||
impl LockTimeLimit {
|
||||
pub fn value(&self) -> u64 {
|
||||
match *self { LockTimeLimit::Blocks => 20, }
|
||||
match *self {
|
||||
LockTimeLimit::Blocks => 20,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Start monerod from the -`-monero-location` flag
|
||||
///
|
||||
///
|
||||
/// default: /home/$USER/monero-xxx-xxx
|
||||
pub fn start_daemon() {
|
||||
info!("starting monerod");
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -375,8 +423,11 @@ pub async fn get_balance() -> reqres::XmrRpcBalanceResponse {
|
|||
info!("fetching wallet balance");
|
||||
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,
|
||||
let params: reqres::XmrRpcBalanceParams = reqres::XmrRpcBalanceParams {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -433,8 +490,10 @@ pub async fn validate_address(address: &String) -> reqres::XmrRpcValidateAddress
|
|||
info!("validating wallet address");
|
||||
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,
|
||||
let params: reqres::XmrRpcValidateAddressParams = reqres::XmrRpcValidateAddressParams {
|
||||
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
|
||||
|
@ -631,11 +716,11 @@ pub async fn check_tx_proof(txp: &proof::TxProof) -> reqres::XmrRpcCheckTxProofR
|
|||
info!("check_tx_proof proof: {:?}", txp);
|
||||
let client = reqwest::Client::new();
|
||||
let host = get_rpc_host();
|
||||
let params: reqres::XmrRpcCheckTxProofParams = reqres::XmrRpcCheckTxProofParams {
|
||||
let params: reqres::XmrRpcCheckTxProofParams = reqres::XmrRpcCheckTxProofParams {
|
||||
address: String::from(&txp.subaddress),
|
||||
message: String::from(&txp.message),
|
||||
signature: String::from(&txp.signature),
|
||||
txid: String::from(&txp.hash),
|
||||
txid: String::from(&txp.hash),
|
||||
};
|
||||
let req = reqres::XmrRpcCheckTxProofRequest {
|
||||
jsonrpc: RpcFields::JsonRpcVersion.value(),
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -663,10 +752,10 @@ pub async fn get_tx_proof(ptxp: proof::TxProof) -> reqres::XmrRpcGetTxProofRespo
|
|||
info!("fetching proof: {:?}", &ptxp.hash);
|
||||
let client = reqwest::Client::new();
|
||||
let host = get_rpc_host();
|
||||
let params: reqres::XmrRpcGetTxProofParams = reqres::XmrRpcGetTxProofParams {
|
||||
let params: reqres::XmrRpcGetTxProofParams = reqres::XmrRpcGetTxProofParams {
|
||||
address: String::from(&ptxp.subaddress),
|
||||
message: String::from(&ptxp.message),
|
||||
txid: String::from(&ptxp.hash),
|
||||
txid: String::from(&ptxp.hash),
|
||||
};
|
||||
let req = reqres::XmrRpcGetTxProofRequest {
|
||||
jsonrpc: RpcFields::JsonRpcVersion.value(),
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -694,8 +787,8 @@ pub async fn get_transfer_by_txid(txid: &str) -> reqres::XmrRpcGetTxByIdResponse
|
|||
info!("fetching tx: {:?}", txid);
|
||||
let client = reqwest::Client::new();
|
||||
let host = get_rpc_host();
|
||||
let params: reqres::XmrRpcGetTxByIdParams = reqres::XmrRpcGetTxByIdParams {
|
||||
txid: String::from(txid)
|
||||
let params: reqres::XmrRpcGetTxByIdParams = reqres::XmrRpcGetTxByIdParams {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -34,7 +51,7 @@ impl Default for TxProof {
|
|||
}
|
||||
|
||||
/// Provide neccessary information for contacts to
|
||||
///
|
||||
///
|
||||
/// provide proof of payment.
|
||||
pub async fn create_invoice() -> reqres::Invoice {
|
||||
info!("creating invoice");
|
||||
|
@ -43,20 +60,24 @@ 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
|
||||
///
|
||||
///
|
||||
/// except that the claims must contain the information
|
||||
///
|
||||
///
|
||||
/// necessary to verify the payment. Confirmations cannot
|
||||
///
|
||||
///
|
||||
/// be zero or above some specified threshold. Setting higher
|
||||
///
|
||||
///
|
||||
/// payment values and lower confirmations works as a spam
|
||||
///
|
||||
/// disincentivizing mechanism.
|
||||
///
|
||||
/// disincentivizing mechanism.
|
||||
pub async fn create_jwp(proof: &TxProof) -> String {
|
||||
info!("creating jwp");
|
||||
// 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 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()),
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
///
|
||||
///
|
||||
/// `subaddress`: a subaddress belonging to this nevmes instance
|
||||
///
|
||||
///
|
||||
/// `created`: UTC timestamp the proof was created.
|
||||
/// <i>Future use</i> Potential offline payments.
|
||||
///
|
||||
///
|
||||
/// `expire`: blocks approved for
|
||||
/// <i>Future use</i>. Potential offline payments.
|
||||
///
|
||||
///
|
||||
/// `hash`: hash of the payment
|
||||
///
|
||||
///
|
||||
/// `message`: (optional) default: empty string
|
||||
///
|
||||
///
|
||||
/// `signature`: validates proof of payment
|
||||
#[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,30 +257,34 @@ 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()
|
||||
}
|
||||
|
||||
/// Validate that the subaddress in the proof was
|
||||
///
|
||||
///
|
||||
/// created by us. TODO(?): Use xmr rpc call `get_address_index`
|
||||
///
|
||||
///
|
||||
/// for faster lookups (check minor > 0)
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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")]
|
||||
|
@ -864,7 +863,7 @@ impl Default for Invoice {
|
|||
}
|
||||
|
||||
/// Not to be confused with the PaymentProof guard.
|
||||
///
|
||||
///
|
||||
/// This is the response when proving payment
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -172,10 +201,10 @@ pub fn get_payment_threshold() -> u128 {
|
|||
}
|
||||
|
||||
/// 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 {
|
||||
cid: String::from(&c.cid),
|
||||
i2p_address: String::from(&c.i2p_address),
|
||||
cid: String::from(&c.cid),
|
||||
i2p_address: String::from(&c.i2p_address),
|
||||
xmr_address: String::from(&c.xmr_address),
|
||||
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
|
||||
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 {
|
||||
body: m.body.iter().cloned().collect(),
|
||||
mid: String::from(&m.mid),
|
||||
uid: utils::empty_string(),
|
||||
created: m.created,
|
||||
from: String::from(&m.from),
|
||||
to: String::from(&m.to),
|
||||
to: String::from(&m.to),
|
||||
};
|
||||
Json(r_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);
|
||||
}
|
||||
|
||||
|
@ -238,13 +279,12 @@ async fn gen_app_wallet() {
|
|||
let mut m_wallet = monero::open_wallet(String::from(filename)).await;
|
||||
if !m_wallet {
|
||||
m_wallet = monero::create_wallet(String::from(filename)).await;
|
||||
if !m_wallet {
|
||||
if !m_wallet {
|
||||
error!("failed to create 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)
|
||||
}
|
||||
}
|
||||
|
@ -269,7 +309,7 @@ fn gen_signing_keys() {
|
|||
}
|
||||
|
||||
/// TODO(c2m): add a button to gui to call this
|
||||
///
|
||||
///
|
||||
/// dont' forget to generate new keys as well
|
||||
pub fn revoke_signing_keys() {
|
||||
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);
|
||||
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,12 +412,16 @@ 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);
|
||||
}
|
||||
|
||||
/// Called by gui for cleaning up monerod, rpc, etc.
|
||||
///
|
||||
///
|
||||
/// pass true from gui connection manager so not to kill nevmes
|
||||
pub fn kill_child_processes(cm: bool) {
|
||||
info!("stopping child processes");
|
||||
|
@ -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.
|
||||
|
@ -414,11 +478,11 @@ pub fn stage_cleanup(f: String) {
|
|||
}
|
||||
|
||||
/// Handle the request from user to additional software
|
||||
///
|
||||
///
|
||||
/// from gui startup. Power users will most like install
|
||||
///
|
||||
///
|
||||
/// software on their own. Note that software pull is over
|
||||
///
|
||||
///
|
||||
/// clearnet. TODO(c2m): trusted download locations over i2p.
|
||||
pub async fn install_software(installations: Installations) -> bool {
|
||||
let mut valid_i2p_hash = true;
|
||||
|
@ -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,12 +558,15 @@ 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
6
nevmes-gui/.rustfmt.toml
Normal 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
|
|
@ -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(
|
||||
|
|
|
@ -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() {
|
||||
|
@ -226,7 +235,7 @@ impl eframe::App for HomeApp {
|
|||
self.is_loading = false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Installation Manager window
|
||||
//-----------------------------------------------------------------------------------
|
||||
let mut is_installing = self.is_installing;
|
||||
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
///
|
||||
|
|
|
@ -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....");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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))),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
@ -160,7 +160,7 @@ impl eframe::App for WrapApp {
|
|||
}
|
||||
if let Ok(lock) = self.state.is_screen_locked_rx.try_recv() {
|
||||
self.state.is_screen_locked = lock;
|
||||
if lock {
|
||||
if lock {
|
||||
let lock_screen = &mut self.state.lock_screen;
|
||||
if self.state.lock_timer >= crate::LOCK_SCREEN_TIMEOUT_SECS {
|
||||
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 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 {
|
||||
|
@ -274,24 +279,27 @@ impl WrapApp {
|
|||
fn send_lock_refresh(&mut self, tx: Sender<bool>, ctx: egui::Context, init: bool) {
|
||||
tokio::spawn(async move {
|
||||
log::debug!("locking screen");
|
||||
if !init {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(crate::LOCK_SCREEN_TIMEOUT_SECS)).await;
|
||||
if !init {
|
||||
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;
|
||||
}
|
||||
|
@ -316,19 +324,19 @@ impl WrapApp {
|
|||
}
|
||||
|
||||
/// When the pointer goes 'active' (i.e. pushing a button, dragging
|
||||
///
|
||||
///
|
||||
/// a slider, etc) reset it. Otherwise this function runs forever
|
||||
///
|
||||
///
|
||||
/// incrementing by one every second. Once this timer matches the
|
||||
///
|
||||
///
|
||||
/// `LOCK_SCREEN_TIMEOUT_SECS` constant the lock screen will trigger.
|
||||
fn send_inc_lock_timer_req(tx: Sender<bool>, ctx: egui::Context) {
|
||||
tokio::spawn(async move {
|
||||
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
6
nevmes-message/.rustfmt.toml
Normal file
6
nevmes-message/.rustfmt.toml
Normal 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
|
|
@ -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))
|
||||
|
|
|
@ -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
8
scripts/fmtall.sh
Executable 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
|
|
@ -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
|
||||
|
||||
|
|
32
src/main.rs
32
src/main.rs
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue