Skip to content

Commit ca0156f

Browse files
author
David Graeff
committed
Fix examples, update rocket integration
Signed-off-by: David Graeff <davgraeff@gmail.com>
1 parent c169f1f commit ca0156f

File tree

16 files changed

+128
-101
lines changed

16 files changed

+128
-101
lines changed

CHANGELOG.md

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
## [0.8.0] - 2024-01-22
10+
911
### Added
1012

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

1517
### Changed
1618

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

2623
## [0.6.1] - 2020-11-12
2724

Cargo.toml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "firestore-db-and-auth"
33
version = "0.8.0"
44
authors = ["David Gräff <david.graeff@web.de>"]
5-
edition = "2018"
5+
edition = "2021"
66
license = "MIT"
77
description = "This crate allows easy access to your Google Firestore DB via service account or OAuth impersonated Google Firebase Auth credentials."
88
readme = "readme.md"
@@ -14,21 +14,24 @@ repository = "https://github.com/davidgraeff/firestore-db-and-auth-rs"
1414
[dependencies]
1515
bytes = "1.1"
1616
cache_control = "0.2"
17-
reqwest = { version = "0.11", default-features = false, features = ["json", "blocking"] }
17+
reqwest = { version = "0.11", default-features = false, features = ["json", "blocking", "hyper-rustls"] }
1818
serde = { version = "1.0", features = ["derive"] }
1919
serde_json = "1.0"
2020
chrono = { version = "0.4", features = ["serde"] }
21-
biscuit = "0.5"
22-
ring = "0.16"
23-
base64 = "0.13"
21+
biscuit = "0.7"
22+
ring = "0.17"
23+
base64 = "0.21"
2424
async-trait = "0.1"
2525
tokio = { version = "1.13", features = ["macros"] }
2626
futures = "0.3"
2727
pin-project = "1.0"
28-
http = "0.2"
28+
http = "1.0"
29+
30+
[dev-dependencies]
31+
tokio-test = "0.4"
2932

3033
[dependencies.rocket]
31-
version = "0.4.6"
34+
version = "0.5.0"
3235
default-features = false
3336
optional = true
3437

examples/create_read_write_document.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ async fn main() -> errors::Result<()> {
215215
credential_file.with_file_name("cached_jwks.jwks").as_path(),
216216
&cred
217217
).await?;
218-
cred.add_jwks_public_keys(&jwkset);
218+
cred.add_jwks_public_keys(&jwkset).await;
219219
cred.verify().await?;
220220

221221
// Perform some db operations via a service account session
@@ -235,12 +235,12 @@ async fn valid_test_credentials() -> errors::Result<Credentials> {
235235
let mut jwks_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
236236
jwks_path.push("firebase-service-account.jwks");
237237

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

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

245245
Ok(cred)
246246
}

examples/own_auth.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use firestore_db_and_auth::{documents, errors, Credentials, FirebaseAuthBearer};
2+
use firestore_db_and_auth::errors::FirebaseError::APIError;
23

34
/// Define your own structure that will implement the FirebaseAuthBearer trait
45
struct MyOwnSession {
@@ -29,8 +30,7 @@ impl FirebaseAuthBearer for MyOwnSession {
2930
}
3031
}
3132

