commit
0bdaa94dd5
@ -0,0 +1,3 @@
|
||||
/target
|
||||
*.profile
|
||||
*.sqlite
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,2 @@
|
||||
[workspace]
|
||||
members=["./niwl", "niwl-client","niwl-server"]
|
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "niwl-client"
|
||||
version = "0.1.0"
|
||||
authors = ["Sarah Jamie Lewis <sarah@openprivacy.ca>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
niwl = {path="../niwl"}
|
||||
fuzzytags = {path="../../fuzzymetatag"}
|
||||
clap = "3.0.0-beta.2"
|
||||
serde = {version="1.0.123", features=["derive"]}
|
||||
serde_json = "1.0.61"
|
||||
bincode = "1.3.1"
|
||||
hex = "0.4.2"
|
||||
base32 = "0.4.0"
|
||||
reqwest = {version="0.11.0", features=["json"]}
|
||||
tokio = "1.2.0"
|
@ -0,0 +1,114 @@
|
||||
use clap::Clap;
|
||||
use std::fs;
|
||||
use niwl::{Profile};
|
||||
|
||||
#[derive(Clap)]
|
||||
#[clap(version = "1.0", author = "Sarah Jamie Lewis <sarah@openprivacy.ca>")]
|
||||
struct Opts {
|
||||
|
||||
#[clap(default_value = "niwl.profile")]
|
||||
profile_filename: String,
|
||||
|
||||
#[clap(default_value = "http://localhost:8000")]
|
||||
niwl_server: String,
|
||||
|
||||
#[clap(subcommand)]
|
||||
subcmd: SubCommand,
|
||||
}
|
||||
|
||||
#[derive(Clap)]
|
||||
enum SubCommand {
|
||||
Generate(Generate),
|
||||
ImportTaggingKey(ImportTaggingKey),
|
||||
TagAndSend(TagAndSend),
|
||||
Detect(Detect)
|
||||
}
|
||||
|
||||
/// Generate a new niwl.profile file
|
||||
#[derive(Clap)]
|
||||
struct Generate {
|
||||
name: String
|
||||
}
|
||||
|
||||
/// Import a friends tagging key into this profile so you can send messages to them
|
||||
#[derive(Clap)]
|
||||
struct ImportTaggingKey {
|
||||
key: String
|
||||
}
|
||||
|
||||
/// Connect to a server and check for new notifications
|
||||
#[derive(Clap)]
|
||||
struct Detect {
|
||||
#[clap(default_value = "2")]
|
||||
length: u8
|
||||
}
|
||||
|
||||
/// Send a message to a friend tagged with their niwl key
|
||||
#[derive(Clap)]
|
||||
struct TagAndSend {
|
||||
/// the id of the friend e.g. "alice"
|
||||
id: String,
|
||||
/// the message you want to send.
|
||||
message: String,
|
||||
}
|
||||
|
||||
fn get_profile(profile_filename: &String) -> Profile {
|
||||
match fs::read_to_string(profile_filename) {
|
||||
Ok(json) => serde_json::from_str(json.as_str()).unwrap(),
|
||||
Err(why) => {
|
||||
panic!("couldn't read orb.profile : {}", why);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn main() {
|
||||
let opts: Opts = Opts::parse();
|
||||
match opts.subcmd {
|
||||
SubCommand::Generate(g) => {
|
||||
let profile = Profile::new(g.name.clone());
|
||||
let hotk = profile.human_readable_tagging_key();
|
||||
println!("Tagging Key: {}", base32::encode(base32::Alphabet::RFC4648{padding:false} ,bincode::serialize(&hotk).unwrap().as_slice()).to_ascii_lowercase());
|
||||
profile.save(&opts.profile_filename);
|
||||
}
|
||||
SubCommand::ImportTaggingKey(cmd) => {
|
||||
let mut profile = get_profile(&opts.profile_filename);
|
||||
profile.import_tagging_key(&cmd.key);
|
||||
profile.save(&opts.profile_filename);
|
||||
},
|
||||
SubCommand::TagAndSend(cmd) => {
|
||||
let mut profile = get_profile(&opts.profile_filename);
|
||||
let server = opts.niwl_server.clone();
|
||||
let contact = cmd.id.clone();
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async {
|
||||
let result = profile.tag_and_send(server, contact).await;
|
||||
println!("{}", result.unwrap().text().await.unwrap());
|
||||
});
|
||||
},
|
||||
SubCommand::Detect(cmd) => {
|
||||
let mut profile = get_profile(&opts.profile_filename);
|
||||
let server = opts.niwl_server.clone();
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async {
|
||||
match profile.detect_tags(server).await {
|
||||
Ok(detected_tags) => {
|
||||
for tag in detected_tags.detected_tags {
|
||||
println!("{}", tag);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
println!("Error: {}", err)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "niwl-server"
|
||||
version = "0.1.0"
|
||||
authors = ["Sarah Jamie Lewis <sarah@openprivacy.ca>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
fuzzytags = {path="../../fuzzymetatag"}
|
||||
rocket = "0.4.6"
|
||||
rocket_contrib = {version="0.4.6", features=["sqlite_pool"]}
|
||||
chrono = "0.4.19"
|
@ -0,0 +1,13 @@
|
||||
|
||||
## Building
|
||||
|
||||
Requires `libsqlite3`
|
||||
|
||||
sudo apt install libsqlite3-dev
|
||||
|
||||
## Running
|
||||
|
||||
First setup the database:
|
||||
|
||||
cat sql/create.sql | sqlite3 tags.sqlite
|
||||
|
@ -0,0 +1,2 @@
|
||||
[global.databases]
|
||||
tags = { url = "./tags.sqlite", pool_size = 20 }
|
@ -0,0 +1,4 @@
|
||||
create table if not exists tags (
|
||||
id text primary key,
|
||||
tag blob not null unique
|
||||
)
|
@ -0,0 +1,55 @@
|
||||
#![feature(proc_macro_hygiene, decl_macro)]
|
||||
|
||||
#[macro_use] extern crate rocket;
|
||||
#[macro_use] extern crate rocket_contrib;
|
||||
|
||||
use rocket_contrib::json;
|
||||
use fuzzytags::{DetectionKey, Tag};
|
||||
use rocket_contrib::json::{Json, JsonValue};
|
||||
use rocket_contrib::databases::rusqlite;
|
||||
use rocket_contrib::databases::rusqlite::types::ToSql;
|
||||
use chrono::{Utc, Duration};
|
||||
use std::ops::Sub;
|
||||
|
||||
#[database("tags")]
|
||||
struct TagsDbConn(rusqlite::Connection);
|
||||
|
||||
#[post("/new", format = "application/json", data = "<tag>")]
|
||||
fn new(conn:TagsDbConn, tag: Json<Tag<24>>) -> JsonValue {
|
||||
conn.0.execute(
|
||||
"INSERT INTO tags (id, tag) VALUES (strftime('%Y-%m-%d %H:%M:%S:%f', 'now'), ?1)",
|
||||
&[&tag.0.compress() as &dyn ToSql],
|
||||
).unwrap();
|
||||
json!({"tag" : tag.to_string()})
|
||||
}
|
||||
|
||||
#[post("/tags", format = "application/json", data = "<detection_key>")]
|
||||
fn tags(conn:TagsDbConn, detection_key: Json<DetectionKey<24>>) -> JsonValue {
|
||||
|
||||
let mut stmt = conn.0.prepare(
|
||||
"SELECT tag FROM tags WHERE id > (?1) AND id < (?2)",
|
||||
).unwrap();
|
||||
|
||||
let now = Utc::now();
|
||||
let after = now.sub(Duration::days(1)).format("%Y-%m-%d %H:%M:%S:%f").to_string();
|
||||
let before = now.format("%Y-%m-%d %H:%M:%S:%f").to_string();
|
||||
let selected_tags = stmt.query_map(&[&after, &before], |row| {
|
||||
let tag_bytes : Vec<u8> = row.get(0);
|
||||
let tag = Tag::<24>::decompress(tag_bytes.as_slice()).unwrap();
|
||||
tag
|
||||
}).unwrap();
|
||||
|
||||
let mut detected_tags : Vec<Tag<24>> = vec![];
|
||||
for tag in selected_tags {
|
||||
let tag : Tag<24> = tag.unwrap();
|
||||
if detection_key.0.test_tag(&tag) {
|
||||
detected_tags.push(tag);
|
||||
}
|
||||
}
|
||||
|
||||
json!({"detected_tags" : detected_tags})
|
||||
}
|
||||
|
||||
fn main() {
|
||||
rocket::ignite().attach(TagsDbConn::fairing()).mount("/", routes![tags, new]).launch();
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "niwl"
|
||||
version = "0.1.0"
|
||||
authors = ["Sarah Jamie Lewis <sarah@openprivacy.ca>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
fuzzytags = {path="../../fuzzymetatag"}
|
||||
serde = {version="1.0.123", features=["derive"]}
|
||||
serde_json = "1.0.61"
|
||||
bincode = "1.3.1"
|
||||
hex = "0.4.2"
|
||||
base32 = "0.4.0"
|
||||
reqwest = {version="0.11.0", features=["json"]}
|
@ -0,0 +1,121 @@
|
||||
#![feature(into_future)]
|
||||
use fuzzytags::{RootSecret, TaggingKey, Tag};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use reqwest::{Response, Error};
|
||||
use std::future::{Future, IntoFuture};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NiwlError {
|
||||
NoKnownContactError(String),
|
||||
RemoteServerError(String)
|
||||
}
|
||||
|
||||
#[derive(Serialize,Deserialize)]
|
||||
pub struct Profile {
|
||||
profile_name: String,
|
||||
root_secret: RootSecret<24>,
|
||||
tagging_keys: HashMap<String, TaggingKey<24>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize,Deserialize)]
|
||||
pub struct HumanOrientedTaggingKey {
|
||||
profile_name: String,
|
||||
tagging_key: TaggingKey<24>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct DetectedTags {
|
||||
pub detected_tags: Vec<Tag<24>>,
|
||||
}
|
||||
|
||||
impl Profile {
|
||||
pub fn new(profile_name: String) -> Profile {
|
||||
let root_secret = RootSecret::<24>::generate();
|
||||
Profile {
|
||||
profile_name,
|
||||
root_secret,
|
||||
tagging_keys: Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn human_readable_tagging_key(&self) -> HumanOrientedTaggingKey {
|
||||
let tagging_key = self.root_secret.tagging_key();
|
||||
HumanOrientedTaggingKey {
|
||||
profile_name: self.profile_name.clone(),
|
||||
tagging_key
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&self, profile_filename: &String) -> std::io::Result<()> {
|
||||
let j = serde_json::to_string(&self);
|
||||
let mut file = match File::create(profile_filename) {
|
||||
Err(why) => panic!("couldn't create : {}", why),
|
||||
Ok(file) => file,
|
||||
};
|
||||
file.write_all(j.unwrap().as_bytes())
|
||||
}
|
||||
|
||||
pub fn generate_tag(&self, id: &String) -> Result<Tag<24>, NiwlError> {
|
||||
if self.tagging_keys.contains_key(id) {
|
||||
let tag = self.tagging_keys[id].generate_tag();
|
||||
println!("Tag for {} {}", id, tag.to_string());
|
||||
return Ok(tag)
|
||||
}
|
||||
Err(NiwlError::NoKnownContactError(format!("No known friend {}. Perhaps you need to import-tagging-key first?", id)))
|
||||
}
|
||||
|
||||
pub fn import_tagging_key(&mut self, key: &String) {
|
||||
match base32::decode(base32::Alphabet::RFC4648 { padding: false }, key.as_str()) {
|
||||
Some(data) => {
|
||||
let tagging_key_result: Result<HumanOrientedTaggingKey, bincode::Error> = bincode::deserialize(&data);
|
||||
match tagging_key_result {
|
||||
Ok(hotk) => {
|
||||
println!("Got: {}: {}", hotk.profile_name, hotk.tagging_key.id());
|
||||
if self.tagging_keys.contains_key(&hotk.profile_name) == false {
|
||||
self.tagging_keys.insert(hotk.profile_name, hotk.tagging_key);
|
||||
} else {
|
||||
println!("There is already an entry for {}", hotk.profile_name)
|
||||
}
|
||||
return
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error: {}", err.to_string());
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
println!("Error Reporting Tagging Key")
|
||||
}
|
||||
|
||||
pub async fn tag_and_send(&self, server: String, contact: String) -> Result<Response, NiwlError> {
|
||||
let client = reqwest::Client::new();
|
||||
match self.generate_tag(&contact) {
|
||||
Ok(tag) => {
|
||||
let result = client.
|
||||
post(&String::from(server + "/new"))
|
||||
.json(&tag)
|
||||
.send().await;
|
||||
match result {
|
||||
Ok(response) => Ok(response),
|
||||
Err(err) => Err(NiwlError::RemoteServerError(err.to_string()))
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn detect_tags(&mut self, server: String) -> Result<DetectedTags, Error> {
|
||||
let client = reqwest::Client::new();
|
||||
let detection_key = self.root_secret.extract_detection_key(1);
|
||||
let result = client.post(&String::from(server + "/tags"))
|
||||
.json(&detection_key)
|
||||
.send().await;
|
||||
result.unwrap().json().await
|
||||
}
|
||||
}
|
Loading…
Reference in new issue