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, host: Option<&str>) -> String {
18 self.host
19 .clone()
20 .unwrap_or_else(|| host.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 {:?}",
38 file
39 ))?;
40 let token = serde_json::from_str(&token).context("While trying to parse token")?;
41 Ok(token)
42 }
43
44 fn store_credentials(&self, token: Token) -> Result<(), Error> {
45 let home = posthog_home_dir();
46 ensure_homedir_exists()?;
47 let file = home.join("credentials.json");
48 let token = serde_json::to_string(&token).context("While trying to serialize token")?;
49 std::fs::write(file.clone(), token).context(format!(
50 "While trying to write credentials to file {:?}",
51 file
52 ))?;
53 Ok(())
54 }
55
56 fn report_location(&self) -> String {
57 posthog_home_dir()
58 .join("credentials.json")
59 .to_string_lossy()
60 .to_string()
61 }
62}
63
64pub struct EnvVarProvider;
66
67impl CredentialProvider for EnvVarProvider {
68 fn get_credentials(&self) -> Result<Token, Error> {
69 let host = std::env::var("POSTHOG_CLI_HOST").ok();
70 let token = std::env::var("POSTHOG_CLI_TOKEN").context("While trying to read env var")?;
71 let env_id = std::env::var("POSTHOG_CLI_ENV_ID").context("While trying to read env var")?;
72 Ok(Token {
73 host,
74 token,
75 env_id,
76 })
77 }
78
79 fn store_credentials(&self, _token: Token) -> Result<(), Error> {
80 Ok(())
81 }
82
83 fn report_location(&self) -> String {
84 unimplemented!("We should never try to save a credential to the env");
85 }
86}
87
88pub fn host_validator(host: &str) -> Result<Validation, CustomUserError> {
89 if host.is_empty() || Url::parse(host).is_err() {
90 return Ok(Validation::Invalid("Host must be a valid URL".into()));
91 }
92
93 Ok(Validation::Valid)
94}
95
96pub fn token_validator(token: &str) -> Result<Validation, CustomUserError> {
97 if token.is_empty() {
98 return Ok(Validation::Invalid("Token cannot be empty".into()));
99 };
100
101 if !token.starts_with("phx_") {
102 return Ok(Validation::Invalid(
103 "Token looks wrong, must start with 'phx_'".into(),
104 ));
105 }
106
107 Ok(Validation::Valid)
108}
109
110pub fn load_token() -> Result<Token, Error> {
111 let env = EnvVarProvider;
112 let env_err = match env.get_credentials() {
113 Ok(token) => {
114 info!("Using token from env var, for environment {}", token.env_id);
115 return Ok(token);
116 }
117 Err(e) => e,
118 };
119 let provider = HomeDirProvider;
120 let dir_err = match provider.get_credentials() {
121 Ok(token) => {
122 info!(
123 "Using token from: {}, for environment {}",
124 provider.report_location(),
125 token.env_id
126 );
127 return Ok(token);
128 }
129 Err(e) => e,
130 };
131
132 Err(
133 anyhow::anyhow!("Couldn't load credentials... Have you logged in recently?")
134 .context(env_err)
135 .context(dir_err),
136 )
137}