1use std::{env, error::Error};
2
3use jsonwebtoken::{decode, errors::ErrorKind, Algorithm, DecodingKey, TokenData, Validation};
4use reqwest::Url;
5use serde::{Deserialize, Serialize};
6use serde_json::{Map, Value};
7
8#[derive(Debug, Serialize, Deserialize)]
9pub struct FirebaseClaims {
10 sign_in_provider: String,
11}
12#[derive(Debug, Serialize, Deserialize)]
13pub struct Claims {
14 aud: String,
15 sub: String,
16 name: String,
17 email: String,
18 email_verified: bool,
19 user_id: String,
20 picture: String,
21 firebase: FirebaseClaims,
22 exp: u64,
23 iat: u64,
24}
25
26async fn get_pbkey() -> Result<Map<String, Value>, Box<dyn Error>> {
27 let url = format!(
28 "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com",
29 );
30 let url = Url::parse(&*url)?;
31 let res = reqwest::get(url).await?.json().await?;
32 Ok(res)
33}
34
35pub async fn verify_token(
65 uid: &str,
66 client_token: &str,
67) -> Result<TokenData<Claims>, Box<dyn Error>> {
68 let res = get_pbkey().await?;
69 let ky = res.keys().nth(1).unwrap(); let ky = res[ky].as_str().unwrap();
71 let pub_key = ky.as_bytes();
72
73 let mut validation = Validation::new(Algorithm::RS256);
74
75 let fb_project_id = env::var("FIREBASE_PROJECT_ID")?;
76 let issuer_str = format!("https://securetoken.google.com/{fb_project_id}");
77 validation.sub = Some(uid.to_string());
78 validation.set_audience(&[fb_project_id]);
79 validation.set_issuer(&[issuer_str]);
80
81 let token_data: Result<TokenData<Claims>, Box<dyn Error>> = match decode::<Claims>(
82 &client_token,
83 &DecodingKey::from_rsa_pem(pub_key).unwrap(),
84 &validation,
85 ) {
86 Ok(c) => Ok(c),
87 Err(err) => match *err.kind() {
88 ErrorKind::InvalidToken => Err("invalid-token")?,
89 ErrorKind::InvalidIssuer => Err("invalid-issuer")?,
90 ErrorKind::InvalidSubject => Err("invalid-subject")?,
91 ErrorKind::InvalidAlgorithm => Err("invalid-alg")?,
92 ErrorKind::InvalidAudience => Err("invalid-aud")?,
93 ErrorKind::InvalidEcdsaKey => Err("invalid-ecdsa-key")?,
94 ErrorKind::InvalidAlgorithmName => Err("invalid-alg-name")?,
95 ErrorKind::InvalidKeyFormat => Err("invalid-key-format")?,
96 ErrorKind::ExpiredSignature => Err("expired-signature")?,
97 ErrorKind::ImmatureSignature => Err("immature-signature")?,
98 ErrorKind::RsaFailedSigning => Err("rsa-failed-signing")?,
99 _ => Err("other-errors")?,
100 },
101 };
102
103 return token_data;
104}
105
106#[cfg(test)]
107mod tests {
108 use std::error::Error;
109
110 use jsonwebtoken::TokenData;
111
112 use crate::{verify_token, Claims};
113
114 #[tokio::test]
115 async fn e2e_fetch() {
116 let uid = "";
117 let client_token = "";
118 let result: Result<TokenData<Claims>, Box<dyn Error>> =
119 verify_token(uid, client_token).await;
120 let mut success = false;
121 match result {
122 Ok(res) => {
123 let text: String = serde_json::to_string(&res.claims).unwrap();
124 success = true;
125 println!("result:{text}");
126 }
127 Err(e) => {
128 println!("err:{e}");
129 }
130 }
131 assert_eq!(success, false)
132 }
133}