pijul_identity/
load.rs

1use super::fix_identities;
2use super::Complete;
3use pijul_config as config;
4
5use libpijul::key::{PublicKey, SecretKey};
6
7use std::fs;
8use std::path::PathBuf;
9
10use anyhow::bail;
11use pijul_interaction::Select;
12use std::sync::OnceLock;
13
14static CHOSEN_IDENTITY: OnceLock<String> = OnceLock::new();
15
16/// Returns the directory in which identity information should be stored.
17///
18/// # Arguments
19/// * `name` - The name of the identity. This is encoded on-disk as identities/`<NAME>`
20/// * `should_exist` - If the path should already exist.
21///
22/// # Errors
23/// * An identity of `name` does not exist
24/// * The identity name is empty
25pub fn path(name: &str, should_exist: bool) -> Result<PathBuf, anyhow::Error> {
26    let mut path = config::global_config_dir()
27        .expect("Could not find global config directory")
28        .join("identities");
29
30    if name.is_empty() {
31        bail!("Cannot get path of un-named identity");
32    }
33
34    path.push(name);
35    if !path.exists() && should_exist {
36        bail!("Cannot get identity path: name does not exist")
37    }
38
39    Ok(path)
40}
41
42/// Returns the public key for identity named <NAME>.
43///
44/// # Arguments
45/// * `name` - The name of the identity. This is encoded on-disk as identities/`<NAME>`
46pub fn public_key(name: &str) -> Result<PublicKey, anyhow::Error> {
47    let text = fs::read_to_string(path(name, true)?.join("identity.toml"))?;
48    let identity: Complete = toml::from_str(&text)?;
49
50    Ok(identity.public_key)
51}
52
53/// Returns the secret key for identity named <NAME>.
54///
55/// # Arguments
56/// * `name` - The name of the identity. This is encoded on-disk as identities/`<NAME>`
57pub fn secret_key(name: &str) -> Result<SecretKey, anyhow::Error> {
58    let identity_text = fs::read_to_string(path(name, true)?.join("secret_key.json"))?;
59    let secret_key: SecretKey = serde_json::from_str(&identity_text)?;
60
61    Ok(secret_key)
62}
63
64/// Choose an identity, either through defaults or a user prompt.
65///
66/// # Errors
67/// * User input is required to continue, but `no_prompt` is set to true
68pub async fn choose_identity_name() -> Result<String, anyhow::Error> {
69    if let Some(name) = CHOSEN_IDENTITY.get() {
70        return Ok(name.clone());
71    }
72
73    let mut possible_identities = Complete::load_all()?;
74    if possible_identities.is_empty() {
75        fix_identities().await?;
76        possible_identities = Complete::load_all()?;
77    }
78
79    let chosen_name = if possible_identities.len() == 1 {
80        possible_identities[0].clone().name
81    } else {
82        let index = Select::new()?
83            .with_prompt("Select identity")
84            .with_items(&possible_identities)
85            .with_default(0 as usize)
86            .interact()?;
87
88        possible_identities[index].clone().name
89    };
90
91    // The user has selected once, don't want to query them again
92    CHOSEN_IDENTITY
93        .set(chosen_name.clone())
94        .expect("Could not set chosen identity");
95
96    Ok(chosen_name)
97}
98
99impl Complete {
100    /// Loads a complete identity associated with the given identity name.
101    ///
102    /// # Arguments
103    /// * `identity_name` - The name of the identity. This is encoded on-disk as identities/`<NAME>`
104    pub fn load(identity_name: &str) -> Result<Self, anyhow::Error> {
105        let identity_path = path(identity_name, true)?;
106
107        let text = fs::read_to_string(identity_path.join("identity.toml"))?;
108        let identity: Complete = toml::from_str(&text)?;
109
110        let secret_key = secret_key(identity_name)?;
111
112        Ok(Self::new(
113            identity_name.to_string(),
114            identity.config,
115            identity.public_key,
116            Some(super::Credentials::from(secret_key)),
117        ))
118    }
119
120    /// Loads all valid identities found on disk
121    pub fn load_all() -> Result<Vec<Self>, anyhow::Error> {
122        let config_dir = config::global_config_dir().unwrap();
123        let identities_path = config_dir.join("identities");
124        std::fs::create_dir_all(&identities_path)?;
125
126        let identities_dir = identities_path.as_path().read_dir()?;
127        let mut identities = vec![];
128
129        for dir_entry in identities_dir {
130            let file_name = dir_entry?.file_name();
131            let identity_name = file_name.to_str().unwrap();
132
133            if let Ok(identity) = Self::load(identity_name) {
134                identities.push(identity);
135            }
136        }
137
138        Ok(identities)
139    }
140}