0
0
Fork 0
mirror of https://git.verdigado.com/NB-Public/simple-wkd.git synced 2024-10-30 01:25:53 +01:00

Initial commit

This commit is contained in:
Delta1925 2023-04-13 18:56:32 +02:00
commit 9c9f4793be
No known key found for this signature in database
GPG key ID: 1C21ACE44193CB25
7 changed files with 2935 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
/data

2699
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

16
Cargo.toml Normal file
View file

@ -0,0 +1,16 @@
[package]
name = "simple-wkd"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "4.3.1"
chrono = "0.4.24"
rand = "0.8.5"
sequoia-net = "0.27.0"
sequoia-openpgp = "1.14.0"
serde = { version = "1.0.160", features = ["derive"] }
serde_json = "1.0.96"
tokio = { version = "1.27.0", features = ["time"] }

31
src/confirmation.rs Normal file
View file

@ -0,0 +1,31 @@
use crate::management::{delete_key, Action, Pending};
use crate::utils::{get_email_from_cert, parse_pem};
use crate::PENDING;
use crate::{pending_path, PATH, VARIANT};
use std::fs;
use std::path::Path;
pub fn confirm_action(token: &str) {
let pending_path = pending_path!().join(token);
let key: Pending = serde_json::from_str(&fs::read_to_string(&pending_path).unwrap()).unwrap();
match key.action() {
Action::Add => {
let cert = parse_pem(key.data());
let domain = get_email_from_cert(&cert)
.split('@')
.last()
.unwrap()
.to_owned();
sequoia_net::wkd::insert(PATH, domain, VARIANT, &cert).unwrap();
}
Action::Delete => delete_key(key.data()),
}
fs::remove_file(&pending_path).unwrap();
}
pub fn send_confirmation_email(email: &str, action: Action, token: &str) {
println!("Email sent to {}", email);
println!("Action: {:?}", action);
println!("Token: {}", token);
}

74
src/main.rs Normal file
View file

@ -0,0 +1,74 @@
mod confirmation;
mod management;
mod utils;
use self::confirmation::{confirm_action, send_confirmation_email};
use self::management::{clean_stale, store_pending_addition, store_pending_deletion, Action};
use self::utils::{gen_random_token, get_email_from_cert, parse_pem};
use actix_web::{get, post, web, App, HttpServer, Result};
use sequoia_net::wkd::Variant;
use serde::Deserialize;
use std::fs;
use std::path::Path;
use tokio::{task, time};
const PATH: &str = "data";
const PENDING: &str = "pending";
const MAX_AGE: i64 = 0;
const VARIANT: Variant = Variant::Direct;
#[derive(Deserialize, Debug)]
struct Pem {
key: String,
}
#[derive(Deserialize, Debug)]
struct Token {
data: String,
}
#[derive(Deserialize, Debug)]
struct Email {
address: String,
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
fs::create_dir_all(pending_path!())?;
task::spawn(async {
let mut metronome = time::interval(time::Duration::from_secs(60 * 60 * 3));
loop {
metronome.tick().await;
clean_stale(MAX_AGE);
}
});
HttpServer::new(|| App::new().service(submit).service(confirm).service(delete))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
#[post("/api/submit")]
async fn submit(pem: web::Form<Pem>) -> Result<String> {
let cert = parse_pem(&pem.key);
let email = get_email_from_cert(&cert);
let token = gen_random_token();
store_pending_addition(pem.key.to_owned(), &token);
send_confirmation_email(&email, Action::Add, &token);
Ok(String::from("OK!"))
}
#[get("/api/confirm/{data}")]
async fn confirm(token: web::Path<Token>) -> Result<String> {
confirm_action(&token.data);
Ok(String::from("OK!"))
}
#[get("/api/delete/{address}")]
async fn delete(email: web::Path<Email>) -> Result<String> {
let token = gen_random_token();
store_pending_deletion(email.address.to_owned(), &token);
send_confirmation_email(&email.address, Action::Delete, &token);
Ok(String::from("OK!"))
}

83
src/management.rs Normal file
View file

@ -0,0 +1,83 @@
use crate::utils::get_user_file_path;
use crate::PENDING;
use crate::{pending_path, PATH};
use chrono::Utc;
use serde::{Deserialize, Serialize};
use std::{fs, path::Path};
#[derive(Serialize, Deserialize, Debug)]
pub enum Action {
Add,
Delete,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Pending {
action: Action,
data: String,
timestamp: i64,
}
impl Pending {
pub fn build_add(pem: String) -> Pending {
let timestamp = Utc::now().timestamp();
Pending {
action: Action::Add,
data: pem,
timestamp,
}
}
pub fn build_delete(email: String) -> Pending {
let timestamp = Utc::now().timestamp();
Pending {
action: Action::Delete,
data: email,
timestamp,
}
}
pub fn action(&self) -> &Action {
&self.action
}
pub fn data(&self) -> &str {
&self.data
}
pub fn timestamp(&self) -> i64 {
self.timestamp
}
}
fn store_pending(pending: Pending, token: &str) {
let serialized = serde_json::to_string(&pending).unwrap();
fs::create_dir_all(pending_path!()).unwrap();
fs::write(pending_path!().join(token), serialized).unwrap();
}
pub fn store_pending_addition(pem: String, token: &str) {
let data = Pending::build_add(pem);
store_pending(data, token);
}
pub fn store_pending_deletion(email: String, token: &str) {
let data = Pending::build_delete(email);
store_pending(data, token);
}
pub fn clean_stale(max_age: i64) {
for path in fs::read_dir(pending_path!()).unwrap() {
let file_path = path.unwrap().path();
let key: Pending = serde_json::from_str(&fs::read_to_string(&file_path).unwrap()).unwrap();
let now = Utc::now().timestamp();
if now - key.timestamp() > max_age {
fs::remove_file(&file_path).unwrap();
println!(
"Deleted {}, since it was stale",
&file_path.to_str().unwrap()
);
}
}
}
pub fn delete_key(email: &str) {
let path = Path::new(PATH).join(get_user_file_path(email));
fs::remove_file(path).unwrap();
}

30
src/utils.rs Normal file
View file

@ -0,0 +1,30 @@
use crate::VARIANT;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use sequoia_net::wkd::Url;
use sequoia_openpgp::{parse::Parse, Cert};
use std::path::PathBuf;
#[macro_export]
macro_rules! pending_path {
() => {
Path::new(PATH).join(PENDING)
};
}
pub fn parse_pem(data: &str) -> Cert {
sequoia_openpgp::Cert::from_bytes(data.as_bytes()).unwrap()
}
pub fn gen_random_token() -> String {
let mut rng = thread_rng();
(0..10).map(|_| rng.sample(Alphanumeric) as char).collect()
}
pub fn get_email_from_cert(cert: &Cert) -> String {
cert.userids().next().unwrap().email().unwrap().unwrap()
}
pub fn get_user_file_path(email: &str) -> PathBuf {
Url::from(email).unwrap().to_file_path(VARIANT).unwrap()
}