32-
#[tokio::main]
33-
async fn main() -> errors::Result<()> {
33+
async fn run() -> errors::Result<()> {
3434
let credentials = Credentials::from_file("firebase-service-account.json").await?;
3535
#[derive(serde::Serialize)]
3636
struct TestData {
@@ -55,9 +55,14 @@ async fn main() -> errors::Result<()> {
5555
Ok(())
5656
}
5757

58+
#[tokio::main]
59+
async fn main() -> errors::Result<()> {
60+
run().await
61+
}
62+
5863
#[tokio::test]
5964
async fn own_auth_test() {
60-
if let Err(APIError(code, str_code, context)) = main() {
65+
if let Err(APIError(code, str_code, context)) = run().await {
6166
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.");
6267
assert_eq!(context, "test_doc");
6368
assert_eq!(code, 401);

examples/session_cookie.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ async fn main() -> Result<(), FirebaseError> {
1717
let jwkset = utils::from_cache_file(
1818
credential_file.with_file_name("cached_jwks.jwks").as_path(), &cred
1919
).await?;
20-
cred.add_jwks_public_keys(&jwkset);
20+
cred.add_jwks_public_keys(&jwkset).await;
2121
cred.verify().await?;
2222

2323
let user_session = utils::user_session_with_cached_refresh_token(&cred).await?;

examples/utils/mod.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,11 @@ pub async fn from_cache_file(cache_file: &std::path::Path, c: &Credentials) -> e
5050
} else {
5151
// If not present, download the two jwks (specific service account + google system account),
5252
// merge them into one set of keys and store them in the cache file.
53-
let mut jwks = JWKSet::new(&download_google_jwks(&c.client_email).await?.0)?;
54-
jwks.keys
55-
.append(&mut JWKSet::new(&download_google_jwks("securetoken@system.gserviceaccount.com").await?.0)?.keys);
53+
let jwk_set_1 = download_google_jwks(&c.client_email).await?;
54+
let jwk_set_2 = download_google_jwks("securetoken@system.gserviceaccount.com").await?;
55+
56+
let mut jwks = JWKSet::new(&jwk_set_1.0)?;
57+
jwks.keys.append(&mut JWKSet::new(&jwk_set_2.0)?.keys);
5658
let f = File::create(cache_file)?;
5759
serde_json::to_writer_pretty(f, &jwks)?;
5860
jwks
@@ -68,12 +70,12 @@ pub async fn valid_test_credentials() -> errors::Result<Credentials> {
6870
let mut jwks_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
6971
jwks_path.push("firebase-service-account.jwks");
7072

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

7375
// Only download the public keys once, and cache them.
7476
let jwkset = from_cache_file(jwks_path.as_path(), &cred).await?;
75-
cred.add_jwks_public_keys(&jwkset);
76-
cred.verify()?;
77+
cred.add_jwks_public_keys(&jwkset).await;
78+
cred.verify().await?;
7779

7880
Ok(cred)
7981
}

readme.md

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Limitations:
3636

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

39-
```rust
39+
```rust,no_run
4040
use firestore_db_and_auth::{Credentials, ServiceSession, documents, errors::Result};
4141
use serde::{Serialize,Deserialize};
4242
@@ -78,20 +78,22 @@ fn write_partial(session: &ServiceSession) -> Result<()> {
7878

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

81-
```rust
81+
```rust,no_run
82+
use firestore_db_and_auth::{documents};
8283
let obj : DemoDTO = documents::read(&session, "tests", "service_test")?;
8384
```
8485

8586
For listing all documents of the "tests" collection you want to use the `List` struct which implements the `Iterator` trait.
8687
It will hide the complexity of the paging API and fetches new documents when necessary:
8788

88-
```rust
89+
```rust,no_run
8990
use firestore_db_and_auth::{documents};
9091
91-
let values: documents::List<DemoDTO, _> = documents::list(&session, "tests");
92-
for doc_result in values {
92+
let mut stream = documents::list(&session, "tests");
93+
while let Some(Ok(doc_result)) = stream.next().await {
9394
// The document is wrapped in a Result<> because fetching new data could have failed
94-
let (doc, _metadata) = doc_result?;
95+
let (doc, _metadata) = doc_result;
96+
let doc: DemoDTO = doc;
9597
println!("{:?}", doc);
9698
}
9799
```
@@ -102,7 +104,7 @@ You cannot keep the iterator for long or expect new documents to appear in an on
102104
For quering the database you would use the `query` method.
103105
In the following example the collection "tests" is queried for document(s) with the "id" field equal to "Sam Weiss".
104106

105-
```rust
107+
```rust,no_run
106108
use firestore_db_and_auth::{documents, dto};
107109
108110
let values = documents::query(&session, "tests", "Sam Weiss".into(), dto::FieldOperator::EQUAL, "id")?;
@@ -128,7 +130,7 @@ This custom error type wraps all possible errors (IO, Reqwest, JWT errors etc)
128130
and Google REST API errors. If you want to specifically check for an API error,
129131
you could do so:
130132

131-
```rust
133+
```rust,no_run
132134
use firestore_db_and_auth::{documents, errors::FirebaseError};
133135
134136
let r = documents::delete(&session, "tests/non_existing", true);
@@ -151,7 +153,7 @@ It may be the collection or document id or any other context information.
151153
The file should contain `"private_key_id": ...`.
152154
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".
153155

154-
```rust
156+
```rust,no_run
155157
use firestore_db_and_auth::{Credentials, ServiceSession};
156158
157159
/// Create credentials object. You may as well do that programmatically.
@@ -170,7 +172,7 @@ let session = ServiceSession::new(&cred)
170172
You can create a user session in various ways.
171173
If you just have the firebase Auth user_id, you would follow these steps:
172174

173-
```rust
175+
```rust,no_run
174176
use firestore_db_and_auth::{Credentials, sessions};
175177
176178
/// Create credentials object. You may as well do that programmatically.
@@ -187,14 +189,14 @@ let session = UserSession::by_user_id(&cred, "the_user_id")
187189

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

190-
```rust
192+
```rust,no_run
191193
let refresh_token = "fkjandsfbajsbfd;asbfdaosa.asduabsifdabsda,fd,a,sdbasfadfasfas.dasdasbfadusbflansf";
192194
let session = UserSession::by_refresh_token(&cred, &refresh_token)?;
193195
```
194196

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

197-
```rust
199+
```rust,no_run
198200
let access_token = "fkjandsfbajsbfd;asbfdaosa.asduabsifdabsda,fd,a,sdbasfadfasfas.dasdasbfadusbflansf";
199201
let session = UserSession::by_access_token(&cred, &access_token)?;
200202
```
@@ -220,7 +222,7 @@ First download the 2 public key files:
220222

221223
Create a `Credentials` object like so:
222224

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

src/credentials.rs

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ use serde::{Deserialize, Serialize};
77
use serde_json;
88
use std::collections::BTreeMap;
99
use std::fs::File;
10-
use std::sync::Arc;
10+
use std::sync::{Arc};
1111
use std::fmt;
12-
use tokio::sync::RwLock;
1312

1413
use super::jwt::{create_jwt_encoded, download_google_jwks, verify_access_token, JWKSet, JWT_AUDIENCE_IDENTITY};
1514
use crate::{errors::FirebaseError, jwt::TokenValidationResult};
1615
use std::io::BufReader;
16+
use base64::Engine;
17+
use base64::prelude::BASE64_STANDARD;
18+
use tokio::sync::RwLock;
1719

1820
type Error = super::errors::FirebaseError;
1921

@@ -54,14 +56,15 @@ pub struct Credentials {
5456
pub client_email: String,
5557
pub client_id: String,
5658
pub api_key: String,
59+
/// The public keys. Those will rotate over time.
60+
/// Altering the keys is still a rare operation, so access should
61+
/// be optimized for reading, hence the RwLock.
5762
#[serde(default, skip)]
5863
pub(crate) keys: Arc<RwLock<Keys>>,
5964
}
6065

6166
/// Converts a PEM (ascii base64) encoded private key into the binary der representation
6267
pub fn pem_to_der(pem_file_contents: &str) -> Result<Vec<u8>, Error> {
63-
use base64::decode;
64-
6568
let pem_file_contents = pem_file_contents
6669
.find("-----BEGIN")
6770
// Cut off the first BEGIN part
@@ -77,7 +80,7 @@ pub fn pem_to_der(pem_file_contents: &str) -> Result<Vec<u8>, Error> {
7780
}
7881

7982
let base64_body = pem_file_contents.unwrap().replace("\n", "");
80-
Ok(decode(&base64_body)
83+
Ok(BASE64_STANDARD.decode(&base64_body)
8184
.map_err(|_| FirebaseError::Generic("Invalid private key in credentials file. Expected Base64 data."))?)
8285
}
8386

@@ -113,9 +116,10 @@ impl Credentials {
113116
/// use firestore_db_and_auth::{Credentials};
114117
/// use firestore_db_and_auth::jwt::JWKSet;
115118
///
116-
/// let c: Credentials = Credentials::new(include_str!("../tests/service-account-test.json"))?
117-
/// .with_jwkset(&JWKSet::new(include_str!("../tests/service-account-test.jwks"))?)?;
118-
/// # Ok::<(), firestore_db_and_auth::errors::FirebaseError>(())
119+
/// # tokio_test::block_on(async {
120+
/// let c: Credentials = Credentials::new(include_str!("../tests/service-account-test.json")).await.unwrap()
121+
/// .with_jwkset(&JWKSet::new(include_str!("../tests/service-account-test.jwks")).unwrap()).await.unwrap();
122+
/// # })
119123
/// ```
120124
///
121125
/// You need two JWKS files for this crate to work:
@@ -162,9 +166,10 @@ impl Credentials {
162166
/// ```no_run
163167
/// use firestore_db_and_auth::{Credentials};
164168
///
165-
/// let c: Credentials = Credentials::new(include_str!("../tests/service-account-test.json"))?
166-
/// .download_jwkset()?;
167-
/// # Ok::<(), firestore_db_and_auth::errors::FirebaseError>(())
169+
/// # tokio_test::block_on(async {
170+
/// let c: Credentials = Credentials::new(include_str!("../tests/service-account-test.json")).await.unwrap()
171+
/// .download_jwkset().await.unwrap();
172+
/// # })
168173
/// ```
169174
pub async fn download_jwkset(self) -> Result<Credentials, Error> {
170175
self.download_google_jwks().await?;
@@ -217,24 +222,24 @@ impl Credentials {
217222
/// use firestore_db_and_auth::credentials::Credentials;
218223
/// use firestore_db_and_auth::JWKSet;
219224
///
220-
/// let mut c : Credentials = serde_json::from_str(include_str!("../tests/service-account-test.json"))?;
221-
/// c.add_jwks_public_keys(&JWKSet::new(include_str!("../tests/service-account-test.jwks"))?);
222-
/// c.compute_secret()?;
223-
/// c.verify()?;
224-
/// # Ok::<(), firestore_db_and_auth::errors::FirebaseError>(())
225+
/// # tokio_test::block_on(async {
226+
/// let mut c : Credentials = serde_json::from_str(include_str!("../tests/service-account-test.json")).unwrap();
227+
/// c.add_jwks_public_keys(&JWKSet::new(include_str!("../tests/service-account-test.jwks")).unwrap()).await;
228+
/// c.compute_secret().await.unwrap();
229+
/// c.verify().await.unwrap();
230+
/// # })
225231
/// ```
226232
pub async fn add_jwks_public_keys(&self, jwkset: &JWKSet) {
227-
let mut keys = self.keys.write().await;
233+
let key_lock = self.keys.write();
234+
let keys = &mut key_lock.await.pub_key;
228235

229236
for entry in jwkset.keys.iter() {
230237
if !entry.headers.key_id.is_some() {
231238
continue;
232239
}
233240

234241
let key_id = entry.headers.key_id.as_ref().unwrap().to_owned();
235-
keys
236-
.pub_key
237-
.insert(key_id, Arc::new(entry.ne.jws_public_key_secret()));
242+
keys.insert(key_id, Arc::new(entry.ne.jws_public_key_secret()));
238243
}
239244
}
240245

0 commit comments

Comments
 (0)