posthog_cli/utils/
auth.rs

1use super::homedir::{ensure_homedir_exists, posthog_home_dir};
2use anyhow::{Context, Error};
3use inquire::{validator::Validation, CustomUserError};
4use tracing::info;
5
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Serialize, Deserialize, Clone)]
9pub struct Token {
10    pub token: String,
11    pub env_id: String,
12}
13
14pub trait CredentialProvider {
15    fn get_credentials(&self) -> Result<Token, Error>;
16    fn store_credentials(&self, token: Token) -> Result<(), Error>;
17    fn report_location(&self) -> String;
18}
19
20pub struct HomeDirProvider;
21
22impl CredentialProvider for HomeDirProvider {
23    fn get_credentials(&self) -> Result<Token, Error> {
24        let home = posthog_home_dir();
25        let file = home.join("credentials.json");
26        let token = std::fs::read_to_string(file.clone()).context(format!(
27            "While trying to read credentials from file {:?}",
28            file
29        ))?;
30        let token = serde_json::from_str(&token).context("While trying to parse token")?;
31        Ok(token)
32    }
33
34    fn store_credentials(&self, token: Token) -> Result<(), Error> {
35        let home = posthog_home_dir();
36        ensure_homedir_exists()?;
37        let file = home.join("credentials.json");
38        let token = serde_json::to_string(&token).context("While trying to serialize token")?;
39        std::fs::write(file.clone(), token).context(format!(
40            "While trying to write credentials to file {:?}",
41            file
42        ))?;
43        Ok(())
44    }
45
46    fn report_location(&self) -> String {
47        posthog_home_dir()
48            .join("credentials.json")
49            .to_string_lossy()
50            .to_string()
51    }
52}
53
54/// Tries to read the token from the env var `POSTHOG_CLI_TOKEN`
55pub struct EnvVarProvider;
56
57impl CredentialProvider for EnvVarProvider {
58    fn get_credentials(&self) -> Result<Token, Error> {
59        let token = std::env::var("POSTHOG_CLI_TOKEN").context("While trying to read env var")?;
60        let env_id = std::env::var("POSTHOG_CLI_ENV_ID").context("While trying to read env var")?;
61        Ok(Token { token, env_id })
62    }
63
64    fn store_credentials(&self, _token: Token) -> Result<(), Error> {
65        Ok(())
66    }
67
68    fn report_location(&self) -> String {
69        unimplemented!("We should never try to save a credential to the env");
70    }
71}
72
73pub fn token_validator(token: &str) -> Result<Validation, CustomUserError> {
74    if token.is_empty() {
75        return Ok(Validation::Invalid("Token cannot be empty".into()));
76    };
77
78    if !token.starts_with("phx_") {
79        return Ok(Validation::Invalid(
80            "Token looks wrong, must start with 'phx_'".into(),
81        ));
82    }
83
84    Ok(Validation::Valid)
85}
86
87pub fn load_token() -> Result<Token, Error> {
88    let env = EnvVarProvider;
89    let env_err = match env.get_credentials() {
90        Ok(token) => {
91            info!("Using token from env var, for environment {}", token.env_id);
92            return Ok(token);
93        }
94        Err(e) => e,
95    };
96    let provider = HomeDirProvider;
97    let dir_err = match provider.get_credentials() {
98        Ok(token) => {
99            info!(
100                "Using token from: {}, for environment {}",
101                provider.report_location(),
102                token.env_id
103            );
104            return Ok(token);
105        }
106        Err(e) => e,
107    };
108
109    Err(
110        anyhow::anyhow!("Couldn't load credentials... Have you logged in recently?")
111            .context(env_err)
112            .context(dir_err),
113    )
114}