use sled::{Db};
use uuid::Uuid;
use chrono::{DateTime, Utc};
use serde::{Serialize, Deserialize};
use bincode::serde::{encode_into_slice, decode_from_slice};
use bincode::config::standard;
use crate::passgen::Capitalization;
use crate::xotp::XOTP;
#[derive(Serialize, Deserialize, Clone, Debug, bincode::Encode, bincode::Decode)]
pub enum PasswordPolicy {
Random {
length: usize,
include_uppercase: bool,
include_lowercase: bool,
include_numbers: bool,
include_special: bool,
url_safe: bool,
avoid_confusion: bool
},
Memorable {
words: u8,
separator: char,
include_numbers: bool,
capitalization: Capitalization
},
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct PasswordHistory {
pub password: Vec<u8>,
pub created_at: DateTime<Utc>
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct PasswordEntry {
pub id: Uuid,
pub name: String,
pub username: Option<String>,
pub histories: Vec<PasswordHistory>, pub current_password: Vec<u8>, pub url: Option<String>,
pub expires_at: Option<DateTime<Utc>>, pub policy: Option<PasswordPolicy>,
pub note: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub deleted: bool,
}
impl PasswordEntry {
pub fn need_update(&self) -> bool {
if self.deleted {
return false;
} else if self.expires_at.is_some() {
let now = Utc::now();
let expires_at = self.expires_at.unwrap();
if now > expires_at {
return true;
} else {
return false;
}
} else {
return false;
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct OtpEntry {
pub id: Uuid,
pub secretdata: XOTP,
pub created_at: DateTime<Utc>,
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Clone, Debug)]
enum IndexType {
Name,
Note,
}
pub struct PasswordManager {
db: Db,
}
impl PasswordManager {
const PASSWORDS_TREE: &'static str = "passwords";
const INDEX_TREE: &'static str = "index";
const XOTP_TREE: &'static str = "xotp";
pub fn new(db_path: &str) -> Result<Self, sled::Error> {
let db = sled::open(db_path)?;
Ok(PasswordManager { db })
}
pub fn add_password(
&self,
name: String,
username: Option<String>,
password: Vec<u8>,
url: Option<String>,
expires_in_days: u32, policy: Option<PasswordPolicy>,
note: Option<String>,
) -> Result<Uuid, Box<dyn std::error::Error>> {
let id = Uuid::new_v4();
let now = Utc::now();
let expires_at = if expires_in_days > 0 {
Some(now + chrono::Duration::days(expires_in_days as i64))
} else {
None
};
let history = PasswordHistory {
password: password.clone(),
created_at: now,
};
let entry = PasswordEntry {
id,
name: name.clone(),
username,
histories: vec![history],
current_password: password.clone(),
url,
expires_at,
policy,
note: note.clone(),
created_at: now,
updated_at: now,
deleted: false,
};
let mut buffer = vec![0u8; 1024]; encode_into_slice(&entry, &mut buffer, standard())?;
let serialized = &buffer[..];
let passwords_tree = self.db.open_tree(Self::PASSWORDS_TREE)?;
passwords_tree.insert(id.as_bytes(), serialized)?;
self.update_index(IndexType::Name, &name, id)?;
if let Some(note) = note {
self.update_index(IndexType::Note, ¬e, id)?;
}
Ok(id)
}
pub fn delete_password(
&self,
id: Option<Uuid>,
name: Option<String>
) -> Result<(), Box<dyn std::error::Error>> {
let passwords_tree = self.db.open_tree(Self::PASSWORDS_TREE)?;
let id = match id {
Some(id) => id,
None => self.get_uuid(name.as_ref().unwrap())?,
};
if let Some(entry) = passwords_tree.get(id.as_bytes())? {
let mut entry: PasswordEntry = bincode::serde::decode_from_slice(&entry, standard())?.0;
entry.deleted = true;
let mut buffer = vec![0u8; 1024]; encode_into_slice(&entry, &mut buffer, standard())?;
let serialized = &buffer[..];
passwords_tree.insert(id.as_bytes(), serialized)?;
}
Ok(())
}
pub fn get_password(
&self,
id: Option<Uuid>,
expired: Option<bool>,
) -> Result<Vec<PasswordEntry>, Box<dyn std::error::Error>> {
let expired = expired.unwrap_or(false);
let passwords_tree = self.db.open_tree(Self::PASSWORDS_TREE)?;
if expired {
let mut entries = Vec::new();
for record in passwords_tree.iter() {
let (_, value) = record?;
let entry: PasswordEntry = bincode::serde::decode_from_slice(&value, standard())?.0;
if entry.need_update() {
entries.push(entry);
}
}
Ok(entries)
} else {
let id = id.ok_or("Password Id must be specified")?;
if let Some(entry) = passwords_tree.get(id.as_bytes())? {
let entry: PasswordEntry = bincode::serde::decode_from_slice(&entry, standard())?.0;
Ok(vec![entry])
} else {
Err("Password Id is wrong".into())
}
}
}
pub fn update_password(
&self,
id: Uuid,
new_password: Vec<u8>
) -> Result<(), Box<dyn std::error::Error>> {
let passwords_tree = self.db.open_tree(Self::PASSWORDS_TREE)?;
if let Some(entry) = passwords_tree.get(id.as_bytes())? {
let mut entry: PasswordEntry = bincode::serde::decode_from_slice(&entry, standard())?.0;
let now = Utc::now();
let history = PasswordHistory {
password: new_password.clone(),
created_at: now,
};
entry.histories.push(history);
entry.current_password = new_password;
entry.updated_at = now;
let mut buffer = vec![0u8; 1024]; encode_into_slice(&entry, &mut buffer, standard())?;
let serialized = &buffer[..];
passwords_tree.insert(id.as_bytes(), serialized)?;
}
Ok(())
}
pub fn find_passwords(
&self,
query: &str,
exact_match: bool,
) -> Result<Vec<PasswordEntry>, Box<dyn std::error::Error>> {
let passwords_tree = self.db.open_tree(Self::PASSWORDS_TREE)?;
let mut results = Vec::new();
for record in passwords_tree.iter() {
let (_, value) = record?;
let entry: PasswordEntry = bincode::serde::decode_from_slice(&value, standard())?.0;
let name_match = if exact_match {
entry.name == query
} else {
entry.name.contains(query)
};
let note_match = entry.note.as_ref().map_or(false, |n| {
if exact_match {
n == query
} else {
n.contains(query)
}
});
if name_match || note_match {
if !entry.deleted {
results.push(entry);
}
}
}
Ok(results)
}
pub fn list_passwords(&self) -> Result<Vec<PasswordEntry>, Box<dyn std::error::Error>> {
let passwords_tree = self.db.open_tree(Self::PASSWORDS_TREE)?;
let mut entries = Vec::new();
for record in passwords_tree.iter() {
let (_, value) = record?;
let entry: PasswordEntry = decode_from_slice(&value, standard())?.0;
if !entry.deleted {
entries.push(entry);
}
}
Ok(entries)
}
pub fn get_uuid(&self, key: &str) -> Result<Uuid, Box<dyn std::error::Error>> {
let index_tree = self.db.open_tree(Self::INDEX_TREE)?;
let index_key = format!("{:?}:{}", IndexType::Name, key);
if let Some(existing) = index_tree.get(&index_key)? {
let ids: Vec<Uuid> = decode_from_slice(&existing, standard())?.0;
if ids.is_empty() {
return Err("No UUID found for the given index type and key".into());
}
Ok(ids[0])
} else {
Err("No UUID found for the given index type and key".into())
}
}
pub fn get_password_entry(
&self,
id: Uuid,
) -> Result<PasswordEntry, Box<dyn std::error::Error>> {
let passwords_tree = self.db.open_tree(Self::PASSWORDS_TREE)?;
if let Some(entry) = passwords_tree.get(id.as_bytes())? {
let entry: PasswordEntry = bincode::serde::decode_from_slice(&entry, standard())?.0;
Ok(entry)
} else {
Err("No PasswordEntry found for the given UUID".into())
}
}
fn update_index(
&self,
index_type: IndexType,
key: &str,
id: Uuid,
) -> Result<(), Box<dyn std::error::Error>> {
let index_tree = self.db.open_tree(Self::INDEX_TREE)?;
let index_key = format!("{:?}:{}", index_type, key);
let mut ids = if let Some(existing) = index_tree.get(&index_key)? {
decode_from_slice(&existing, standard())?.0
} else {
Vec::new()
};
if !ids.contains(&id) {
ids.push(id);
let serialized = bincode::serde::encode_to_vec(&ids, standard())?;
index_tree.insert(index_key, serialized.as_slice())?;
}
Ok(())
}
pub fn add_otp(
&self,
id: Uuid,
otp: XOTP,
) -> Result<(), Box<dyn std::error::Error>> {
let xotp_tree = self.db.open_tree(Self::XOTP_TREE)?;
let otp_entry = OtpEntry {
id: id.clone(),
secretdata: otp,
created_at: Utc::now(),
};
let serialized = bincode::serde::encode_to_vec(&otp_entry, standard())?;
xotp_tree.insert(id.clone(), serialized.as_slice())?;
Ok(())
}
pub fn list_otp(
&self,
) -> Result<Vec<OtpEntry>, Box<dyn std::error::Error>> {
let xotp_tree = self.db.open_tree(Self::XOTP_TREE)?;
let mut entries = Vec::new();
for record in xotp_tree.iter() {
let (_, value) = record?;
let entry: OtpEntry = bincode::serde::decode_from_slice(&value, standard())?.0;
entries.push(entry);
}
Ok(entries)
}
pub fn get_otp(
&self,
id: Uuid,
) -> Result<Option<XOTP>, Box<dyn std::error::Error>> {
let xotp_tree = self.db.open_tree(Self::XOTP_TREE)?;
if let Some(entry) = xotp_tree.get(id.as_bytes())? {
let otp_entry: OtpEntry = bincode::serde::decode_from_slice(&entry, standard())?.0;
Ok(Some(otp_entry.secretdata))
} else {
Ok(None)
}
}
}