use super::pair;
use super::{SeaError, UserAuth, KeyPair};
use crate::chain::Chain;
use aes_gcm::{Aes256Gcm, KeyInit, aead::Aead};
use base64::{engine::general_purpose, Engine as _};
use pbkdf2::pbkdf2_hmac;
use serde_json::json;
use sha2::Sha256;
use std::sync::Arc;
pub async fn create_user(
chain: Arc<Chain>,
alias: Option<String>,
password: &str,
) -> Result<UserAuth, SeaError> {
let pair = pair::generate_pair().await?;
let salt = generate_salt();
let password_hash = hash_password(password, &salt);
let password_key = super::work::work(
password.as_bytes(),
Some(salt.clone()),
super::work::WorkOptions {
name: Some("PBKDF2".to_string()),
iterations: Some(100_000),
salt: Some(salt.clone()),
hash: Some("SHA-256".to_string()),
length: Some(256), encode: Some("base64".to_string()),
},
).await?;
let priv_data = json!({"priv": pair.priv_key});
let priv_data_str = serde_json::to_string(&priv_data)
.map_err(|e| SeaError::Crypto(format!("Serialization error: {}", e)))?;
use aes_gcm::{Aes256Gcm, KeyInit, aead::Aead};
use rand::RngCore;
let password_key_bytes = general_purpose::STANDARD_NO_PAD.decode(&password_key)
.map_err(|_| SeaError::Crypto("Failed to decode password key".to_string()))?;
let cipher = Aes256Gcm::new_from_slice(&password_key_bytes)
.map_err(|e| SeaError::Crypto(format!("Failed to create cipher: {}", e)))?;
let mut nonce_bytes = [0u8; 12];
rand::thread_rng().fill_bytes(&mut nonce_bytes);
let nonce = aes_gcm::Nonce::from_slice(&nonce_bytes);
let ciphertext = cipher.encrypt(nonce, priv_data_str.as_bytes())
.map_err(|e| SeaError::Crypto(format!("Encryption failed: {}", e)))?;
let priv_key_encrypted = json!({
"ct": general_purpose::STANDARD_NO_PAD.encode(ciphertext),
"iv": general_purpose::STANDARD_NO_PAD.encode(nonce_bytes),
"s": general_purpose::STANDARD_NO_PAD.encode(&salt),
});
let epriv_key_encrypted = if let Some(ref epriv) = pair.epriv_key {
let epriv_data = json!({"epriv": epriv});
let epriv_data_str = serde_json::to_string(&epriv_data)
.map_err(|e| SeaError::Crypto(format!("Serialization error: {}", e)))?;
let mut epriv_nonce_bytes = [0u8; 12];
rand::thread_rng().fill_bytes(&mut epriv_nonce_bytes);
let epriv_nonce = aes_gcm::Nonce::from_slice(&epriv_nonce_bytes);
let epriv_ciphertext = cipher.encrypt(epriv_nonce, epriv_data_str.as_bytes())
.map_err(|e| SeaError::Crypto(format!("Encryption failed: {}", e)))?;
json!({
"ct": general_purpose::STANDARD_NO_PAD.encode(epriv_ciphertext),
"iv": general_purpose::STANDARD_NO_PAD.encode(epriv_nonce_bytes),
"s": general_purpose::STANDARD_NO_PAD.encode(&salt),
})
} else {
json!({})
};
let user_soul = format!("~{}", pair.pub_key);
let user_data = json!({
"alias": alias.clone().unwrap_or_default(),
"pub": pair.pub_key.clone(),
"epub": pair.epub_key.clone(),
"hash": password_hash,
"salt": general_purpose::STANDARD_NO_PAD.encode(&salt),
"priv": priv_key_encrypted,
"epriv": epriv_key_encrypted,
});
chain.get(&user_soul).put(user_data).await
.map_err(|e| SeaError::Crypto(format!("Failed to store user data: {}", e)))?;
if let Some(ref alias_str) = alias {
let alias_soul = format!("~{}@{}", pair.pub_key, alias_str);
chain.get(&alias_soul).put(json!({
"#": user_soul
})).await
.map_err(|e| SeaError::Crypto(format!("Failed to store alias: {}", e)))?;
}
Ok(UserAuth { pair, alias })
}
pub async fn authenticate(
chain: Arc<Chain>,
alias: &str,
password: &str,
) -> Result<UserAuth, SeaError> {
let graph = &chain.core.graph;
let all_nodes = graph.all_nodes();
let mut user_soul: Option<String> = None;
for (soul, node) in all_nodes.iter() {
if let Some(alias_value) = node.data.get("alias") {
if let Some(alias_str) = alias_value.as_str() {
if alias_str == alias {
user_soul = Some(soul.clone());
break;
}
}
}
}
if user_soul.is_none() {
let alias_path = format!("~{}@{}", "", alias);
let alias_chain = chain.get(&alias_path);
let mut alias_data: Option<serde_json::Value> = None;
alias_chain.once(|data, _key| {
alias_data = Some(data);
}).await.map_err(|e| SeaError::Crypto(format!("Failed to look up alias: {}", e)))?;
if let Some(data) = alias_data {
if let Some(soul_ref) = data.get("#").and_then(|v| v.as_str()) {
user_soul = Some(soul_ref.to_string());
} else if data.is_string() {
user_soul = data.as_str().map(|s| s.to_string());
}
}
}
if user_soul.is_none() {
return Err(SeaError::Crypto(format!("User with alias '{}' not found", alias)));
}
let user_soul = user_soul.unwrap();
let user_chain = chain.get(&user_soul);
let mut user_data: Option<serde_json::Value> = None;
user_chain.once(|data, _key| {
user_data = Some(data);
}).await.map_err(|e| SeaError::Crypto(format!("Failed to get user data: {}", e)))?;
let user_data = user_data.ok_or_else(|| SeaError::Crypto("User data not found".to_string()))?;
let stored_hash = user_data.get("hash")
.and_then(|v| v.as_str())
.ok_or_else(|| SeaError::Crypto("Missing password hash in user data".to_string()))?;
let salt_b64 = user_data.get("salt")
.and_then(|v| v.as_str())
.ok_or_else(|| SeaError::Crypto("Missing salt in user data".to_string()))?;
let salt = general_purpose::STANDARD_NO_PAD.decode(salt_b64)
.map_err(|e| SeaError::Crypto(format!("Invalid salt encoding: {}", e)))?;
if !verify_password(password, &salt, stored_hash) {
return Err(SeaError::Crypto("Invalid password".to_string()));
}
let pub_key = user_data.get("pub")
.and_then(|v| v.as_str())
.ok_or_else(|| SeaError::Crypto("Missing public key in user data".to_string()))?
.to_string();
let priv_encrypted = user_data.get("priv")
.ok_or_else(|| SeaError::Crypto("Missing encrypted private key".to_string()))?;
let password_key = super::work::work(
password.as_bytes(),
Some(salt.clone()),
super::work::WorkOptions {
name: Some("PBKDF2".to_string()),
iterations: Some(100_000),
salt: Some(salt.clone()),
hash: Some("SHA-256".to_string()),
length: Some(256), encode: Some("base64".to_string()),
},
).await?;
let ct_b64 = priv_encrypted.get("ct")
.and_then(|v| v.as_str())
.ok_or_else(|| SeaError::Decryption("Missing ciphertext".to_string()))?;
let iv_b64 = priv_encrypted.get("iv")
.and_then(|v| v.as_str())
.ok_or_else(|| SeaError::Decryption("Missing IV".to_string()))?;
let password_key_bytes = general_purpose::STANDARD_NO_PAD.decode(&password_key)
.map_err(|_| SeaError::Crypto("Failed to decode password key".to_string()))?;
let cipher = Aes256Gcm::new_from_slice(&password_key_bytes)
.map_err(|e| SeaError::Crypto(format!("Failed to create cipher: {}", e)))?;
let iv_bytes = general_purpose::STANDARD_NO_PAD.decode(iv_b64)
.map_err(|_| SeaError::Decryption("Invalid IV encoding".to_string()))?;
let ciphertext = general_purpose::STANDARD_NO_PAD.decode(ct_b64)
.map_err(|_| SeaError::Decryption("Invalid ciphertext encoding".to_string()))?;
let nonce = aes_gcm::Nonce::from_slice(&iv_bytes);
let plaintext = cipher.decrypt(nonce, ciphertext.as_ref())
.map_err(|e| SeaError::Decryption(format!("Decryption failed: {}", e)))?;
let priv_data_str = String::from_utf8(plaintext)
.map_err(|e| SeaError::Decryption(format!("Invalid UTF-8: {}", e)))?;
let priv_decrypted: serde_json::Value = serde_json::from_str(&priv_data_str)
.map_err(|e| SeaError::Decryption(format!("Invalid JSON: {}", e)))?;
let priv_key = priv_decrypted.get("priv")
.and_then(|v| v.as_str())
.ok_or_else(|| SeaError::Crypto("Failed to decrypt private key".to_string()))?
.to_string();
let epub_key = user_data.get("epub")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let epriv_key = if let Some(epriv_encrypted) = user_data.get("epriv") {
if epriv_encrypted.is_object() && !epriv_encrypted.as_object().unwrap().is_empty() {
let epriv_ct_b64 = epriv_encrypted.get("ct")
.and_then(|v| v.as_str())
.ok_or_else(|| SeaError::Decryption("Missing ciphertext".to_string()))?;
let epriv_iv_b64 = epriv_encrypted.get("iv")
.and_then(|v| v.as_str())
.ok_or_else(|| SeaError::Decryption("Missing IV".to_string()))?;
let epriv_iv_bytes = general_purpose::STANDARD_NO_PAD.decode(epriv_iv_b64)
.map_err(|_| SeaError::Decryption("Invalid IV encoding".to_string()))?;
let epriv_ciphertext = general_purpose::STANDARD_NO_PAD.decode(epriv_ct_b64)
.map_err(|_| SeaError::Decryption("Invalid ciphertext encoding".to_string()))?;
let epriv_nonce = aes_gcm::Nonce::from_slice(&epriv_iv_bytes);
let epriv_plaintext = cipher.decrypt(epriv_nonce, epriv_ciphertext.as_ref())
.map_err(|e| SeaError::Decryption(format!("Decryption failed: {}", e)))?;
let epriv_data_str = String::from_utf8(epriv_plaintext)
.map_err(|e| SeaError::Decryption(format!("Invalid UTF-8: {}", e)))?;
let epriv_decrypted: serde_json::Value = serde_json::from_str(&epriv_data_str)
.map_err(|e| SeaError::Decryption(format!("Invalid JSON: {}", e)))?;
epriv_decrypted.get("epriv")
.and_then(|v| v.as_str())
.map(|s| s.to_string())
} else {
None
}
} else {
None
};
let pair = KeyPair {
pub_key,
priv_key,
epub_key,
epriv_key,
};
Ok(UserAuth {
pair,
alias: Some(alias.to_string()),
})
}
pub fn hash_password(password: &str, salt: &[u8]) -> String {
let mut hash = vec![0u8; 32];
pbkdf2_hmac::<Sha256>(password.as_bytes(), salt, 100000, &mut hash);
general_purpose::STANDARD_NO_PAD.encode(&hash)
}
pub fn verify_password(password: &str, salt: &[u8], hash: &str) -> bool {
let computed_hash = hash_password(password, salt);
computed_hash == hash
}
pub fn generate_salt() -> Vec<u8> {
use rand::RngCore;
let mut salt = vec![0u8; 16];
rand::thread_rng().fill_bytes(&mut salt);
salt
}
pub async fn recall(
chain: Option<Arc<Chain>>,
storage_path: Option<&str>,
) -> Result<Option<UserAuth>, SeaError> {
if let Some(_chain_ref) = chain {
}
if let Some(path) = storage_path {
let contents = tokio::fs::read_to_string(path).await
.map_err(|e| SeaError::Crypto(format!("Failed to read storage file: {}", e)))?;
let pair_data: serde_json::Value = serde_json::from_str(&contents)
.map_err(|e| SeaError::Crypto(format!("Failed to parse storage data: {}", e)))?;
if let Some(exp) = pair_data.get("exp").and_then(|v| v.as_f64()) {
let now = chrono::Utc::now().timestamp_millis() as f64;
let expiry_ms = 12.0 * 60.0 * 60.0 * 1000.0; if now > (exp + expiry_ms) {
return Ok(None); }
}
let pub_key = pair_data.get("pub")
.and_then(|v| v.as_str())
.ok_or_else(|| SeaError::Crypto("Missing pub key in stored data".to_string()))?
.to_string();
let priv_key = pair_data.get("priv")
.and_then(|v| v.as_str())
.ok_or_else(|| SeaError::Crypto("Missing priv key in stored data".to_string()))?
.to_string();
let epub_key = pair_data.get("epub")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let epriv_key = pair_data.get("epriv")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let alias = pair_data.get("alias")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let pair = KeyPair {
pub_key,
priv_key,
epub_key,
epriv_key,
};
return Ok(Some(UserAuth { pair, alias }));
}
Ok(None)
}