awsmfa 0.3.2

The automation tool for Multi-Factor Authentication (MFA) process to use awscli.
Documentation
use crate::Result;

use super::{aws_home, sts::StsCredential, ConfFile, ConfLoader, Profile};
use std::path::{Path, PathBuf};

const PROFILE: &str = r"^\[(.+)\]$";
const FILENAME: &str = "credentials";

#[derive(Debug)]
pub struct Credentials {
    content: ConfFile,
}

impl Credentials {
    pub fn new() -> Result<Self> {
        let path = filepath()?;
        Self::load(path.as_path())
    }

    pub fn set_cred(self, name: &str, cred: StsCredential) -> Self {
        let StsCredential {
            access_key_id,
            secret_access_key,
            session_token,
            ..
        } = cred;

        let profile = Profile::new(name)
            .set("aws_access_key_id", &access_key_id)
            .set("aws_secret_access_key", &secret_access_key)
            .set("aws_session_token", &session_token);

        let content = self.content.set(profile);

        Self { content }
    }

    pub fn save(&self) -> Result<()> {
        let path = filepath()?;
        self.write(path.as_path())
    }

    fn load(path: &Path) -> Result<Self> {
        let fmt = Box::new(|p: &str| format!("[{p}]"));
        let content = ConfLoader::new()
            .set_path(path)
            .set_reg_profile(PROFILE)
            .set_formatter(fmt)
            .load()?;

        Ok(Self { content })
    }

    fn write(&self, path: &Path) -> Result<()> {
        self.content.write(path)
    }
}

fn filepath() -> Result<PathBuf> {
    Ok(aws_home()?.join(FILENAME))
}

#[cfg(test)]
mod tests {
    use super::*;

    fn build() -> Credentials {
        let path = Path::new("mock/test_credentials");
        Credentials::load(path).unwrap()
    }

    #[test]
    fn it_loads_credentials() {
        let creds = build();
        let result = creds.content.profile("tanaka");
        assert!(result.is_some());

        let profile = result.unwrap();
        assert_eq!(
            profile.get("aws_access_key_id"),
            Some("ABCDEFGHIJKLMNOPQRST")
        );
        assert_eq!(
            profile.get("aws_secret_access_key"),
            Some("abcdefghijklmnopqrstuvwxyz+-#$1234567890")
        );
    }

    #[test]
    fn it_sets_profile_from_sts_credentials() {
        let mut cred = StsCredential::default();
        cred.access_key_id = "access_key_id".to_string();
        cred.secret_access_key = "secret_access_key".to_string();
        cred.session_token = "session_token".to_string();

        let creds = build().set_cred("test", cred);
        let result = creds.content.profile("test");
        assert!(result.is_some());

        let profile = result.unwrap();
        assert_eq!(profile.get("aws_access_key_id"), Some("access_key_id"));
        assert_eq!(
            profile.get("aws_secret_access_key"),
            Some("secret_access_key")
        );
        assert_eq!(profile.get("aws_session_token"), Some("session_token"));
    }

    #[test]
    fn it_writes_to_file() {
        let path = Path::new("mock/write_test_credentials");
        let creds0 = build();
        let result = creds0.write(path);
        assert!(result.is_ok());

        let creds1 = Credentials::load(path).unwrap();
        assert_eq!(creds0.content, creds1.content);
    }
}