Skip to content

Commit

Permalink
Fix examples, update rocket integration
Browse files Browse the repository at this point in the history
Signed-off-by: David Graeff <davgraeff@gmail.com>
  • Loading branch information
David Graeff authored and davidgraeff committed Jan 22, 2024
1 parent f0e4e11 commit baa17a8
Show file tree
Hide file tree
Showing 16 changed files with 150 additions and 118 deletions.
13 changes: 5 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.8.0] - 2024-01-22

### Added

- Credentials::download_google_jwks(): Update/replace public keys. Useful for long running services.
Expand All @@ -14,14 +16,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Support for reqwest 0.11 / Tokio 1.0
- Prefer to use `AsRef<str>` when passing params
- Add ability to read raw document contents (without deserializing the JSON)
- [Breaking] Change Credentials::new: No JWKSet parameter, use with_jwkset or download_jwkset
- [Breaking] Change Credentials::from_file: Do not download jwks anymore. Use with_jwkset or download_jwkset.
- [Breaking] Rename JWKSetDTO to JWKSet
- [Breaking] jwt::download_google_jwks returns a string and not a DTO anymore for better error reporting
- [Breaking] jwt::download_google_jwks_async is behind the unstable feature now, as originally intended
- [Breaking] Async only API
- [Breaking] Rocket example uses the Rocket 0.5 release
- [Breaking] Migrated to Rust edition 2021

## [0.6.1] - 2020-11-12

Expand Down
17 changes: 10 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "firestore-db-and-auth"
version = "0.8.0"
authors = ["David Gräff <david.graeff@web.de>"]
edition = "2018"
edition = "2021"
license = "MIT"
description = "This crate allows easy access to your Google Firestore DB via service account or OAuth impersonated Google Firebase Auth credentials."
readme = "readme.md"
Expand All @@ -14,21 +14,24 @@ 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"] }
reqwest = { version = "0.11", default-features = false, features = ["json", "blocking", "hyper-rustls"] }
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"
biscuit = "0.7"
ring = "0.17"
base64 = "0.21"
async-trait = "0.1"
tokio = { version = "1.13", features = ["macros"] }
futures = "0.3"
pin-project = "1.0"
http = "0.2"
http = "1.0"

[dev-dependencies]
tokio-test = "0.4"

[dependencies.rocket]
version = "0.4.6"
version = "0.5.0"
default-features = false
optional = true

Expand Down
8 changes: 4 additions & 4 deletions examples/create_read_write_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ async fn main() -> errors::Result<()> {

// 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?;
cred.add_jwks_public_keys(&jwkset);
cred.add_jwks_public_keys(&jwkset).await;
cred.verify().await?;

// Perform some db operations via a service account session
Expand All @@ -225,12 +225,12 @@ async fn valid_test_credentials() -> errors::Result<Credentials> {
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!("../tests/service-account-test.json"))?;
let cred: Credentials = Credentials::new(include_str!("../firebase-service-account.json")).await?;

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

Ok(cred)
}
Expand Down
11 changes: 8 additions & 3 deletions examples/own_auth.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
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
Expand Down Expand Up @@ -29,8 +30,7 @@ impl FirebaseAuthBearer for MyOwnSession {
}
}

