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