diff --git a/examples/create_read_write_document.rs b/examples/create_read_write_document.rs index f2afb4f..dbcdab6 100644 --- a/examples/create_read_write_document.rs +++ b/examples/create_read_write_document.rs @@ -32,13 +32,7 @@ async fn write_document(session: &mut ServiceSession, doc_id: &str) -> errors::R a_timestamp: chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Nanos, true), }; - documents::write( - session, - "tests", - Some(doc_id), - &obj, - documents::WriteOptions::default() - ).await + documents::write(session, "tests", Some(doc_id), &obj, documents::WriteOptions::default()).await } async fn write_partial_document(session: &mut ServiceSession, doc_id: &str) -> errors::Result { @@ -55,7 +49,8 @@ async fn write_partial_document(session: &mut ServiceSession, doc_id: &str) -> e Some(doc_id), &obj, documents::WriteOptions { merge: true }, - ).await + ) + .await } fn check_write(result: WriteResult, doc_id: &str) { @@ -105,10 +100,8 @@ async fn user_account_session(cred: Credentials) -> errors::Result<()> { assert_eq!(user_session.project_id(), cred.project_id); println!("user::Session::by_access_token"); - let user_session = sessions::user::Session::by_access_token( - &cred, - &user_session.access_token_unchecked().await - ).await?; + let user_session = + sessions::user::Session::by_access_token(&cred, &user_session.access_token_unchecked().await).await?; assert_eq!(user_session.user_id, utils::TEST_USER_ID); @@ -128,7 +121,8 @@ async fn user_account_session(cred: Credentials) -> errors::Result<()> { Some(doc_id), &obj, documents::WriteOptions::default(), - ).await?, + ) + .await?, doc_id, ); @@ -146,7 +140,8 @@ async fn user_account_session(cred: Credentials) -> errors::Result<()> { "abc".into(), dto::FieldOperator::EQUAL, "a_string", - ).await? + ) + .await? .collect(); assert_eq!(results.len(), 1); let doc: DemoDTO = documents::read_by_name(&user_session, &results.get(0).unwrap().name).await?; @@ -192,11 +187,9 @@ async fn user_account_session(cred: Credentials) -> errors::Result<()> { println!("user::Session documents::query for f64"); let f: f64 = 13.37; - let count = documents::query(&user_session, "tests", f.into(), dto::FieldOperator::EQUAL, "a_float") - .await?; + let count = documents::query(&user_session, "tests", f.into(), dto::FieldOperator::EQUAL, "a_float").await?; - let count = count - .count(); + let count = count.count(); assert_eq!(count, 0); Ok(()) @@ -211,10 +204,7 @@ async fn main() -> errors::Result<()> { let cred = Credentials::from_file(credential_file.to_str().unwrap()).await?; // Only download the public keys once, and cache them. - let jwkset = utils::from_cache_file( - credential_file.with_file_name("cached_jwks.jwks").as_path(), - &cred - ).await?; + let jwkset = utils::from_cache_file(credential_file.with_file_name("cached_jwks.jwks").as_path(), &cred).await?; cred.add_jwks_public_keys(&jwkset).await; cred.verify().await?; diff --git a/examples/firebase_user.rs b/examples/firebase_user.rs index f2e214d..4ca6ebd 100644 --- a/examples/firebase_user.rs +++ b/examples/firebase_user.rs @@ -5,13 +5,11 @@ const TEST_USER_ID: &str = include_str!("test_user_id.txt"); #[tokio::main] async fn main() -> errors::Result<()> { - let cred = Credentials::from_file("firebase-service-account.json").await.expect("Read credentials file"); + let cred = Credentials::from_file("firebase-service-account.json") + .await + .expect("Read credentials file"); - let user_session = UserSession::by_user_id( - &cred, - TEST_USER_ID, - false - ).await?; + let user_session = UserSession::by_user_id(&cred, TEST_USER_ID, false).await?; println!("users::user_info"); let user_info_container = users::user_info(&user_session).await?; diff --git a/examples/own_auth.rs b/examples/own_auth.rs index a1ad4ee..f80b7fc 100644 --- a/examples/own_auth.rs +++ b/examples/own_auth.rs @@ -1,5 +1,5 @@ -use firestore_db_and_auth::{documents, errors, Credentials, FirebaseAuthBearer}; use firestore_db_and_auth::errors::FirebaseError::APIError; +use firestore_db_and_auth::{documents, errors, Credentials, FirebaseAuthBearer}; /// Define your own structure that will implement the FirebaseAuthBearer trait struct MyOwnSession { @@ -51,7 +51,8 @@ async fn run() -> errors::Result<()> { Some("test_doc"), &t, documents::WriteOptions::default(), - ).await?; + ) + .await?; Ok(()) } diff --git a/examples/session_cookie.rs b/examples/session_cookie.rs index c9de3d8..c6bbc34 100644 --- a/examples/session_cookie.rs +++ b/examples/session_cookie.rs @@ -14,19 +14,13 @@ async fn main() -> Result<(), FirebaseError> { let cred = Credentials::from_file(credential_file.to_str().unwrap()).await?; // Only download the public keys once, and cache them. - let jwkset = utils::from_cache_file( - credential_file.with_file_name("cached_jwks.jwks").as_path(), &cred - ).await?; + let jwkset = utils::from_cache_file(credential_file.with_file_name("cached_jwks.jwks").as_path(), &cred).await?; cred.add_jwks_public_keys(&jwkset).await; cred.verify().await?; let user_session = utils::user_session_with_cached_refresh_token(&cred).await?; - let cookie = session_cookie::create( - &cred, - user_session.access_token().await, - Duration::seconds(3600) - ).await?; + let cookie = session_cookie::create(&cred, user_session.access_token().await, Duration::seconds(3600)).await?; println!("Created session cookie: {}", cookie); Ok(()) diff --git a/examples/utils/mod.rs b/examples/utils/mod.rs index 0fe7db4..4466253 100644 --- a/examples/utils/mod.rs +++ b/examples/utils/mod.rs @@ -21,11 +21,7 @@ pub async fn user_session_with_cached_refresh_token(cred: &Credentials) -> error // Generate a new refresh token if necessary println!("Generate new user auth token"); let user_session: sessions::user::Session = if refresh_token.is_empty() { - let session = sessions::user::Session::by_user_id( - &cred, - TEST_USER_ID, - true - ).await?; + let session = sessions::user::Session::by_user_id(&cred, TEST_USER_ID, true).await?; std::fs::write("refresh-token-for-tests.txt", &session.refresh_token.as_ref().unwrap())?; session } else { diff --git a/src/credentials.rs b/src/credentials.rs index 736fe3e..6e2edfe 100644 --- a/src/credentials.rs +++ b/src/credentials.rs @@ -2,19 +2,19 @@ //! This module contains the [`crate::credentials::Credentials`] type, used by [`crate::sessions`] to create and maintain //! authentication tokens for accessing the Firebase REST API. -use chrono::{Duration, DateTime, offset}; +use chrono::{offset, DateTime, Duration}; use serde::{Deserialize, Serialize}; use serde_json; use std::collections::BTreeMap; -use std::fs::File; -use std::sync::{Arc}; use std::fmt; +use std::fs::File; +use std::sync::Arc; use super::jwt::{create_jwt_encoded, download_google_jwks, verify_access_token, JWKSet, JWT_AUDIENCE_IDENTITY}; use crate::{errors::FirebaseError, jwt::TokenValidationResult}; -use std::io::BufReader; -use base64::Engine; use base64::prelude::BASE64_STANDARD; +use base64::Engine; +use std::io::BufReader; use tokio::sync::RwLock; type Error = super::errors::FirebaseError; @@ -30,10 +30,10 @@ pub(crate) struct Keys { impl fmt::Debug for Keys { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Keys") - .field("pub_key_expires_at", &self.pub_key_expires_at) - .field("pub_key", &self.pub_key.keys().collect::>()) - .field("secret", &self.secret.is_some()) - .finish() + .field("pub_key_expires_at", &self.pub_key_expires_at) + .field("pub_key", &self.pub_key.keys().collect::>()) + .field("secret", &self.secret.is_some()) + .finish() } } @@ -80,7 +80,8 @@ pub fn pem_to_der(pem_file_contents: &str) -> Result, Error> { } let base64_body = pem_file_contents.unwrap().replace("\n", ""); - Ok(BASE64_STANDARD.decode(&base64_body) + Ok(BASE64_STANDARD + .decode(&base64_body) .map_err(|_| FirebaseError::Generic("Invalid private key in credentials file. Expected Base64 data."))?) } @@ -177,7 +178,6 @@ impl Credentials { Ok(self) } - /// Verifies that creating access tokens is possible with the given credentials and public keys. /// Returns an empty result type on success. pub async fn verify(&self) -> Result<(), Error> { @@ -188,7 +188,8 @@ impl Credentials { Some(self.client_id.clone()), None, JWT_AUDIENCE_IDENTITY, - ).await?; + ) + .await?; verify_access_token(&self, &access_token).await?; Ok(()) } @@ -202,9 +203,9 @@ impl Credentials { pub async fn decode_secret(&self, kid: &str) -> Result>, Error> { let should_refresh = { let keys = self.keys.read().await; - keys.pub_key_expires_at.map(|expires_at| { - expires_at - offset::Utc::now() < Duration::minutes(10) - }).unwrap_or(false) + keys.pub_key_expires_at + .map(|expires_at| expires_at - offset::Utc::now() < Duration::minutes(10)) + .unwrap_or(false) }; if should_refresh { diff --git a/src/documents/list.rs b/src/documents/list.rs index c37c5dc..2d5e1c0 100644 --- a/src/documents/list.rs +++ b/src/documents/list.rs @@ -1,15 +1,12 @@ use super::*; +use bytes::Bytes; +use core::pin::Pin; use futures::{ - Future, stream::{self, Stream}, - task::{ - Context, - Poll, - }, + task::{Context, Poll}, + Future, }; -use core::pin::Pin; use std::boxed::Box; -use bytes::Bytes; /// List all documents of a given collection. /// @@ -44,7 +41,10 @@ use bytes::Bytes; /// ## Arguments /// * 'auth' The authentication token /// * 'collection_id' The document path / collection; For example "my_collection" or "a/nested/collection" -pub fn list(auth: &AUTH, collection_id: impl Into) -> Pin> + Send>> +pub fn list( + auth: &AUTH, + collection_id: impl Into, +) -> Pin> + Send>> where for<'b> T: Deserialize<'b> + 'static, AUTH: FirebaseAuthBearer + Clone + Send + Sync + 'static, @@ -52,65 +52,71 @@ where let auth = auth.clone(); let collection_id = collection_id.into(); - Box::pin(stream::unfold(ListInner { - url: firebase_url(auth.project_id(), &collection_id), - auth, - next_page_token: None, - documents: vec![], - current: 0, - done: false, - collection_id: collection_id.to_string(), - }, |this| async move { - let mut this = this.clone(); - if this.done { - return None; - } + Box::pin(stream::unfold( + ListInner { + url: firebase_url(auth.project_id(), &collection_id), + auth, + next_page_token: None, + documents: vec![], + current: 0, + done: false, + collection_id: collection_id.to_string(), + }, + |this| async move { + let mut this = this.clone(); + if this.done { + return None; + } - if this.documents.len() <= this.current { - let url = match &this.next_page_token { - Some(next_page_token) => format!("{}pageToken={}", this.url, next_page_token), - None => this.url.clone(), - }; + if this.documents.len() <= this.current { + let url = match &this.next_page_token { + Some(next_page_token) => format!("{}pageToken={}", this.url, next_page_token), + None => this.url.clone(), + }; - let result = get_new_data(&this.collection_id, &url, &this.auth).await; - match result { - Err(e) => { - this.done = true; - return Some((Err(e), this)); - } - Ok(v) => match v.documents { - None => return None, - Some(documents) => { - this.documents = documents; - this.current = 0; - this.next_page_token = v.next_page_token; + let result = get_new_data(&this.collection_id, &url, &this.auth).await; + match result { + Err(e) => { + this.done = true; + return Some((Err(e), this)); + } + Ok(v) => match v.documents { + None => return None, + Some(documents) => { + this.documents = documents; + this.current = 0; + this.next_page_token = v.next_page_token; + } }, - }, + } } - } - let doc = this.documents.get(this.current).unwrap().clone(); + let doc = this.documents.get(this.current).unwrap().clone(); - this.current += 1; + this.current += 1; - if this.documents.len() <= this.current && this.next_page_token.is_none() { - this.done = true; - } + if this.documents.len() <= this.current && this.next_page_token.is_none() { + this.done = true; + } - let result = document_to_pod(&doc, None); - match result { - Err(e) => Some((Err(e), this)), - Ok(pod) => Some((Ok(( - pod, - dto::Document { - update_time: doc.update_time.clone(), - create_time: doc.create_time.clone(), - name: doc.name.clone(), - fields: None, - }, - )), this)), - } - })) + let result = document_to_pod(&doc, None); + match result { + Err(e) => Some((Err(e), this)), + Ok(pod) => Some(( + Ok(( + pod, + dto::Document { + update_time: doc.update_time.clone(), + create_time: doc.create_time.clone(), + name: doc.name.clone(), + fields: None, + }, + )), + this, + )), + } + }, + )) } async fn get_new_data<'a>( @@ -125,14 +131,12 @@ async fn get_new_data<'a>( .send() .await?; - let resp = extract_google_api_error_async(resp, || collection_id.to_owned()) - .await?; + let resp = extract_google_api_error_async(resp, || collection_id.to_owned()).await?; let json: dto::ListDocumentsResponse = resp.json().await?; Ok(json) } - #[derive(Clone)] struct ListInner { auth: AUTH, @@ -142,4 +146,4 @@ struct ListInner { done: bool, url: String, collection_id: String, -} \ No newline at end of file +} diff --git a/src/documents/mod.rs b/src/documents/mod.rs index bb51fc2..e5f52d0 100644 --- a/src/documents/mod.rs +++ b/src/documents/mod.rs @@ -12,17 +12,16 @@ use serde::{Deserialize, Serialize}; use std::path::Path; mod delete; -mod read; -mod write; mod list; mod query; +mod read; +mod write; pub use delete::*; -pub use read::*; -pub use write::*; pub use list::*; pub use query::*; - +pub use read::*; +pub use write::*; /// An [`Iterator`] implementation that provides a join method /// diff --git a/src/documents/read.rs b/src/documents/read.rs index 65f890e..17234e9 100644 --- a/src/documents/read.rs +++ b/src/documents/read.rs @@ -16,17 +16,15 @@ where // We take the raw response first in order to provide // more complete errors on deserialization failure let full = resp.bytes().await?; - let json = serde_json::from_slice(&full) - .map_err(|e| FirebaseError::SerdeVerbose { - doc: Some(String::from(document_name)), - input_doc: String::from_utf8_lossy(&full).to_string(), - ser: e - })?; + let json = serde_json::from_slice(&full).map_err(|e| FirebaseError::SerdeVerbose { + doc: Some(String::from(document_name)), + input_doc: String::from_utf8_lossy(&full).to_string(), + ser: e, + })?; Ok(document_to_pod(&json, Some(&full))?) } - /// /// Read a document of a specific type from a collection /// @@ -42,7 +40,6 @@ where read_by_name(auth, &document_name).await } - /// Return the raw unparsed content of the Firestore document. Methods like /// [`read()`](../documents/fn.read.html) will deserialize the JSON-encoded /// response into a known type `T` @@ -53,16 +50,11 @@ where pub async fn contents(auth: &impl FirebaseAuthBearer, path: &str, document_id: &str) -> Result { let document_name = document_name(&auth.project_id(), path, document_id); let resp = request_document(auth, &document_name).await?; - resp.text().await - .map_err(|e| FirebaseError::Request(e)) + resp.text().await.map_err(|e| FirebaseError::Request(e)) } - /// Executes the request to retrieve the document. Returns the response from `reqwest` -async fn request_document( - auth: &impl FirebaseAuthBearer, - document_name: &str, -) -> Result { +async fn request_document(auth: &impl FirebaseAuthBearer, document_name: &str) -> Result { let url = firebase_url_base(document_name.as_ref()); let resp = auth @@ -79,9 +71,7 @@ async fn request_document( fn document_name(project_id: &str, path: &str, document_id: &str) -> String { format!( "projects/{}/databases/(default)/documents/{}/{}", - project_id, - path, - document_id + project_id, path, document_id ) } diff --git a/src/documents/write.rs b/src/documents/write.rs index 674df25..a66d37c 100644 --- a/src/documents/write.rs +++ b/src/documents/write.rs @@ -119,10 +119,10 @@ where .and_then(|f| Some(f.as_ref().to_owned())) .or(Some(String::new())) .unwrap() - }).await?; + }) + .await?; - let result_document: dto::Document = resp.json() - .await?; + let result_document: dto::Document = resp.json().await?; let document_id = Path::new(&result_document.name) .file_name() .ok_or_else(|| FirebaseError::Generic("Resulting documents 'name' field is not a valid path"))? diff --git a/src/errors.rs b/src/errors.rs index 30e669a..a0c500b 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -112,11 +112,14 @@ impl fmt::Display for FirebaseError { } else { ser.fmt(f) } - }, + } FirebaseError::SerdeVerbose { doc, input_doc, ser } => { let doc = doc.clone().unwrap_or("Unknown document".to_string()); - writeln!(f, "Serde deserialization failed for document '{}' with error '{}' on input: '{}'", - doc, ser, input_doc) + writeln!( + f, + "Serde deserialization failed for document '{}' with error '{}' on input: '{}'", + doc, ser, input_doc + ) } } } diff --git a/src/firebase_rest_to_rust.rs b/src/firebase_rest_to_rust.rs index a6cff98..4a14776 100644 --- a/src/firebase_rest_to_rust.rs +++ b/src/firebase_rest_to_rust.rs @@ -3,10 +3,10 @@ //! the data types of the Firebase REST API. Those are 1:1 translations of the grpc API //! and deeply nested and wrapped. +use bytes::Bytes; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; -use bytes::Bytes; use super::dto; use super::errors::{FirebaseError, Result}; @@ -144,7 +144,9 @@ where let v = serde_json::to_value(r)?; let r: T = serde_json::from_value(v).map_err(|e| FirebaseError::SerdeVerbose { doc: Some(document.name.clone()), - input_doc: String::from_utf8_lossy(input_doc.unwrap_or(&Bytes::new())).replace("\n", " ").to_string(), + input_doc: String::from_utf8_lossy(input_doc.unwrap_or(&Bytes::new())) + .replace("\n", " ") + .to_string(), ser: e, })?; Ok(r) diff --git a/src/jwt.rs b/src/jwt.rs index 0496148..82d6fa7 100644 --- a/src/jwt.rs +++ b/src/jwt.rs @@ -6,14 +6,14 @@ use serde::{Deserialize, Serialize}; use chrono::{Duration, Utc}; use std::collections::HashSet; -use std::slice::Iter; use std::ops::Add; +use std::slice::Iter; use crate::errors::FirebaseError; use biscuit::jwa::SignatureAlgorithm; use biscuit::{ClaimPresenceOptions, SingleOrMultiple, ValidationOptions}; -use std::ops::Deref; use cache_control::CacheControl; +use std::ops::Deref; type Error = super::errors::FirebaseError; @@ -68,7 +68,8 @@ impl JWKSet { pub async fn download_google_jwks(account_mail: &str) -> Result<(String, Option), Error> { let url = format!("https://www.googleapis.com/service_accounts/v1/jwk/{}", account_mail); let resp = reqwest::Client::new().get(&url).send().await?; - let max_age = resp.headers() + let max_age = resp + .headers() .get("cache-control") .and_then(|cache_control| cache_control.to_str().ok()) .and_then(|cache_control| CacheControl::from_value(cache_control)) @@ -88,10 +89,7 @@ pub(crate) async fn create_jwt_encoded>( audience: &str, ) -> Result { let jwt = create_jwt(credentials, scope, duration, client_id, user_id, audience)?; - let secret_lock = credentials - .keys - .read() - .await; + let secret_lock = credentials.keys.read().await; let secret = secret_lock .secret .as_ref() @@ -250,7 +248,10 @@ pub mod session_cookie { use super::*; use std::ops::Add; - pub(crate) async fn create_jwt_encoded(credentials: &Credentials, duration: chrono::Duration) -> Result { + pub(crate) async fn create_jwt_encoded( + credentials: &Credentials, + duration: chrono::Duration, + ) -> Result { let scope = [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/firebase.database", @@ -288,10 +289,7 @@ pub mod session_cookie { }; let jwt = JWT::new_decoded(header, expected_claims); - let secret_lock = credentials - .keys - .read() - .await; + let secret_lock = credentials.keys.read().await; let secret = secret_lock .secret .as_ref() diff --git a/src/rocket/mod.rs b/src/rocket/mod.rs index f2cb280..763ecba 100644 --- a/src/rocket/mod.rs +++ b/src/rocket/mod.rs @@ -34,8 +34,8 @@ use super::credentials::Credentials; use super::errors::FirebaseError; use super::sessions; -use rocket::{http::Status, request, State}; use rocket::request::Outcome; +use rocket::{http::Status, request, State}; /// Use this Rocket guard to secure a route for authenticated users only. /// Will return the associated session, that contains the used access token for further use diff --git a/src/sessions.rs b/src/sessions.rs index 2ca972d..5afca2b 100644 --- a/src/sessions.rs +++ b/src/sessions.rs @@ -16,8 +16,8 @@ use serde::{Deserialize, Serialize}; use std::cell::RefCell; use std::ops::Deref; use std::slice::Iter; -use tokio::sync::RwLock; use std::sync::Arc; +use tokio::sync::RwLock; pub mod user { use super::*; @@ -231,8 +231,12 @@ pub mod user { /// - `refresh_token` A refresh token. /// /// Async support: This is a blocking operation. - pub async fn by_refresh_token(credentials: &Credentials, refresh_token: &str) -> Result { - let r: RefreshTokenToAccessTokenResponse = get_new_access_token(&credentials.api_key, refresh_token).await?; + pub async fn by_refresh_token( + credentials: &Credentials, + refresh_token: &str, + ) -> Result { + let r: RefreshTokenToAccessTokenResponse = + get_new_access_token(&credentials.api_key, refresh_token).await?; Ok(Session { user_id: r.user_id, access_token_: Arc::new(RwLock::new(r.id_token)), @@ -243,7 +247,6 @@ pub mod user { }) } - /// Create a new firestore user session with a fresh access token. /// /// Arguments: @@ -267,10 +270,7 @@ pub mod user { Some(user_id.to_owned()), JWT_AUDIENCE_IDENTITY, )?; - let secret_lock = credentials - .keys - .read() - .await; + let secret_lock = credentials.keys.read().await; let secret = secret_lock .secret .as_ref() @@ -295,7 +295,6 @@ pub mod user { }) } - /// Create a new firestore user session by a valid access token /// /// Remember that such a session cannot renew itself. As soon as the access token expired, @@ -352,11 +351,7 @@ pub mod user { return_secure_token, }; - let response = reqwest::Client::new() - .post(&uri) - .json(&json) - .send() - .await?; + let response = reqwest::Client::new().post(&uri).json(&json).send().await?; let oauth_response: OAuthResponse = response.json().await?; @@ -499,7 +494,8 @@ pub mod service_account { let mut jwt = self.jwt.write().await; if jwt_update_expiry_if(&mut jwt, 50) { - self.credentials.keys + self.credentials + .keys .read() .await .secret @@ -549,10 +545,7 @@ pub mod service_account { JWT_AUDIENCE_FIRESTORE, )?; let encoded = { - let secret_lock = credentials - .keys - .read() - .await; + let secret_lock = credentials.keys.read().await; let secret = secret_lock .secret .as_ref() diff --git a/src/users.rs b/src/users.rs index 56b60ba..15b2163 100644 --- a/src/users.rs +++ b/src/users.rs @@ -119,7 +119,12 @@ struct SignInUpUserRequest { pub returnSecureToken: bool, } -async fn sign_up_in(session: &service_account::Session, email: &str, password: &str, action: &str) -> Result { +async fn sign_up_in( + session: &service_account::Session, + email: &str, + password: &str, + action: &str, +) -> Result { let url = firebase_auth_url(action, &session.credentials.api_key); let resp = session .client() @@ -141,7 +146,8 @@ async fn sign_up_in(session: &service_account::Session, email: &str, password: & Some(&resp.localId), Some(&resp.idToken), Some(&resp.refreshToken), - ).await?) + ) + .await?) } /// Creates the firebase auth user with the given email and password and returns