2023-02-27 14:20:01 +00:00
|
|
|
use serde::{Serialize, Deserialize};
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::time::SystemTime;
|
|
|
|
use chrono::{DateTime, Utc};
|
|
|
|
use egui::{RichText, Ui};
|
|
|
|
use pwbox::sodium::Sodium;
|
|
|
|
use pwbox::{ErasedPwBox, Eraser, Suite};
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
pub struct NoteMetadata {
|
|
|
|
pub date: SystemTime,
|
|
|
|
pub arbitrary: Option<HashMap<String, String>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
pub enum Note {
|
|
|
|
Encrypted {
|
2023-02-27 15:43:33 +00:00
|
|
|
id: String,
|
2023-02-27 14:20:01 +00:00
|
|
|
title: String,
|
|
|
|
metadata: NoteMetadata,
|
|
|
|
encrypted_text: String,
|
|
|
|
},
|
|
|
|
Decrypted {
|
2023-02-27 15:43:33 +00:00
|
|
|
id: String,
|
2023-02-27 14:20:01 +00:00
|
|
|
title: String,
|
|
|
|
metadata: NoteMetadata,
|
|
|
|
text: String,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Note {
|
|
|
|
pub fn encrypt(&self, password: &str) -> anyhow::Result<Self> {
|
|
|
|
let mut rng = rand::thread_rng();
|
|
|
|
|
|
|
|
match self {
|
2023-02-27 15:43:33 +00:00
|
|
|
Note::Decrypted { id, text, metadata, title } => {
|
2023-02-27 14:20:01 +00:00
|
|
|
let pwbox = Sodium::build_box(&mut rng)
|
|
|
|
.seal(password.to_string().into_bytes(), text.clone().into_bytes())?;
|
|
|
|
|
|
|
|
let mut eraser = Eraser::new();
|
|
|
|
eraser.add_suite::<Sodium>();
|
|
|
|
let erased: ErasedPwBox = eraser.erase(&pwbox)?;
|
|
|
|
|
|
|
|
Ok(Self::Encrypted {
|
2023-02-27 15:43:33 +00:00
|
|
|
id: id.clone(),
|
2023-02-27 14:20:01 +00:00
|
|
|
title: title.clone(),
|
|
|
|
metadata: metadata.clone(),
|
|
|
|
encrypted_text: serde_json::to_string(&erased)?,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
_ => Ok(self.clone()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn decrypt(&self, password: &str) -> anyhow::Result<Self> {
|
|
|
|
match self {
|
2023-02-27 15:43:33 +00:00
|
|
|
Note::Encrypted { id, title, metadata, encrypted_text } => {
|
2023-02-27 14:20:01 +00:00
|
|
|
let mut eraser = Eraser::new();
|
|
|
|
eraser.add_suite::<Sodium>();
|
|
|
|
let erased: ErasedPwBox = serde_json::from_str(encrypted_text)?;
|
|
|
|
|
|
|
|
let decrypted_bytes = eraser.restore(&erased)?.open(password)?.to_vec();
|
|
|
|
let decrypted_text = String::from_utf8(decrypted_bytes)?;
|
|
|
|
|
|
|
|
Ok(Self::Decrypted {
|
2023-02-27 15:43:33 +00:00
|
|
|
id: id.clone(),
|
2023-02-27 14:20:01 +00:00
|
|
|
title: title.clone(),
|
|
|
|
metadata: metadata.clone(),
|
|
|
|
text: decrypted_text,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
_ => Ok(self.clone()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-27 15:43:33 +00:00
|
|
|
pub fn render(&self, ui: &mut Ui, password: &str, cb: impl FnOnce(String)) -> Option<Self> {
|
2023-02-27 14:20:01 +00:00
|
|
|
let mut value = None;
|
|
|
|
ui.group(|ui| {
|
|
|
|
match self {
|
2023-02-27 15:43:33 +00:00
|
|
|
Note::Encrypted { id, title, metadata, .. } => {
|
|
|
|
render_title_and_metadata(ui, title, metadata, false, (id.clone(), Box::new(cb)));
|
2023-02-27 14:20:01 +00:00
|
|
|
if ui.button("decrypt note").clicked() {
|
|
|
|
value = Some(self.decrypt(password).unwrap());
|
|
|
|
}
|
|
|
|
}
|
2023-02-27 15:43:33 +00:00
|
|
|
Note::Decrypted { id, title, metadata, text, .. } => {
|
|
|
|
render_title_and_metadata(ui, title, metadata, true, (id.clone(), Box::new(cb)));
|
2023-02-27 14:20:01 +00:00
|
|
|
ui.label(text);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
value
|
|
|
|
}
|
2023-02-27 15:43:33 +00:00
|
|
|
|
|
|
|
pub fn id(&self) -> String {
|
|
|
|
match self {
|
|
|
|
Note::Encrypted { id, .. } => id.clone(),
|
|
|
|
Note::Decrypted { id, .. } => id.clone(),
|
|
|
|
}
|
|
|
|
}
|
2023-02-27 14:20:01 +00:00
|
|
|
}
|
|
|
|
|
2023-02-27 15:43:33 +00:00
|
|
|
fn render_title_and_metadata(ui: &mut Ui, title: impl Into<String>, metadata: &NoteMetadata, show_delete_button: bool, delete_info: (String, Box<impl FnOnce(String)>)) {
|
2023-02-27 14:20:01 +00:00
|
|
|
ui.horizontal(|ui| {
|
|
|
|
ui.label(RichText::new(title).size(14.0));
|
|
|
|
|
|
|
|
let date: DateTime<Utc> = metadata.date.into();
|
|
|
|
ui.separator();
|
2023-02-27 15:43:33 +00:00
|
|
|
ui.label(date.format("%d-%m-%y %H:%M").to_string());
|
2023-02-27 14:20:01 +00:00
|
|
|
|
|
|
|
if let Some(arbitrary) = &metadata.arbitrary {
|
|
|
|
for (k, v) in arbitrary.iter() {
|
|
|
|
ui.separator();
|
|
|
|
ui.label(RichText::new(format!("{}: {}", k, v)).monospace());
|
|
|
|
}
|
|
|
|
}
|
2023-02-27 15:43:33 +00:00
|
|
|
|
|
|
|
ui.separator();
|
|
|
|
if show_delete_button {
|
|
|
|
let (id, cb) = delete_info;
|
|
|
|
if ui.button("delete note").clicked() {
|
|
|
|
cb(id);
|
|
|
|
}
|
|
|
|
}
|
2023-02-27 14:20:01 +00:00
|
|
|
});
|
2023-02-27 15:43:33 +00:00
|
|
|
|
2023-02-27 14:20:01 +00:00
|
|
|
ui.separator();
|
|
|
|
}
|