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