Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move to async #31

Closed
wants to merge 26 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6c62fb2
we can make async requests that interest us
mkawalec Oct 4, 2021
20713b6
remove mutexes in favor of rwlocks
mkawalec Oct 6, 2021
9b0a701
version bump
mkawalec Oct 6, 2021
c3f40d2
we can have async lists
mkawalec Oct 6, 2021
b732468
have a more iterative logic
mkawalec Oct 6, 2021
be09f2f
relaxed the bounds on sessions
mkawalec Oct 11, 2021
96366b6
use default encoding functions
mkawalec Oct 12, 2021
1f87723
refresh the tokens properly
mkawalec Oct 12, 2021
cc78ab5
left only async
mkawalec Nov 1, 2021
4f78f64
added a quick and dirty way of generating valid id tokens
mkawalec Nov 2, 2021
1d39dda
google keys are refreshed
mkawalec Nov 2, 2021
e8d4a5e
verify that the public keys are truly refreshed
mkawalec Nov 2, 2021
fd25261
use rwlock for better read performance
mkawalec Nov 3, 2021
fbe1a08
simplify token generation
mkawalec Nov 3, 2021
5bb7938
added debug annotations to the session
mkawalec Nov 16, 2021
477695a
include the input document on failures
mkawalec Dec 6, 2021
7d1e776
removed unused oauth fields
mkawalec Dec 6, 2021
56729cf
log the input document when deserialization fails
mkawalec Dec 6, 2021
aa6b93f
removed printlns
mkawalec Dec 6, 2021
203363e
removed the expiration messages
mkawalec Dec 6, 2021
6a21910
make the failed input output nicer
mkawalec Dec 6, 2021
4c5b64e
replace newlines with spaces
mkawalec Dec 6, 2021
d6df985
moved some examples to async
mkawalec Jan 17, 2022
fa549cc
other examples moved to async
mkawalec Jan 17, 2022
319b40a
removed the debug tokens
mkawalec Jan 17, 2022
c70e508
removed debug prints
mkawalec Jan 17, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "firestore-db-and-auth"
version = "0.6.1"
version = "0.8.0"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would argue this should probably be a major version breaking change. With the changes proposed here all sync code disappears - meaning this library becomes exclusively async. Changes here would no longer be compatible with those using the sync only version of this library

authors = ["David Gräff <david.graeff@web.de>"]
edition = "2018"
license = "MIT"
Expand All @@ -12,13 +12,20 @@ maintenance = { status = "passively-maintained" }
repository = "https://github.com/davidgraeff/firestore-db-and-auth-rs"

