1use super::credentials::Credentials;
4
5use serde::{Deserialize, Serialize};
6
7use chrono::{Duration, Utc};
8use std::collections::HashSet;
9use std::ops::Add;
10use std::slice::Iter;
11
12use crate::errors::FirebaseError;
13use biscuit::jwa::SignatureAlgorithm;
14use biscuit::{ClaimPresenceOptions, SingleOrMultiple, ValidationOptions};
15use cache_control::CacheControl;
16use std::ops::Deref;
17
18type Error = super::errors::FirebaseError;
19
20pub static JWT_AUDIENCE_FIRESTORE: &str = "https://firestore.googleapis.com/google.firestore.v1.Firestore";
21pub static JWT_AUDIENCE_IDENTITY: &str =
22 "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit";
23
24#[derive(Debug, Serialize, Deserialize, Clone, Default)]
25pub struct JwtOAuthPrivateClaims {
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub scope: Option<String>,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub client_id: Option<String>,
30 #[serde(skip_serializing_if = "Option::is_none")]
31 pub uid: Option<String>, }
33
34pub(crate) type AuthClaimsJWT = biscuit::JWT<JwtOAuthPrivateClaims, biscuit::Empty>;
35
36#[derive(Serialize, Deserialize, Default, Clone)]
37pub struct JWSEntry {
38 #[serde(flatten)]
39 pub(crate) headers: biscuit::jws::RegisteredHeader,
40 #[serde(flatten)]
41 pub(crate) ne: biscuit::jwk::RSAKeyParameters,
42}
43
44#[derive(Serialize, Deserialize, Default, Clone)]
45pub struct JWKSet {
46 pub keys: Vec<JWSEntry>,
47}
48
49impl JWKSet {
50 pub fn new(jwk_content: &str) -> Result<JWKSet, Error> {
56 let jwk_set: JWKSet = serde_json::from_str(jwk_content).map_err(|e| FirebaseError::Ser {
57 doc: Option::from(format!("Failed to parse jwkset. Return value: {}", jwk_content)),
58 ser: e,
59 })?;
60 Ok(jwk_set)
61 }
62}
63
64pub async fn download_google_jwks(account_mail: &str) -> Result<(String, Option<Duration>), Error> {
69 let url = format!("https://www.googleapis.com/service_accounts/v1/jwk/{}", account_mail);
70 let resp = reqwest::Client::new().get(&url).send().await?;
71 let max_age = resp
72 .headers()
73 .get("cache-control")
74 .and_then(|cache_control| cache_control.to_str().ok())
75 .and_then(|cache_control| CacheControl::from_value(cache_control))
76 .and_then(|cache_control| cache_control.max_age)
77 .and_then(|max_age| Duration::from_std(max_age).ok());
78
79 let text = resp.text().await?;
80 Ok((text, max_age))
81}
82
83pub(crate) async fn create_jwt_encoded<S: AsRef<str>>(
84 credentials: &Credentials,
85 scope: Option<Iter<'_, S>>,
86 duration: chrono::Duration,
87 client_id: Option<String>,
88 user_id: Option<String>,
89 audience: &str,
90) -> Result<String, Error> {
91 let jwt = create_jwt(credentials, scope, duration, client_id, user_id, audience)?;
92 let secret_lock = credentials.keys.read().await;
93 let secret = secret_lock
94 .secret
95 .as_ref()
96 .ok_or(Error::Generic("No private key added via add_keypair_key!"))?;
97 Ok(jwt.encode(&secret.deref())?.encoded()?.encode())
98}
99
100pub(crate) fn is_expired(access_token: &str, tolerance_in_minutes: i64) -> Result<bool, FirebaseError> {
104 let token = AuthClaimsJWT::new_encoded(&access_token);
105 let claims = token.unverified_payload()?;
106 if let Some(expiry) = claims.registered.expiry.as_ref() {
107 let diff: Duration = Utc::now().signed_duration_since(expiry.deref().clone());
108 return Ok(diff.num_minutes() - tolerance_in_minutes > 0);
109 }
110
111 Ok(true)
112}
113
114pub(crate) fn jwt_update_expiry_if(jwt: &mut AuthClaimsJWT, expire_in_minutes: i64) -> bool {
116 let ref mut claims = jwt.payload_mut().unwrap().registered;
117
118 let now = biscuit::Timestamp::from(Utc::now());
119 let now_plus_hour = biscuit::Timestamp::from(Utc::now().add(Duration::hours(1)));
120
121 if let Some(issued_at) = claims.issued_at.as_ref() {
122 let diff: Duration = Utc::now().signed_duration_since(issued_at.deref().clone());
123 if diff.num_minutes() > expire_in_minutes {
124 claims.issued_at = Some(now);
125 claims.expiry = Some(now_plus_hour);
126 } else {
127 return false;
128 }
129 } else {
130 claims.issued_at = Some(now);
131 claims.expiry = Some(now_plus_hour);
132 }
133
134 true
135}
136
137pub(crate) fn create_jwt<S>(
138 credentials: &Credentials,
139 scope: Option<Iter<S>>,
140 duration: chrono::Duration,
141 client_id: Option<String>,
142 user_id: Option<String>,
143 audience: &str,
144) -> Result<AuthClaimsJWT, Error>
145where
146 S: AsRef<str>,
147{
148 use biscuit::{
149 jws::{Header, RegisteredHeader},
150 ClaimsSet, Empty, RegisteredClaims, JWT,
151 };
152
153 let header: Header<Empty> = Header::from(RegisteredHeader {
154 algorithm: SignatureAlgorithm::RS256,
155 key_id: Some(credentials.private_key_id.to_owned()),
156 ..Default::default()
157 });
158 let expected_claims = ClaimsSet::<JwtOAuthPrivateClaims> {
159 registered: RegisteredClaims {
160 issuer: Some(credentials.client_email.clone()),
161 audience: Some(SingleOrMultiple::Single(audience.to_string())),
162 subject: Some(credentials.client_email.clone()),
163 expiry: Some(biscuit::Timestamp::from(Utc::now().add(duration))),
164 issued_at: Some(biscuit::Timestamp::from(Utc::now())),
165 ..Default::default()
166 },
167 private: JwtOAuthPrivateClaims {
168 scope: scope.and_then(|f| {
169 Some(f.fold(String::new(), |acc, x| {
170 let x: &str = x.as_ref();
171 return acc + x + " ";
172 }))
173 }),
174 client_id,
175 uid: user_id,
176 },
177 };
178 Ok(JWT::new_decoded(header, expected_claims))
179}
180
181#[derive(Debug)]
182pub struct TokenValidationResult {
183 pub claims: JwtOAuthPrivateClaims,
184 pub audience: String,
185 pub subject: String,
186}
187
188impl TokenValidationResult {
189 pub fn get_scopes(&self) -> HashSet<String> {
190 match self.claims.scope {
191 Some(ref v) => v.split(" ").map(|f| f.to_owned()).collect(),
192 None => HashSet::new(),
193 }
194 }
195}
196
197pub(crate) async fn verify_access_token(
198 credentials: &Credentials,
199 access_token: &str,
200) -> Result<TokenValidationResult, Error> {
201 let token = AuthClaimsJWT::new_encoded(&access_token);
202
203 let header = token.unverified_header()?;
204 let kid = header
205 .registered
206 .key_id
207 .as_ref()
208 .ok_or(FirebaseError::Generic("No jwt kid"))?;
209 let secret = credentials
210 .decode_secret(kid)
211 .await?
212 .ok_or(FirebaseError::Generic("No secret for kid"))?;
213
214 let token = token.into_decoded(&secret.deref(), SignatureAlgorithm::RS256)?;
215
216 use biscuit::Presence::*;
217
218 let o = ValidationOptions {
219 claim_presence_options: ClaimPresenceOptions {
220 issued_at: Required,
221 not_before: Optional,
222 expiry: Required,
223 issuer: Required,
224 audience: Required,
225 subject: Required,
226 id: Optional,
227 },
228 ..Default::default()
230 };
231
232 let claims = token.payload()?;
233 claims.registered.validate(o)?;
234
235 let audience = match claims.registered.audience.as_ref().unwrap() {
236 SingleOrMultiple::Single(v) => v.to_string(),
237 SingleOrMultiple::Multiple(v) => v.get(0).unwrap().to_string(),
238 };
239
240 Ok(TokenValidationResult {
241 claims: claims.private.clone(),
242 subject: claims.registered.subject.as_ref().unwrap().to_string(),
243 audience,
244 })
245}
246
247pub mod session_cookie {
248 use super::*;
249 use std::ops::Add;
250
251 pub(crate) async fn create_jwt_encoded(
252 credentials: &Credentials,
253 duration: chrono::Duration,
254 ) -> Result<String, Error> {
255 let scope = [
256 "https://www.googleapis.com/auth/cloud-platform",
257 "https://www.googleapis.com/auth/firebase.database",
258 "https://www.googleapis.com/auth/firebase.messaging",
259 "https://www.googleapis.com/auth/identitytoolkit",
260 "https://www.googleapis.com/auth/userinfo.email",
261 ];
262
263 const AUDIENCE: &str = "https://accounts.google.com/o/oauth2/token";
264
265 use biscuit::{
266 jws::{Header, RegisteredHeader},
267 ClaimsSet, Empty, RegisteredClaims, JWT,
268 };
269
270 let header: Header<Empty> = Header::from(RegisteredHeader {
271 algorithm: SignatureAlgorithm::RS256,
272 key_id: Some(credentials.private_key_id.to_owned()),
273 ..Default::default()
274 });
275 let expected_claims = ClaimsSet::<JwtOAuthPrivateClaims> {
276 registered: RegisteredClaims {
277 issuer: Some(credentials.client_email.clone()),
278 audience: Some(SingleOrMultiple::Single(AUDIENCE.to_string())),
279 subject: Some(credentials.client_email.clone()),
280 expiry: Some(biscuit::Timestamp::from(Utc::now().add(duration))),
281 issued_at: Some(biscuit::Timestamp::from(Utc::now())),
282 ..Default::default()
283 },
284 private: JwtOAuthPrivateClaims {
285 scope: Some(scope.join(" ")),
286 client_id: None,
287 uid: None,
288 },
289 };
290 let jwt = JWT::new_decoded(header, expected_claims);
291
292 let secret_lock = credentials.keys.read().await;
293 let secret = secret_lock
294 .secret
295 .as_ref()
296 .ok_or(Error::Generic("No private key added via add_keypair_key!"))?;
297 Ok(jwt.encode(&secret.deref())?.encoded()?.encode())
298 }
299}