1pub mod config;
4pub mod device_code;
5mod file_store;
6mod jwt;
7pub mod refresh;
8mod store;
9mod token_store;
10mod tokens;
11
12pub use file_store::FileStore;
13pub use jwt::extract_tenant_id;
14pub use store::KeychainStore;
15pub use token_store::TokenStore;
16pub use tokens::TokenSet;
17
18use pidge_core::TokenStorage;
19
20use crate::error::ClientError;
21
22pub struct AuthClient {
26 http: reqwest::Client,
27 client_id: String,
28 authority_base: String,
29 scope: String,
30}
31
32impl AuthClient {
33 pub fn from_env() -> Result<Self, ClientError> {
37 let client_id = config::client_id().ok_or(ClientError::NotProvisioned)?;
38 Ok(Self {
39 http: reqwest::Client::builder()
40 .user_agent(format!("pidge/{}", env!("CARGO_PKG_VERSION")))
41 .build()?,
42 client_id,
43 authority_base: config::AUTHORITY.to_string(),
44 scope: config::scope_string(),
45 })
46 }
47
48 pub fn for_test(client_id: impl Into<String>, authority_base: impl Into<String>) -> Self {
50 Self {
51 http: reqwest::Client::new(),
52 client_id: client_id.into(),
53 authority_base: authority_base.into(),
54 scope: config::scope_string(),
55 }
56 }
57
58 pub async fn start_device_code(&self) -> Result<device_code::DeviceCodeResponse, ClientError> {
61 device_code::start(
62 &self.http,
63 &self.authority_base,
64 &self.client_id,
65 &self.scope,
66 )
67 .await
68 }
69
70 pub async fn poll_for_tokens(
72 &self,
73 device_code_resp: &device_code::DeviceCodeResponse,
74 ) -> Result<device_code::PollSuccess, ClientError> {
75 device_code::poll(
76 &self.http,
77 &self.authority_base,
78 &self.client_id,
79 &device_code_resp.device_code,
80 device_code_resp.interval,
81 device_code_resp.expires_in,
82 device_code::real_sleep,
83 )
84 .await
85 }
86
87 pub async fn get_valid_token(&self, email: &str) -> Result<String, ClientError> {
95 let storage = storage_for(email);
96 let tokens =
97 TokenStore::load(email, storage)?.ok_or_else(|| ClientError::SessionExpired {
98 email: email.to_string(),
99 })?;
100
101 if !tokens.needs_refresh() {
102 return Ok(tokens.access_token);
103 }
104
105 let new_tokens = refresh::refresh(
106 &self.http,
107 &self.authority_base,
108 &self.client_id,
109 &tokens,
110 &self.scope,
111 email,
112 )
113 .await?;
114 TokenStore::save(email, &new_tokens, storage)?;
115 Ok(new_tokens.access_token)
116 }
117}
118
119fn storage_for(email: &str) -> TokenStorage {
123 pidge_core::Config::load()
124 .ok()
125 .and_then(|c| c.find(email).map(|a| a.storage))
126 .unwrap_or_default()
127}