edgee_api_client/
auth.rs

1use std::path::PathBuf;
2
3use anyhow::{Context, Result};
4use serde::{Deserialize, Serialize};
5
6use crate::{
7    connect_builder::{IsUnset, SetApiToken, State},
8    ConnectBuilder,
9};
10
11#[derive(Debug, Clone, Default, Deserialize, Serialize)]
12pub struct Credentials {
13    pub api_token: Option<String>,
14}
15
16impl Credentials {
17    pub fn path() -> Result<PathBuf> {
18        let config_dir = dirs::config_dir()
19            .ok_or_else(|| anyhow::anyhow!("Could not get user config directory"))?
20            .join("edgee");
21        if !config_dir.exists() {
22            std::fs::create_dir_all(&config_dir).context("Could not create Edgee config dir")?;
23        }
24
25        Ok(config_dir.join("credentials.toml"))
26    }
27
28    pub fn load() -> Result<Self> {
29        let creds_path = Self::path()?;
30        if !creds_path.exists() {
31            return Ok(Self::default());
32        }
33
34        let content =
35            std::fs::read_to_string(creds_path).context("Could not read credentials file")?;
36        toml::from_str(&content).context("Could not load credentials file")
37    }
38
39    pub fn save(&self) -> Result<()> {
40        use std::io::Write;
41
42        let content =
43            toml::to_string_pretty(self).context("Could not serialize credentials data")?;
44
45        let creds_path = Self::path()?;
46
47        let mut file = {
48            use std::fs::OpenOptions;
49
50            let mut options = OpenOptions::new();
51            options.write(true).create(true).truncate(true);
52
53            #[cfg(unix)]
54            {
55                use std::os::unix::fs::OpenOptionsExt;
56
57                // Set credentials file permissions to 0600 (u=rw-,g=,o=)
58                // so only the user has access.
59                options.mode(0o0600);
60            }
61
62            options.open(creds_path)?
63        };
64
65        file.write_all(content.as_bytes())
66            .context("Could not write credentials data")
67    }
68
69    pub fn check_api_token(&self) -> Result<()> {
70        let Some(_api_token) = self.api_token.as_deref() else {
71            anyhow::bail!("Not logged in");
72        };
73
74        // TODO: Check API token is valid using the API
75
76        Ok(())
77    }
78}
79
80impl<'a, S: State> ConnectBuilder<'a, S> {
81    pub fn credentials(self, creds: &Credentials) -> ConnectBuilder<'a, SetApiToken<S>>
82    where
83        S::ApiToken: IsUnset,
84    {
85        let api_token = creds.api_token.as_deref().unwrap();
86        self.api_token(api_token)
87    }
88}