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> {
115 if !self.mode.requires_bearer_token() {
116 return Ok(self.mode.emulator_bearer_token().map(str::to_string));
117 }
118
119 #[cfg(feature = "live-user-management")]
120 {
121 let provider = self
122 .token_provider
123 .get_or_try_init(|| async {
124 match &self.credentials {
125 Credentials::ServiceAccount(key) => {
126 crate::auth::token_provider::TokenProvider::from_service_account(key)
127 }
128 Credentials::ApplicationDefault => {
129 crate::auth::token_provider::TokenProvider::from_application_default()
130 .await
131 }
132 Credentials::Emulator => unreachable!(
133 "requires_bearer_token() is false in emulator mode; \
134 bearer_token() returns before reaching this branch"
135 ),
136 }
137 })
138 .await?;
139 return provider.access_token().await.map(Some);
140 }
141
142 #[cfg(not(feature = "live-user-management"))]
143 {
144 Err(AuthError::Core(crate::core::CoreError::Credentials(
145 "user management against production Firebase requires the \
146 `live-user-management` feature"
147 .to_string(),
148 )))
149 }
150 }
151
152 fn user_operations<'a>(&'a self, bearer_token: Option<&'a str>) -> UserOperations<'a> {
153 UserOperations::new(
154 &self.http,
155 &self.endpoints,
156 bearer_token,
157 self.mode.emulator_api_key(),
158 )
159 }
160
161 pub async fn get_user(&self, uid: &str) -> Result<UserRecord, AuthError> {
163 let token = self.bearer_token().await?;
164 self.user_operations(token.as_deref()).get_user(uid).await
165 }
166
167 pub async fn get_user_by_email(&self, email: &str) -> Result<UserRecord, AuthError> {
169 let token = self.bearer_token().await?;
170 self.user_operations(token.as_deref())
171 .get_user_by_email(email)
172 .await
173 }
174
175 pub async fn create_user(&self, request: CreateUserRequest) -> Result<UserRecord, AuthError> {
177 let token = self.bearer_token().await?;
178 self.user_operations(token.as_deref())
179 .create_user(request)
180 .await
181 }
182
183 pub async fn update_user(
185 &self,
186 uid: &str,
187 request: UpdateUserRequest,
188 ) -> Result<UserRecord, AuthError> {
189 let token = self.bearer_token().await?;
190 self.user_operations(token.as_deref())
191 .update_user(uid, request)
192 .await
193 }
194
195 pub async fn set_custom_user_claims(
197 &self,
198 uid: &str,
199 claims: serde_json::Map<String, serde_json::Value>,
200 ) -> Result<(), AuthError> {
201 let token = self.bearer_token().await?;
202 self.user_operations(token.as_deref())
203 .set_custom_user_claims(uid, claims)
204 .await
205 }
206
207 pub async fn delete_user(&self, uid: &str) -> Result<(), AuthError> {
209 let token = self.bearer_token().await?;
210 self.user_operations(token.as_deref())
211 .delete_user(uid)
212 .await
213 }
214
215 pub async fn list_users(
217 &self,
218 max_results: u32,
219 page_token: Option<&str>,
220 ) -> Result<UserPage, AuthError> {
221 let token = self.bearer_token().await?;
222 self.user_operations(token.as_deref())
223 .list_users(max_results, page_token)
224 .await
225 }
226}
227
228pub struct AuthClientBuilder {
230 project_id: String,
231 service_account: Option<ServiceAccountKey>,
232 #[cfg(feature = "live-user-management")]
233 use_application_default_credentials: bool,
234 emulator_host: Option<String>,
235 http_client: Option<reqwest::Client>,
236}
237
238impl AuthClientBuilder {
239 pub fn new(project_id: impl Into<String>) -> Self {
241 Self {
242 project_id: project_id.into(),
243 service_account: None,
244 #[cfg(feature = "live-user-management")]
245 use_application_default_credentials: false,
246 emulator_host: None,
247 http_client: None,
248 }
249 }
250
251 pub fn service_account_key(mut self, key: ServiceAccountKey) -> Self {
253 self.service_account = Some(key);
254 self
255 }
256
257 #[cfg(feature = "live-user-management")]
262 pub fn application_default_credentials(mut self) -> Self {
263 self.use_application_default_credentials = true;
264 self
265 }
266
267 #[cfg(feature = "emulator")]
273 pub fn use_emulator(mut self, host: impl Into<String>) -> Self {
274 self.emulator_host = Some(host.into());
275 self
276 }
277
278 pub fn http_client(mut self, client: reqwest::Client) -> Self {
280 self.http_client = Some(client);
281 self
282 }
283
284 pub fn build(self) -> Result<AuthClient, AuthError> {
286 let project_id = ProjectId::new(self.project_id)?;
287 let mode = ClientMode::resolve(self.emulator_host);
288
289 let credentials = if let Some(key) = self.service_account {
290 Credentials::ServiceAccount(Box::new(key))
291 } else {
292 #[cfg(feature = "live-user-management")]
293 if self.use_application_default_credentials {
294 Credentials::ApplicationDefault
295 } else if matches!(mode, ClientMode::Emulator { .. }) {
296 Credentials::Emulator
297 } else {
298 return Err(AuthError::Core(crate::core::CoreError::Credentials(
299 "no credentials configured: call service_account_key(...) or \
300 application_default_credentials()"
301 .to_string(),
302 )));
303 }
304 #[cfg(not(feature = "live-user-management"))]
305 if matches!(mode, ClientMode::Emulator { .. }) {
306 Credentials::Emulator
307 } else {
308 return Err(AuthError::Core(crate::core::CoreError::Credentials(
309 "no credentials configured: call service_account_key(...)".to_string(),
310 )));
311 }
312 };
313
314 let http = HttpClient::new(self.http_client.unwrap_or_default());
315 let endpoints = mode.endpoints();
316 let jwks = JwksCache::new(http.clone());
317 let id_token_verifier = IdTokenVerifier::new(project_id.clone(), jwks);
318 let session_cookie_certs = SessionCookieCertCache::new(http.clone());
319 let session_cookie_verifier =
320 SessionCookieVerifier::new(project_id.clone(), session_cookie_certs);
321
322 Ok(AuthClient {
323 http,
324 project_id,
325 mode,
326 credentials,
327 id_token_verifier,
328 session_cookie_verifier,
329 endpoints,
330 #[cfg(feature = "live-user-management")]
331 token_provider: tokio::sync::OnceCell::new(),
332 })
333 }
334}