1use crate::auth::custom_token::CustomTokenSigner;
4use crate::auth::error::AuthError;
5use crate::auth::id_token::{IdTokenClaims, IdTokenVerifier, JwksCache};
6use crate::auth::identity_toolkit::IdentityToolkitEndpoints;
7use crate::auth::mode::ClientMode;
8use crate::auth::session_cookie::{SessionCookieCertCache, SessionCookieVerifier};
9use crate::auth::users::{
10 CreateUserRequest, UpdateUserRequest, UserOperations, UserPage, UserRecord,
11};
12use crate::core::{Credentials, HttpClient, ProjectId, ServiceAccountKey};
13use std::time::Duration;
14
15pub struct AuthClient {
22 http: HttpClient,
23 project_id: ProjectId,
24 mode: ClientMode,
25 credentials: Credentials,
26 id_token_verifier: IdTokenVerifier,
27 session_cookie_verifier: SessionCookieVerifier,
28 endpoints: IdentityToolkitEndpoints,
29 #[cfg(feature = "live-user-management")]
30 token_provider: tokio::sync::OnceCell<crate::auth::token_provider::TokenProvider>,
31}
32
33impl AuthClient {
34 pub fn builder(project_id: impl Into<String>) -> AuthClientBuilder {
36 AuthClientBuilder::new(project_id)
37 }
38
39 pub fn project_id(&self) -> &ProjectId {
41 &self.project_id
42 }
43
44 pub async fn verify_id_token(
46 &self,
47 token: &str,
48 ) -> Result<crate::auth::id_token::IdTokenClaims, AuthError> {
49 Ok(self.id_token_verifier.verify(token).await?)
50 }
51
52 pub fn create_custom_token(
58 &self,
59 uid: &str,
60 claims: Option<serde_json::Map<String, serde_json::Value>>,
61 ) -> Result<String, AuthError> {
62 let Credentials::ServiceAccount(key) = &self.credentials else {
63 return Err(AuthError::Core(crate::core::CoreError::Credentials(
64 "create_custom_token requires an explicit service account key".to_string(),
65 )));
66 };
67 let signer = CustomTokenSigner::new((**key).clone());
68 Ok(signer.create_custom_token(uid, claims)?)
69 }
70
71 pub async fn create_session_cookie(
74 &self,
75 id_token: &str,
76 valid_duration: Duration,
77 ) -> Result<String, AuthError> {
78 let token = self.bearer_token().await?;
79 crate::auth::session_cookie::create_session_cookie(
80 &self.http,
81 &self.endpoints.create_session_cookie(),
82 id_token,
83 valid_duration,
84 token.as_deref(),
85 self.mode.emulator_api_key(),
86 )
87 .await
88 }
89
90 pub async fn verify_session_cookie(&self, cookie: &str) -> Result<IdTokenClaims, AuthError> {
96 Ok(self.session_cookie_verifier.verify(cookie).await?)
97 }
98
99 async fn bearer_token(&self) -> Result<Option<String>, AuthError> {
109 if !self.mode.requires_bearer_token() {
110 return Ok(None);
111 }
112
113 #[cfg(feature = "live-user-management")]
114 {
115 let provider = self
116 .token_provider
117 .get_or_try_init(|| async {
118 match &self.credentials {
119 Credentials::ServiceAccount(key) => {
120 crate::auth::token_provider::TokenProvider::from_service_account(key)
121 }
122 Credentials::ApplicationDefault => {
123 crate::auth::token_provider::TokenProvider::from_application_default()
124 .await
125 }
126 Credentials::Emulator => unreachable!(
127 "requires_bearer_token() is false in emulator mode; \
128 bearer_token() returns before reaching this branch"
129 ),
130 }
131 })
132 .await?;
133 return provider.access_token().await.map(Some);
134 }
135
136 #[cfg(not(feature = "live-user-management"))]
137 {
138 Err(AuthError::Core(crate::core::CoreError::Credentials(
139 "user management against production Firebase requires the \
140 `live-user-management` feature"
141 .to_string(),
142 )))
143 }
144 }
145
146 fn user_operations<'a>(&'a self, bearer_token: Option<&'a str>) -> UserOperations<'a> {
147 UserOperations::new(
148 &self.http,
149 &self.endpoints,
150 bearer_token,
151 self.mode.emulator_api_key(),
152 )
153 }
154
155 pub async fn get_user(&self, uid: &str) -> Result<UserRecord, AuthError> {
157 let token = self.bearer_token().await?;
158 self.user_operations(token.as_deref()).get_user(uid).await
159 }
160
161 pub async fn get_user_by_email(&self, email: &str) -> Result<UserRecord, AuthError> {
163 let token = self.bearer_token().await?;
164 self.user_operations(token.as_deref())
165 .get_user_by_email(email)
166 .await
167 }
168
169 pub async fn create_user(&self, request: CreateUserRequest) -> Result<UserRecord, AuthError> {
171 let token = self.bearer_token().await?;
172 self.user_operations(token.as_deref())
173 .create_user(request)
174 .await
175 }
176
177 pub async fn update_user(
179 &self,
180 uid: &str,
181 request: UpdateUserRequest,
182 ) -> Result<UserRecord, AuthError> {
183 let token = self.bearer_token().await?;
184 self.user_operations(token.as_deref())
185 .update_user(uid, request)
186 .await
187 }
188
189 pub async fn set_custom_user_claims(
191 &self,
192 uid: &str,
193 claims: serde_json::Map<String, serde_json::Value>,
194 ) -> Result<(), AuthError> {
195 let token = self.bearer_token().await?;
196 self.user_operations(token.as_deref())
197 .set_custom_user_claims(uid, claims)
198 .await
199 }
200
201 pub async fn delete_user(&self, uid: &str) -> Result<(), AuthError> {
203 let token = self.bearer_token().await?;
204 self.user_operations(token.as_deref())
205 .delete_user(uid)
206 .await
207 }
208
209 pub async fn list_users(
211 &self,
212 max_results: u32,
213 page_token: Option<&str>,
214 ) -> Result<UserPage, AuthError> {
215 let token = self.bearer_token().await?;
216 self.user_operations(token.as_deref())
217 .list_users(max_results, page_token)
218 .await
219 }
220}
221
222pub struct AuthClientBuilder {
224 project_id: String,
225 service_account: Option<ServiceAccountKey>,
226 #[cfg(feature = "live-user-management")]
227 use_application_default_credentials: bool,
228 emulator_host: Option<String>,
229 http_client: Option<reqwest::Client>,
230}
231
232impl AuthClientBuilder {
233 pub fn new(project_id: impl Into<String>) -> Self {
235 Self {
236 project_id: project_id.into(),
237 service_account: None,
238 #[cfg(feature = "live-user-management")]
239 use_application_default_credentials: false,
240 emulator_host: None,
241 http_client: None,
242 }
243 }
244
245 pub fn service_account_key(mut self, key: ServiceAccountKey) -> Self {
247 self.service_account = Some(key);
248 self
249 }
250
251 #[cfg(feature = "live-user-management")]
256 pub fn application_default_credentials(mut self) -> Self {
257 self.use_application_default_credentials = true;
258 self
259 }
260
261 #[cfg(feature = "emulator")]
267 pub fn use_emulator(mut self, host: impl Into<String>) -> Self {
268 self.emulator_host = Some(host.into());
269 self
270 }
271
272 pub fn http_client(mut self, client: reqwest::Client) -> Self {
274 self.http_client = Some(client);
275 self
276 }
277
278 pub fn build(self) -> Result<AuthClient, AuthError> {
280 let project_id = ProjectId::new(self.project_id)?;
281 let mode = ClientMode::resolve(self.emulator_host);
282
283 let credentials = if let Some(key) = self.service_account {
284 Credentials::ServiceAccount(Box::new(key))
285 } else {
286 #[cfg(feature = "live-user-management")]
287 if self.use_application_default_credentials {
288 Credentials::ApplicationDefault
289 } else if matches!(mode, ClientMode::Emulator { .. }) {
290 Credentials::Emulator
291 } else {
292 return Err(AuthError::Core(crate::core::CoreError::Credentials(
293 "no credentials configured: call service_account_key(...) or \
294 application_default_credentials()"
295 .to_string(),
296 )));
297 }
298 #[cfg(not(feature = "live-user-management"))]
299 if matches!(mode, ClientMode::Emulator { .. }) {
300 Credentials::Emulator
301 } else {
302 return Err(AuthError::Core(crate::core::CoreError::Credentials(
303 "no credentials configured: call service_account_key(...)".to_string(),
304 )));
305 }
306 };
307
308 let http = HttpClient::new(self.http_client.unwrap_or_default());
309 let endpoints = mode.endpoints();
310 let jwks = JwksCache::new(http.clone());
311 let id_token_verifier = IdTokenVerifier::new(project_id.clone(), jwks);
312 let session_cookie_certs = SessionCookieCertCache::new(http.clone());
313 let session_cookie_verifier =
314 SessionCookieVerifier::new(project_id.clone(), session_cookie_certs);
315
316 Ok(AuthClient {
317 http,
318 project_id,
319 mode,
320 credentials,
321 id_token_verifier,
322 session_cookie_verifier,
323 endpoints,
324 #[cfg(feature = "live-user-management")]
325 token_provider: tokio::sync::OnceCell::new(),
326 })
327 }
328}