#[tokio::main]
async fn main() -> errors::Result<()> {
async fn run() -> errors::Result<()> {
let credentials = Credentials::from_file("firebase-service-account.json").await?;
#[derive(serde::Serialize)]
struct TestData {
Expand All @@ -56,9 +56,14 @@ async fn main() -> errors::Result<()> {
Ok(())
}

#[tokio::main]
async fn main() -> errors::Result<()> {
run().await
}

#[tokio::test]
async fn own_auth_test() {
if let Err(APIError(code, str_code, context)) = main() {
if let Err(APIError(code, str_code, context)) = run().await {
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");
assert_eq!(code, 401);
Expand Down
2 changes: 1 addition & 1 deletion examples/session_cookie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ async fn main() -> Result<(), FirebaseError> {

// 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?;
cred.add_jwks_public_keys(&jwkset);
cred.add_jwks_public_keys(&jwkset).await;
cred.verify().await?;

let user_session = utils::user_session_with_cached_refresh_token(&cred).await?;
Expand Down
14 changes: 8 additions & 6 deletions examples/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@ pub async fn from_cache_file(cache_file: &std::path::Path, c: &Credentials) -> e
} else {
// If not present, download the two jwks (specific service account + google system account),
// merge them into one set of keys and store them in the cache file.
let mut jwks = JWKSet::new(&download_google_jwks(&c.client_email).await?.0)?;
jwks.keys
.append(&mut JWKSet::new(&download_google_jwks("securetoken@system.gserviceaccount.com").await?.0)?.keys);
let jwk_set_1 = download_google_jwks(&c.client_email).await?;
let jwk_set_2 = download_google_jwks("securetoken@system.gserviceaccount.com").await?;

let mut jwks = JWKSet::new(&jwk_set_1.0)?;
jwks.keys.append(&mut JWKSet::new(&jwk_set_2.0)?.keys);
let f = File::create(cache_file)?;
serde_json::to_writer_pretty(f, &jwks)?;
jwks
Expand All @@ -64,12 +66,12 @@ pub async fn valid_test_credentials() -> errors::Result<Credentials> {
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!("../../tests/service-account-test.json"))?;
let cred: Credentials = Credentials::new(include_str!("../../firebase-service-account.json")).await?;

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

Ok(cred)
}
60 changes: 31 additions & 29 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Limitations:

This crate operates on DTOs (Data transfer objects) for type-safe operations on your Firestore DB.

```rust
```rust,no_run
use firestore_db_and_auth::{Credentials, ServiceSession, documents, errors::Result};
use serde::{Serialize,Deserialize};
Expand Down Expand Up @@ -78,34 +78,36 @@ fn write_partial(session: &ServiceSession) -> Result<()> {

Read the document with the id "service_test" from the Firestore "tests" collection:

```rust
```rust,no_run
use firestore_db_and_auth::{documents};
let obj : DemoDTO = documents::read(&session, "tests", "service_test")?;
```

For listing all documents of the "tests" collection you want to use the `List` struct which implements the `Iterator` trait.
It will hide the complexity of the paging API and fetches new documents when necessary:
For listing all documents of the "tests" collection you want to use the `list` method which implements an async stream.
This hide the complexity of the paging API and fetches new documents when necessary:

```rust
```rust,no_run
use firestore_db_and_auth::{documents};
let values: documents::List<DemoDTO, _> = documents::list(&session, "tests");
for doc_result in values {
let mut stream = documents::list(&session, "tests");
while let Some(Ok(doc_result)) = stream.next().await {
// The document is wrapped in a Result<> because fetching new data could have failed
let (doc, _metadata) = doc_result?;
let (doc, _metadata) = doc_result;
let doc: DemoDTO = doc;
println!("{:?}", doc);
}
```

*Note:* The resulting list or list cursor is a snapshot view with a limited lifetime.
You cannot keep the iterator for long or expect new documents to appear in an ongoing iteration.
You cannot keep the iterator/stream for long or expect new documents to appear in an ongoing iteration.

For quering the database you would use the `query` method.
For querying the database you would use the `query` method.
In the following example the collection "tests" is queried for document(s) with the "id" field equal to "Sam Weiss".

```rust
```rust,no_run
use firestore_db_and_auth::{documents, dto};
let values = documents::query(&session, "tests", "Sam Weiss".into(), dto::FieldOperator::EQUAL, "id")?;
let values = documents::query(&session, "tests", "Sam Weiss".into(), dto::FieldOperator::EQUAL, "id").await?;
for metadata in values {
println!("id: {}, created: {}, updated: {}", metadata.name.as_ref().unwrap(), metadata.create_time.as_ref().unwrap(), metadata.update_time.as_ref().unwrap());
// Fetch the actual document
Expand All @@ -128,10 +130,10 @@ This custom error type wraps all possible errors (IO, Reqwest, JWT errors etc)
and Google REST API errors. If you want to specifically check for an API error,
you could do so:

```rust
```rust,no_run
use firestore_db_and_auth::{documents, errors::FirebaseError};
let r = documents::delete(&session, "tests/non_existing", true);
let r = documents::delete(&session, "tests/non_existing", true).await;
if let Err(e) = r.err() {
if let FirebaseError::APIError(code, message, context) = e {
assert_eq!(code, 404);
Expand All @@ -151,13 +153,13 @@ It may be the collection or document id or any other context information.
The file should contain `"private_key_id": ...`.
2. Add another field `"api_key" : "YOUR_API_KEY"` and replace YOUR_API_KEY with your *Web API key*, to be found in the [Google Firebase console](https://console.firebase.google.com) in "Project Overview -> Settings - > General".

```rust
```rust,no_run
use firestore_db_and_auth::{Credentials, ServiceSession};
/// Create credentials object. You may as well do that programmatically.
let cred = Credentials::from_file("firebase-service-account.json")
let cred = Credentials::from_file("firebase-service-account.json").await
.expect("Read credentials file")
.download_jwkset()
.download_jwkset().await
.expect("Failed to download public keys");
/// To use any of the Firestore methods, you need a session first. You either want
/// an impersonated session bound to a Firebase Auth user or a service account session.
Expand All @@ -170,33 +172,33 @@ let session = ServiceSession::new(&cred)
You can create a user session in various ways.
If you just have the firebase Auth user_id, you would follow these steps:

```rust
```rust,no_run
use firestore_db_and_auth::{Credentials, sessions};
/// Create credentials object. You may as well do that programmatically.
let cred = Credentials::from_file("firebase-service-account.json")
let cred = Credentials::from_file("firebase-service-account.json").await
.expect("Read credentials file")
.download_jwkset()
.download_jwkset().await
.expect("Failed to download public keys");
/// To use any of the Firestore methods, you need a session first.
/// Create an impersonated session bound to a Firebase Auth user via your service account credentials.
let session = UserSession::by_user_id(&cred, "the_user_id")
let session = UserSession::by_user_id(&cred, "the_user_id").await
.expect("Create a user session");
```

If you already have a valid refresh token and want to generate an access token (and a session object), you do this instead:

```rust
```rust,no_run
let refresh_token = "fkjandsfbajsbfd;asbfdaosa.asduabsifdabsda,fd,a,sdbasfadfasfas.dasdasbfadusbflansf";
let session = UserSession::by_refresh_token(&cred, &refresh_token)?;
let session = UserSession::by_refresh_token(&cred, &refresh_token).await?;
```

Another way of retrieving a session object is by providing a valid access token like so:

```rust
```rust,no_run
let access_token = "fkjandsfbajsbfd;asbfdaosa.asduabsifdabsda,fd,a,sdbasfadfasfas.dasdasbfadusbflansf";
let session = UserSession::by_access_token(&cred, &access_token)?;
let session = UserSession::by_access_token(&cred, &access_token).await?;
```

The `by_access_token` method will fail if the token is not valid anymore.
Expand All @@ -220,16 +222,16 @@ First download the 2 public key files:

Create a `Credentials` object like so:

```rust
```rust,no_run
use firestore_db_and_auth::Credentials;
let c = Credentials::new(include_str!("firebase-service-account.json"))?
.with_jwkset(&JWKSet::new(include_str!("firebase-service-account.jwks"))?)?;
let c = Credentials::new(include_str!("firebase-service-account.json")).await?
.with_jwkset(&JWKSet::new(include_str!("firebase-service-account.jwks"))?).await?;
```

> Please note though, that Googles JWK keys change periodically.
> You probably want to redeploy your service with fresh public keys about every three weeks.
>
> For long running service you want to call Credentials::download_google_jwks() periodically.
> For long-running service you want to call Credentials::download_google_jwks() periodically.
### More information

Expand Down
Loading

0 comments on commit baa17a8

Please sign in to comment.