Skip to content

Commit

Permalink
Switch to object-driven approach for profiles
Browse files Browse the repository at this point in the history
  • Loading branch information
duckysmacky committed Jan 11, 2025
1 parent 0310a99 commit ee93433
Show file tree
Hide file tree
Showing 12 changed files with 323 additions and 313 deletions.
9 changes: 5 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 3 additions & 4 deletions src/core/data/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
use argon2::{self, Config};
use rand::random;
use crate::{Result, Error};
use crate::core::data::profiles::Profile;

/// Hashes the given password. Returns hashed password and salt
pub fn hash_password(password: &str) -> Result<(String, [u8; 16])> {
Expand All @@ -14,8 +13,8 @@ pub fn hash_password(password: &str) -> Result<(String, [u8; 16])> {
Ok((hashed_password, salt))
}

/// Verifies password for the given profile
pub fn verify_password(input_password: &str, profile: Profile) -> bool {
argon2::verify_encoded(&profile.password_hash, input_password.as_bytes())
/// Verifies password by comparing it to the password hash
pub fn verify_password(password_hash: &str, password: &str) -> bool {
argon2::verify_encoded(password_hash, password.as_bytes())
.unwrap_or(false)
}
4 changes: 2 additions & 2 deletions src/core/data/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ pub fn write_bytes(path: &Path, bytes: &[u8], truncate: bool) -> Result<()> {

/// Writes string to the specified file. Creates a new one if already doesn't exist
pub fn write_file(path: &Path, contents: &str, truncate: bool) -> Result<()> {
log_debug!("Writing file to \"{}\"", path.display());
log_debug!("Writing to \"{}\"", path.display());
let mut file = File::options()
.write(true)
.create(true)
Expand All @@ -59,6 +59,6 @@ pub fn write_file(path: &Path, contents: &str, truncate: bool) -> Result<()> {
file.write_all(contents.as_bytes())?;
file.flush()?;

log_debug!("Wrote {} to file", contents.len());
log_debug!("Wrote {} to file", contents);
Ok(())
}
34 changes: 10 additions & 24 deletions src/core/data/keys.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,22 @@
//! Contains functions related to key manipulation
//! Contains wrapper functions above profiles to get and set current profile's key
use crate::{Error, Result, Key};
use super::profile;
use crate::log_debug;
use crate::core::encryption::cipher;
use super::profiles;
use crate::{Key, Result};

/// Gets the key for the current profile
pub fn get_key() -> Result<Key> {
log_debug!("Getting encryption key from current profile");

let profile_data = profiles::get_current_profile()?;
Ok(profile_data.key)
let mut profiles = profile::get_profiles();
let profile = profiles.get_current_profile()?;
Ok(profile.key)
}

/// Sets the key for the current profile
pub fn set_key(new_key: Key) -> Result<()> {
log_debug!("Setting a new encryption key for current profile");

let mut profile = profiles::get_current_profile()?;
profile.key = new_key;

profiles::save_profile(profile)
.map_err(|err| Error::IOError(format!("Unable to save profile data: {}", err)))
}

/// Generates new key and overwrites the old for the current profile
pub fn generate_new_key() -> Result<()> {
log_debug!("Generating new encryption key for current profile");

let mut profile = profiles::get_current_profile()?;
profile.key = cipher::generate_key();

profiles::save_profile(profile)
.map_err(|err| Error::IOError(format!("Unable to save profile data: {}", err)))
let mut profiles = profile::get_profiles();
let profile = profiles.get_current_profile()?;
profile.set_key(new_key);
profiles.save()
}
4 changes: 2 additions & 2 deletions src/core/data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
//! interactions
pub mod keys;
pub mod auth;
pub mod profiles;
mod auth;
pub mod profile;
pub mod config;
pub mod os;
pub mod io;
229 changes: 229 additions & 0 deletions src/core/data/profile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
//! Contains function for user profile manipulation
use super::auth;
use super::io::{read_file, write_file};
use crate::core::data::os;
use crate::core::encryption::cipher;
use crate::{log_debug, log_error, log_info, Error, Key, Result};
use serde::{Deserialize, Serialize};
use std::io::{self};
use std::path::PathBuf;

/// Name of the file which stores all the profile data
const PROFILES_FILE_NAME: &str = "profiles.json";

/// Struct holding all the needed profile information for the program. Saved on the disk as a JSON
/// file
#[derive(Serialize, Deserialize, Debug)]
pub struct LockboxProfiles {
current_profile: Option<String>,
profiles: Vec<Profile>,
#[serde(skip)]
file_path: PathBuf
}

/// Object-driven approach
impl LockboxProfiles {
/// Imports self from the stored "profiles.json" file. In case of the file missing, generates a
/// new object with default empty values
pub fn import() -> Result<Self> {
log_debug!("Importing Lockbox profiles");
let data_directory = os::get_data_dir()?;
let profiles_file = data_directory.join(PROFILES_FILE_NAME);

let profiles = match read_file(&profiles_file) {
Ok(file_data) => {
let mut profiles: LockboxProfiles = serde_json::from_str(&file_data)?;
profiles.file_path = profiles_file;
profiles
},
Err(err) => {
if err.kind() == io::ErrorKind::NotFound {
log_info!("\"profiles.json\" file doesn't exist. Generating new profiles data");
Self::new(profiles_file)
} else {
return Err(err.into());
}
}
};

Ok(profiles)
}

/// Bare-minimum constructor to use in case of file not being available to import from
fn new(
file_path: PathBuf
) -> Self {
LockboxProfiles {
current_profile: None,
profiles: vec![],
file_path
}
}

/// Returns currently selected profile data
pub fn get_current_profile(&mut self) -> Result<&mut Profile> {
log_debug!("Getting current profile");
let current_profile = self.current_profile.clone();

let profile = match current_profile {
None => return Err(Error::ProfileError("No profile is currently selected".to_string())),
Some(profile_name) => {
self.find_profile(&profile_name)?
}
};

Ok(profile)
}

/// Returns a list of currently available profiles
pub fn get_profiles(&self) -> &Vec<Profile> {
log_debug!("Getting all available profiles");
&self.profiles
}

/// Sets the current profile to profile which name was supplied. Returns an error if given
/// profile doesn't exist
pub fn set_current(&mut self, profile_name: &str) -> Result<()> {
log_debug!("Setting current profile to \"{}\"", profile_name);

self.current_profile = Some(profile_name.to_string());
self.save()?;

log_debug!("Set current profile to \"{}\"", profile_name);
Ok(())
}

/// Deletes a profile with provided name
pub fn delete_profile(&mut self, profile_name: &str) -> Result<()> {
log_debug!("Deleting profile with name \"{}\"", profile_name);

for (i, profile) in self.profiles.iter().enumerate() {
if profile.name == profile_name {
self.profiles.remove(i);
self.current_profile = {
if self.profiles.is_empty() {
None
} else {
Some(self.profiles.get(0).unwrap().name.clone())
}
};
self.save()?;
return Ok(())
}
}

Err(Error::ProfileError(format!("Profile with name \"{}\" doesn\'t exist", profile_name)))
}

/// Saves provided profile data to profiles file. Updates existing profile or creates a new one,
/// if it doesn't already exist
#[allow(dead_code)]
pub fn save_profile(&mut self, profile: Profile) -> Result<()> {
log_debug!("Saving profile: {:?}", &profile);

let profile_name = profile.name.clone();

if self.profiles.is_empty() {
self.profiles.push(profile);
self.current_profile = Some(profile_name);
} else {
for i in 0..self.profiles.len() {
if self.profiles[i].name == profile_name {
self.profiles.insert(i, profile);
break;
}

if i == self.profiles.len() - 1 {
self.profiles.push(profile);
break;
}
}
}

self.save()?;
Ok(())
}

/// Adds a new profile to the profiles file. Errors if the profile already exists, as this
/// functions only accepts new profiles
pub fn new_profile(&mut self, profile: Profile) -> Result<()> {
log_debug!("Adding a new profile: {:?}", &profile);

let profile_name = profile.name.clone();
if self.find_profile(&profile_name).is_ok() {
return Err(Error::ProfileError(format!("Profile with name \"{}\" already exists", profile_name)));
}
self.profiles.push(profile);

self.save()?;
Ok(())
}

/// Returns profile for profile which name was supplied
pub fn find_profile(&mut self, profile_name: &str) -> Result<&mut Profile> {
log_debug!("Searching for profile with name \"{}\"", profile_name);

for profile in &mut self.profiles {
if profile.name == profile_name {
return Ok(profile)
}
}

Err(Error::ProfileError(format!("Profile with name \"{}\" doesn\'t exist", profile_name)))
}

/// Writes to the profile data file. Overwrites old data
pub fn save(&self) -> Result<()> {
log_debug!("Saving profiles data to \"profiles.json\"");
let json_data = serde_json::to_string(&self)?;

write_file(&self.file_path, &json_data, true)?;
Ok(())
}
}

/// Struct containing main information about a profile
#[derive(Serialize, Deserialize, Debug)]
pub struct Profile {
pub name: String,
pub key: Key,
password_hash: String
}

impl Profile {
pub fn new(
name: &str,
password: &str
) -> Result<Self> {
let (hash, _salt) = auth::hash_password(password)?;
Ok(Profile {
name: name.to_string(),
key: cipher::generate_key(),
password_hash: hash,
})
}

/// Checks whether the provided password is valid for the profile
pub fn verify_password(&self, password: &str) -> bool {
auth::verify_password(&self.password_hash, password)
}

/// Sets a new key for the profile
pub fn set_key(&mut self, key: Key) {
self.key = key;
}
}

pub fn get_profiles() -> LockboxProfiles {
log_debug!("Getting Lockbox profiles");

match LockboxProfiles::import() {
Ok(profiles) => profiles,
Err(err) => {
log_error!("Unable to import Lockbox profiles");
log_error!("{}", err);
std::process::exit(1);
}
}
}
Loading

0 comments on commit ee93433

Please sign in to comment.