[dependencies]
bytes = "1.1"
cache_control = "0.2"
reqwest = { version = "0.11", default-features = false, features = ["json", "blocking"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
biscuit = "0.5"
ring = "0.16"
base64 = "0.13"
async-trait = "0.1"
tokio = { version = "1.13", features = ["macros"] }
futures = "0.3"
pin-project = "1.0"
http = "0.2"

[dependencies.rocket]
version = "0.4.6"
Expand Down
107 changes: 65 additions & 42 deletions examples/create_read_write_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use firestore_db_and_auth::{documents, dto, errors, sessions, Credentials, Fireb
use firestore_db_and_auth::documents::WriteResult;
use serde::{Deserialize, Serialize};

use futures::stream::StreamExt;

mod utils;

#[derive(Debug, Serialize, Deserialize)]
Expand All @@ -21,7 +23,7 @@ struct DemoDTOPartial {
an_int: u32,
}

fn write_document(session: &mut ServiceSession, doc_id: &str) -> errors::Result<WriteResult> {
async fn write_document(session: &mut ServiceSession, doc_id: &str) -> errors::Result<WriteResult> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally the question to ask here in the examples is if we want to convert all of the examples to async code. I don't think the lib should imply that you must use async (though I think most people would indeed chose to do so).

Personally I would choose to move forward with the async approach through these examples. Curious if you have any thoughts @davidgraeff ?

println!("Write document");

let obj = DemoDTO {
Expand All @@ -30,10 +32,16 @@ fn write_document(session: &mut ServiceSession, doc_id: &str) -> errors::Result<
a_timestamp: chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Nanos, true),
};

documents::write(session, "tests", Some(doc_id), &obj, documents::WriteOptions::default())
documents::write(
session,
"tests",
Some(doc_id),
&obj,
documents::WriteOptions::default()
).await
}

fn write_partial_document(session: &mut ServiceSession, doc_id: &str) -> errors::Result<WriteResult> {
async fn write_partial_document(session: &mut ServiceSession, doc_id: &str) -> errors::Result<WriteResult> {
println!("Partial write document");

let obj = DemoDTOPartial {
Expand All @@ -47,7 +55,7 @@ fn write_partial_document(session: &mut ServiceSession, doc_id: &str) -> errors:
Some(doc_id),
&obj,
documents::WriteOptions { merge: true },
)
).await
}

fn check_write(result: WriteResult, doc_id: &str) {
Expand All @@ -62,25 +70,25 @@ fn check_write(result: WriteResult, doc_id: &str) {
);
}

fn service_account_session(cred: Credentials) -> errors::Result<()> {
let mut session = ServiceSession::new(cred).unwrap();
let b = session.access_token().to_owned();
async fn service_account_session(cred: Credentials) -> errors::Result<()> {
let mut session = ServiceSession::new(cred).await.unwrap();
let b = session.access_token().await.to_owned();

let doc_id = "service_test";
check_write(write_document(&mut session, doc_id)?, doc_id);
check_write(write_document(&mut session, doc_id).await?, doc_id);

// Check if cached value is used
assert_eq!(session.access_token(), b);
assert_eq!(session.access_token().await, b);

println!("Read and compare document");
let read: DemoDTO = documents::read(&mut session, "tests", doc_id)?;
let read: DemoDTO = documents::read(&mut session, "tests", doc_id).await?;

assert_eq!(read.a_string, "abcd");
assert_eq!(read.an_int, 14);

check_write(write_partial_document(&mut session, doc_id)?, doc_id);
check_write(write_partial_document(&mut session, doc_id).await?, doc_id);
println!("Read and compare document");
let read: DemoDTOPartial = documents::read(&mut session, "tests", doc_id)?;
let read: DemoDTOPartial = documents::read(&mut session, "tests", doc_id).await?;

// Should be updated
assert_eq!(read.an_int, 16);
Expand All @@ -90,14 +98,17 @@ fn service_account_session(cred: Credentials) -> errors::Result<()> {
Ok(())
}

fn user_account_session(cred: Credentials) -> errors::Result<()> {
let user_session = utils::user_session_with_cached_refresh_token(&cred)?;
async fn user_account_session(cred: Credentials) -> errors::Result<()> {
let user_session = utils::user_session_with_cached_refresh_token(&cred).await?;

assert_eq!(user_session.user_id, utils::TEST_USER_ID);
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())?;
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);

Expand All @@ -117,13 +128,13 @@ fn user_account_session(cred: Credentials) -> errors::Result<()> {
Some(doc_id),
&obj,
documents::WriteOptions::default(),
)?,
).await?,
doc_id,
);

// Test reading
println!("user::Session documents::read");
let read: DemoDTO = documents::read(&user_session, "tests", doc_id)?;
let read: DemoDTO = documents::read(&user_session, "tests", doc_id).await?;

assert_eq!(read.a_string, "abc");
assert_eq!(read.an_int, 12);
Expand All @@ -135,22 +146,24 @@ fn user_account_session(cred: Credentials) -> errors::Result<()> {
"abc".into(),
dto::FieldOperator::EQUAL,
"a_string",
)?
).await?
.collect();
assert_eq!(results.len(), 1);
let doc: DemoDTO = documents::read_by_name(&user_session, &results.get(0).unwrap().name)?;
let doc: DemoDTO = documents::read_by_name(&user_session, &results.get(0).unwrap().name).await?;
assert_eq!(doc.a_string, "abc");

let mut count = 0;
let list_it: documents::List<DemoDTO, _> = documents::list(&user_session, "tests".to_owned());
let list_it = documents::list(&user_session, "tests".to_owned())
.collect::<Vec<errors::Result<(DemoDTO, _)>>>()
.await;
for _doc in list_it {
count += 1;
}
assert_eq!(count, 2);

// test if the call fails for a non existing document
println!("user::Session documents::delete");
let r = documents::delete(&user_session, "tests/non_existing", true);
let r = documents::delete(&user_session, "tests/non_existing", true).await;
assert!(r.is_err());
match r.err().unwrap() {
errors::FirebaseError::APIError(code, message, context) => {
Expand All @@ -161,7 +174,7 @@ fn user_account_session(cred: Credentials) -> errors::Result<()> {
_ => panic!("Expected an APIError"),
};

documents::delete(&user_session, &("tests/".to_owned() + doc_id), false)?;
documents::delete(&user_session, &("tests/".to_owned() + doc_id), false).await?;

// Check if document is indeed removed
println!("user::Session documents::query");
Expand All @@ -171,71 +184,81 @@ fn user_account_session(cred: Credentials) -> errors::Result<()> {
"abc".into(),
dto::FieldOperator::EQUAL,
"a_string",
)?
)
.await?
.count();
assert_eq!(count, 0);

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")?.count();

let count = documents::query(&user_session, "tests", f.into(), dto::FieldOperator::EQUAL, "a_float")
.await?;

let count = count
.count();
assert_eq!(count, 0);

Ok(())
}

fn main() -> errors::Result<()> {
#[tokio::main]
async fn main() -> errors::Result<()> {
// Search for a credentials file in the root directory
use std::path::PathBuf;
let mut credential_file = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
credential_file.push("firebase-service-account.json");
let mut cred = Credentials::from_file(credential_file.to_str().unwrap())?;
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)?;
let jwkset = utils::from_cache_file(
credential_file.with_file_name("cached_jwks.jwks").as_path(),
&cred
).await?;
cred.add_jwks_public_keys(&jwkset);
cred.verify()?;
cred.verify().await?;

// Perform some db operations via a service account session
service_account_session(cred.clone())?;
service_account_session(cred.clone()).await?;

// Perform some db operations via a firebase user session
user_account_session(cred)?;
user_account_session(cred).await?;

Ok(())
}

/// For integration tests and doc code snippets: Create a Credentials instance.
/// Necessary public jwk sets are downloaded or re-used if already present.
#[cfg(test)]
fn valid_test_credentials() -> errors::Result<Credentials> {
async fn valid_test_credentials() -> errors::Result<Credentials> {
use std::path::PathBuf;
let mut jwks_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
jwks_path.push("firebase-service-account.jwks");

let mut cred: Credentials = Credentials::new(include_str!("../firebase-service-account.json"))?;
let mut cred: Credentials = Credentials::new(include_str!("../tests/service-account-test.json"))?;

// Only download the public keys once, and cache them.
let jwkset = utils::from_cache_file(jwks_path.as_path(), &cred)?;
let jwkset = utils::from_cache_file(jwks_path.as_path(), &cred).await?;
cred.add_jwks_public_keys(&jwkset);
cred.verify()?;

Ok(cred)
}

#[test]
fn valid_test_credentials_test() -> errors::Result<()> {
valid_test_credentials()?;
#[tokio::test]
async fn valid_test_credentials_test() -> errors::Result<()> {
valid_test_credentials().await?;
Ok(())
}

#[test]
fn service_account_session_test() -> errors::Result<()> {
service_account_session(valid_test_credentials()?)?;
#[tokio::test]
async fn service_account_session_test() -> errors::Result<()> {
service_account_session(valid_test_credentials().await?).await?;
Ok(())
}

#[test]
fn user_account_session_test() -> errors::Result<()> {
user_account_session(valid_test_credentials()?)?;
#[tokio::test]
async fn user_account_session_test() -> errors::Result<()> {
user_account_session(valid_test_credentials().await?).await?;
Ok(())
}
13 changes: 9 additions & 4 deletions examples/firebase_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ use firestore_db_and_auth::*;

const TEST_USER_ID: &str = include_str!("test_user_id.txt");

fn main() -> errors::Result<()> {
let cred = Credentials::from_file("firebase-service-account.json").expect("Read credentials file");
#[tokio::main]
async fn main() -> errors::Result<()> {
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)?;
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)?;
let user_info_container = users::user_info(&user_session).await?;
assert_eq!(user_info_container.users[0].localId.as_ref().unwrap(), TEST_USER_ID);

Ok(())
Expand Down
25 changes: 10 additions & 15 deletions examples/own_auth.rs
Original file line number Diff line number Diff line change
@@ -1,41 +1,37 @@
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 {
/// The google credentials
pub credentials: Credentials,
pub blocking_client: reqwest::blocking::Client,
pub client: reqwest::Client,
access_token: String,
}

#[async_trait::async_trait]
impl FirebaseAuthBearer for MyOwnSession {
fn project_id(&self) -> &str {
&self.credentials.project_id
}
/// An access token. If a refresh token is known and the access token expired,
/// the implementation should try to refresh the access token before returning.
fn access_token(&self) -> String {
async fn access_token(&self) -> String {
self.access_token.clone()
}
/// The access token, unchecked. Might be expired or in other ways invalid.
fn access_token_unchecked(&self) -> String {
async fn access_token_unchecked(&self) -> String {
self.access_token.clone()
}
/// The reqwest http client.
/// The `Client` holds a connection pool internally, so it is advised that it is reused for multiple, successive connections.
fn client(&self) -> &reqwest::blocking::Client {
&self.blocking_client
}

fn client_async(&self) -> &reqwest::Client {
fn client(&self) -> &reqwest::Client {
&self.client
}
}

fn main() -> errors::Result<()> {
let credentials = Credentials::from_file("firebase-service-account.json")?;
#[tokio::main]
async fn main() -> errors::Result<()> {
let credentials = Credentials::from_file("firebase-service-account.json").await?;
#[derive(serde::Serialize)]
struct TestData {
an_int: u32,
Expand All @@ -44,7 +40,6 @@ fn main() -> errors::Result<()> {

let session = MyOwnSession {
credentials,
blocking_client: reqwest::blocking::Client::new(),
client: reqwest::Client::new(),
access_token: "The access token".to_owned(),
};
Expand All @@ -56,12 +51,12 @@ fn main() -> errors::Result<()> {
Some("test_doc"),
&t,
documents::WriteOptions::default(),
)?;
).await?;
Ok(())
}

#[test]
fn own_auth_test() {
#[tokio::test]
async fn own_auth_test() {
if let Err(APIError(code, str_code, context)) = main() {
assert_eq!(str_code, "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.");
assert_eq!(context, "test_doc");
Expand Down
Loading