use super::credentials;
use super::errors::{extract_google_api_error, FirebaseError};
use super::jwt::{
create_jwt, is_expired, jwt_update_expiry_if, verify_access_token, AuthClaimsJWT, JWT_AUDIENCE_FIRESTORE,
JWT_AUDIENCE_IDENTITY,
};
use super::FirebaseAuthBearer;
use chrono::Duration;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::ops::Deref;
use std::slice::Iter;
pub mod user {
use super::*;
use credentials::Credentials;
#[inline]
fn token_endpoint(v: &str) -> String {
format!(
"https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key={}",
v
)
}
#[inline]
fn refresh_to_access_endpoint(v: &str) -> String {
format!("https://securetoken.googleapis.com/v1/token?key={}", v)
}
pub struct Session {
pub user_id: String,
pub refresh_token: Option<String>,
pub api_key: String,
access_token_: RefCell<String>,
project_id_: String,
pub client: reqwest::Client,
}
impl super::FirebaseAuthBearer for Session {
fn project_id(&self) -> &str {
&self.project_id_
}
fn access_token(&self) -> String {
let jwt = self.access_token_.borrow();
let jwt = jwt.as_str();
if is_expired(&jwt, 0).unwrap() {
if let Ok(response) = get_new_access_token(&self.api_key, jwt) {
self.access_token_.swap(&RefCell::new(response.id_token.clone()));
return response.id_token;
} else {
return String::new();
}
}
jwt.to_owned()
}
fn access_token_unchecked(&self) -> String {
self.access_token_.borrow().clone()
}
fn client(&self) -> &Client {
&self.client
}
}
fn get_new_access_token(
api_key: &str,
refresh_token: &str,
) -> Result<RefreshTokenToAccessTokenResponse, FirebaseError> {
let request_body = vec![("grant_type", "refresh_token"), ("refresh_token", refresh_token)];
let url = refresh_to_access_endpoint(api_key);
let client = Client::new();
let ref mut response = client.post(&url).form(&request_body).send()?;
Ok(response.json()?)
}
#[allow(non_snake_case)]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
struct CustomJwtToFirebaseID {
token: String,
returnSecureToken: bool,
}
impl CustomJwtToFirebaseID {
fn new(token: String, with_refresh_token: bool) -> Self {
CustomJwtToFirebaseID {
token,
returnSecureToken: with_refresh_token,
}
}
}
#[allow(non_snake_case)]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
struct CustomJwtToFirebaseIDResponse {
kind: Option<String>,
idToken: String,
refreshToken: Option<String>,
expiresIn: Option<String>,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
struct RefreshTokenToAccessTokenResponse {
expires_in: String,
token_type: String,
refresh_token: String,
id_token: String,
user_id: String,
project_id: String,
}
impl Session {
pub fn new(
credentials: &Credentials,
user_id: Option<&str>,
firebase_tokenid: Option<&str>,
refresh_token: Option<&str>,
) -> Result<Session, FirebaseError> {
if let Some(firebase_tokenid) = firebase_tokenid {
let r = Session::by_access_token(credentials, firebase_tokenid);
if r.is_ok() {
let mut r = r.unwrap();
r.refresh_token = refresh_token.and_then(|f| Some(f.to_owned()));
return Ok(r);
}
}
if let Some(refresh_token) = refresh_token {
let r = Session::by_refresh_token(credentials, refresh_token);
if r.is_ok() {
return r;
}
}
if let Some(user_id) = user_id {
let r = Session::by_user_id(credentials, user_id, true);
if r.is_ok() {
return r;
}
}
Err(FirebaseError::Generic("No parameter given"))
}
pub fn by_refresh_token(credentials: &Credentials, refresh_token: &str) -> Result<Session, FirebaseError> {
let r: RefreshTokenToAccessTokenResponse = get_new_access_token(&credentials.api_key, refresh_token)?;
Ok(Session {
user_id: r.user_id,
access_token_: RefCell::new(r.id_token),
refresh_token: Some(r.refresh_token),
project_id_: credentials.project_id.to_owned(),
api_key: credentials.api_key.clone(),
client: reqwest::Client::new(),
})
}
pub fn by_user_id(
credentials: &Credentials,
user_id: &str,
with_refresh_token: bool,
) -> Result<Session, FirebaseError> {
let scope: Option<Iter<String>> = None;
let jwt = create_jwt(
&credentials,
scope,
Duration::hours(1),
None,
Some(user_id.to_owned()),
JWT_AUDIENCE_IDENTITY,
)?;
let secret = credentials
.keys
.secret
.as_ref()
.ok_or(FirebaseError::Generic("No private key added via add_keypair_key!"))?;
let encoded = jwt.encode(&secret.deref())?.encoded()?.encode();
let mut r = Client::new()
.post(&token_endpoint(&credentials.api_key))
.json(&CustomJwtToFirebaseID::new(encoded, with_refresh_token))
.send()?;
extract_google_api_error(&mut r, || user_id.to_owned())?;
let r: CustomJwtToFirebaseIDResponse = r.json()?;
Ok(Session {
user_id: user_id.to_owned(),
access_token_: RefCell::new(r.idToken),
refresh_token: r.refreshToken,
project_id_: credentials.project_id.to_owned(),
api_key: credentials.api_key.clone(),
client: reqwest::Client::new(),
})
}
pub fn by_access_token(credentials: &Credentials, firebase_tokenid: &str) -> Result<Session, FirebaseError> {
let result = verify_access_token(&credentials, firebase_tokenid)?
.ok_or(FirebaseError::Generic("Validation failed"))?;
Ok(Session {
user_id: result.subject,
project_id_: result.audience,
access_token_: RefCell::new(firebase_tokenid.to_owned()),
refresh_token: None,
api_key: credentials.api_key.clone(),
client: reqwest::Client::new(),
})
}
}
}
pub mod service_account {
use super::*;
use credentials::Credentials;
use chrono::Duration;
use reqwest::Client;
use std::cell::RefCell;
use std::ops::Deref;
pub struct Session {
pub credentials: Credentials,
pub client: reqwest::Client,
jwt: RefCell<AuthClaimsJWT>,
access_token_: RefCell<String>,
}
impl super::FirebaseAuthBearer for Session {
fn project_id(&self) -> &str {
&self.credentials.project_id
}
fn access_token(&self) -> String {
let mut jwt = self.jwt.borrow_mut();
if jwt_update_expiry_if(&mut jwt, 50) {
if let Some(secret) = self.credentials.keys.secret.as_ref() {
if let Ok(v) = self.jwt.borrow().encode(&secret.deref()) {
if let Ok(v2) = v.encoded() {
self.access_token_.swap(&RefCell::new(v2.encode()));
}
}
}
}
self.access_token_.borrow().clone()
}
fn access_token_unchecked(&self) -> String {
self.access_token_.borrow().clone()
}
fn client(&self) -> &Client {
&self.client
}
}
impl Session {
pub fn new(credentials: Credentials) -> Result<Session, FirebaseError> {
let scope: Option<Iter<String>> = None;
let jwt = create_jwt(
&credentials,
scope,
Duration::hours(1),
None,
None,
JWT_AUDIENCE_FIRESTORE,
)?;
let secret = credentials
.keys
.secret
.as_ref()
.ok_or(FirebaseError::Generic("No private key added via add_keypair_key!"))?;
let encoded = jwt.encode(&secret.deref())?.encoded()?.encode();
Ok(Session {
access_token_: RefCell::new(encoded),
jwt: RefCell::new(jwt),
credentials,
client: reqwest::Client::new(),
})
}
}
}