firebase_rs_sdk/app_check/
token_provider.rs1use std::sync::atomic::{AtomicBool, Ordering};
2
3use crate::app_check::errors::AppCheckError;
4use crate::app_check::FirebaseAppCheckInternal;
5use crate::firestore::error::{
6 internal_error, invalid_argument, unauthenticated, unavailable, FirestoreError, FirestoreResult,
7};
8use crate::firestore::remote::datastore::{TokenProvider, TokenProviderArc};
9
10pub struct AppCheckTokenProvider {
12 app_check: FirebaseAppCheckInternal,
13 force_refresh: AtomicBool,
14}
15
16impl AppCheckTokenProvider {
17 pub fn new(app_check: FirebaseAppCheckInternal) -> Self {
19 Self {
20 app_check,
21 force_refresh: AtomicBool::new(false),
22 }
23 }
24
25 pub fn into_arc(self) -> TokenProviderArc {
27 std::sync::Arc::new(self)
28 }
29}
30
31pub fn app_check_token_provider_arc(app_check: FirebaseAppCheckInternal) -> TokenProviderArc {
33 AppCheckTokenProvider::new(app_check).into_arc()
34}
35
36impl Clone for AppCheckTokenProvider {
37 fn clone(&self) -> Self {
38 Self {
39 app_check: self.app_check.clone(),
40 force_refresh: AtomicBool::new(self.force_refresh.load(Ordering::SeqCst)),
41 }
42 }
43}
44
45impl TokenProvider for AppCheckTokenProvider {
46 fn get_token(&self) -> FirestoreResult<Option<String>> {
47 let force_refresh = self.force_refresh.swap(false, Ordering::SeqCst);
48 let result = self
49 .app_check
50 .get_token(force_refresh)
51 .map_err(map_app_check_error)?;
52
53 if let Some(error) = result.error {
54 return Err(map_app_check_error(error));
55 }
56
57 if let Some(error) = result.internal_error {
58 return Err(internal_error(error.to_string()));
59 }
60
61 if result.token.is_empty() {
62 Ok(None)
63 } else {
64 Ok(Some(result.token))
65 }
66 }
67
68 fn invalidate_token(&self) {
69 self.force_refresh.store(true, Ordering::SeqCst);
70 }
71}
72
73fn map_app_check_error(error: AppCheckError) -> FirestoreError {
74 match error {
75 AppCheckError::AlreadyInitialized { .. }
76 | AppCheckError::UseBeforeActivation { .. }
77 | AppCheckError::InvalidConfiguration { .. } => invalid_argument(error.to_string()),
78 AppCheckError::TokenFetchFailed { .. } | AppCheckError::ProviderError { .. } => {
79 unavailable(error.to_string())
80 }
81 AppCheckError::TokenExpired => unauthenticated(error.to_string()),
82 AppCheckError::Internal(message) => internal_error(message),
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use crate::app::{FirebaseApp, FirebaseAppConfig, FirebaseOptions};
90 use crate::app_check::api::{initialize_app_check, token_with_ttl};
91 use crate::app_check::types::{AppCheckOptions, AppCheckProvider, AppCheckToken};
92 use crate::component::ComponentContainer;
93 use std::sync::Arc;
94 use std::time::Duration;
95
96 #[derive(Clone)]
97 struct StaticTokenProvider {
98 token: String,
99 }
100
101 impl AppCheckProvider for StaticTokenProvider {
102 fn get_token(&self) -> crate::app_check::AppCheckResult<AppCheckToken> {
103 token_with_ttl(self.token.clone(), Duration::from_secs(60))
104 }
105 }
106
107 #[derive(Clone)]
108 struct ErrorProvider;
109
110 impl AppCheckProvider for ErrorProvider {
111 fn get_token(&self) -> crate::app_check::AppCheckResult<AppCheckToken> {
112 Err(AppCheckError::TokenFetchFailed {
113 message: "network".into(),
114 })
115 }
116 }
117
118 fn test_app(name: &str) -> FirebaseApp {
119 FirebaseApp::new(
120 FirebaseOptions::default(),
121 FirebaseAppConfig::new(name.to_owned(), false),
122 ComponentContainer::new(name.to_owned()),
123 )
124 }
125
126 #[test]
127 fn returns_token_string() {
128 let provider = Arc::new(StaticTokenProvider {
129 token: "app-check-123".into(),
130 });
131 let options = AppCheckOptions::new(provider);
132 let app_check = initialize_app_check(Some(test_app("app-check-ok")), options).unwrap();
133 let internal = FirebaseAppCheckInternal::new(app_check);
134 let provider = AppCheckTokenProvider::new(internal);
135
136 let token = provider.get_token().unwrap();
137 assert_eq!(token.as_deref(), Some("app-check-123"));
138 }
139
140 #[test]
141 fn propagates_errors() {
142 let provider = Arc::new(ErrorProvider);
143 let options = AppCheckOptions::new(provider);
144 let app_check = initialize_app_check(Some(test_app("app-check-err")), options).unwrap();
145 let internal = FirebaseAppCheckInternal::new(app_check);
146 let provider = AppCheckTokenProvider::new(internal);
147
148 let error = provider.get_token().unwrap_err();
149 assert_eq!(
150 error.code,
151 crate::firestore::error::FirestoreErrorCode::Unavailable
152 );
153 }
154}