mirror of
https://github.com/spartanz51/tutabridge.git
synced 2026-06-24 10:54:32 +02:00
Restructure into Cargo workspace with Tauri desktop GUI
Split the bridge into a tutabridge-core crate, a Tauri v2 desktop app (src-tauri) and a React/TS UI (ui), keeping the CLI entrypoint at the workspace root. Add encrypted local storage (SQLCipher metadata index + encrypted .eml files) so mail persists across launches and only the delta is fetched. Wire the bridge to the Tuta Rust SDK via the tuta-repo submodule (batch loading, MailDetailsBlob reading, interactive 2FA login). Implement SMTP sending: build the draft and send it through Tuta's DraftService/SendDraftService, mirroring the web client (body in compressedBodyText, non-empty sender/recipient names, populated SendDraftParameters). Add unit tests for the draft/send payload building.
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
|
||||
use tokio_rustls::rustls::ServerConfig;
|
||||
use tokio_rustls::TlsAcceptor;
|
||||
|
||||
fn cert_dir() -> PathBuf {
|
||||
dirs::config_dir()
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
.join("tutabridge")
|
||||
}
|
||||
|
||||
fn cert_path() -> PathBuf {
|
||||
cert_dir().join("cert.pem")
|
||||
}
|
||||
|
||||
fn key_path() -> PathBuf {
|
||||
cert_dir().join("key.pem")
|
||||
}
|
||||
|
||||
pub fn load_or_create_tls_acceptor() -> Result<TlsAcceptor, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let cert_file = cert_path();
|
||||
let key_file = key_path();
|
||||
|
||||
let (cert_pem, key_pem) = if cert_file.exists() && key_file.exists() {
|
||||
log::info!("Loading TLS certificate from {}", cert_file.display());
|
||||
(std::fs::read_to_string(&cert_file)?, std::fs::read_to_string(&key_file)?)
|
||||
} else {
|
||||
log::info!("Generating self-signed TLS certificate...");
|
||||
let (cert, key) = generate_self_signed()?;
|
||||
std::fs::create_dir_all(cert_dir())?;
|
||||
std::fs::write(&cert_file, &cert)?;
|
||||
std::fs::write(&key_file, &key)?;
|
||||
log::info!("Certificate saved to {}", cert_file.display());
|
||||
(cert, key)
|
||||
};
|
||||
|
||||
let certs = load_certs(&cert_pem)?;
|
||||
let key = load_key(&key_pem)?;
|
||||
|
||||
let config = ServerConfig::builder()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(certs, key)?;
|
||||
|
||||
Ok(TlsAcceptor::from(Arc::new(config)))
|
||||
}
|
||||
|
||||
fn generate_self_signed() -> Result<(String, String), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut params = rcgen::CertificateParams::new(vec!["localhost".to_string()])?;
|
||||
params.subject_alt_names = vec![
|
||||
rcgen::SanType::DnsName("localhost".try_into()?),
|
||||
rcgen::SanType::IpAddress(std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST)),
|
||||
];
|
||||
let key_pair = rcgen::KeyPair::generate()?;
|
||||
let cert = params.self_signed(&key_pair)?;
|
||||
|
||||
Ok((cert.pem(), key_pair.serialize_pem()))
|
||||
}
|
||||
|
||||
fn load_certs(
|
||||
pem: &str,
|
||||
) -> Result<Vec<CertificateDer<'static>>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut reader = std::io::BufReader::new(pem.as_bytes());
|
||||
let certs: Vec<CertificateDer<'static>> = rustls_pemfile::certs(&mut reader)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
if certs.is_empty() {
|
||||
return Err("No certificates found in PEM".into());
|
||||
}
|
||||
Ok(certs)
|
||||
}
|
||||
|
||||
fn load_key(
|
||||
pem: &str,
|
||||
) -> Result<PrivateKeyDer<'static>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut reader = std::io::BufReader::new(pem.as_bytes());
|
||||
let keys: Vec<PrivatePkcs8KeyDer<'static>> = rustls_pemfile::pkcs8_private_keys(&mut reader)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let key = keys
|
||||
.into_iter()
|
||||
.next()
|
||||
.ok_or("No private key found in PEM")?;
|
||||
Ok(PrivateKeyDer::Pkcs8(key))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_tls_acceptor_builds() {
|
||||
let _ = tokio_rustls::rustls::crypto::ring::default_provider().install_default();
|
||||
let (cert_pem, key_pem) = generate_self_signed().unwrap();
|
||||
let certs = load_certs(&cert_pem).unwrap();
|
||||
let key = load_key(&key_pem).unwrap();
|
||||
let config = ServerConfig::builder()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(certs, key);
|
||||
assert!(config.is_ok());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user