use anyhow::Context;
use jsonwebtoken::{Algorithm, DecodingKey, Validation, get_current_timestamp};
use serde::{Serialize, de::DeserializeOwned};
use super::public_keys::PublicKeys;
use crate::ServiceAccount;
const FIREBASE_AUDIENCE: &str =
"https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit";
pub struct UserTokenManager {
public_keys: PublicKeys,
service_account: ServiceAccount,
}
impl UserTokenManager {
pub fn new(service_account: ServiceAccount, http_client: reqwest::Client) -> Self {
Self {
public_keys: PublicKeys::new(http_client),
service_account,
}
}
pub async fn decode_id_token<C: DeserializeOwned>(
&self,
token: &str,
) -> Result<C, anyhow::Error> {
let header = jsonwebtoken::decode_header(token)?;
if header.alg != jsonwebtoken::Algorithm::RS256 {
anyhow::bail!("Invalid ID token JWT algorithm");
}
let public_key_id = header
.kid
.context("ID token is missing public key ID in header")?;
let public_key = self
.public_keys
.get(&public_key_id)
.await?
.context("Unrecognized public key in header of ID token")?;
let mut validation = Validation::new(jsonwebtoken::Algorithm::RS256);
validation.set_audience(&[&self.service_account.project_id]);
validation.set_issuer(&[&format!(
"https://securetoken.google.com/{}",
&self.service_account.project_id
)]);
let decoded = jsonwebtoken::decode(
token,
&DecodingKey::from_rsa_pem(public_key.as_ref())
.context("Invalid public key format in ID token")?,
&validation,
)?;
Ok(decoded.claims)
}
pub async fn create_custom_token(&self, uid: &str) -> Result<String, anyhow::Error> {
#[derive(Serialize)]
struct CustomTokenClaims<'a> {
aud: &'a str,
iat: u64,
exp: u64,
iss: &'a str,
sub: &'a str,
uid: &'a str,
}
let header = jsonwebtoken::Header::new(Algorithm::RS256);
let issued_at_time = get_current_timestamp();
let expires_at = issued_at_time + (60 * 60);
let claims = CustomTokenClaims {
iss: &self.service_account.client_email,
sub: &self.service_account.client_email,
aud: FIREBASE_AUDIENCE,
iat: issued_at_time,
exp: expires_at,
uid,
};
let encoding_key =
jsonwebtoken::EncodingKey::from_rsa_pem(self.service_account.private_key.as_bytes())
.context("Failed to create JWT encoding key from the given private key")?;
let jwt = jsonwebtoken::encode(&header, &claims, &encoding_key)
.context("Failed to create custom token JWT")?;
Ok(jwt)
}
}