Firestore API and Auth
This crate allows easy access to your Google Firestore DB via service account or OAuth impersonated Google Firebase Auth credentials.
Features:
- Subset of the Firestore v1 API
- Optionally handles authentication and token refreshing for you
- Support for the downloadable Google service account json file from Google Clound console. (See https://cloud.google.com/storage/docs/reference/libraries#client-libraries-install-cpp)
Usecases:
- Strictly typed document read/write/query access
- Cloud functions (Google Compute, AWS Lambda) access to Firestore
Limitations:
- Listening to document / collection changes is not yet possible
Document operations
This crate operates on DTOs (Data transfer objects) for type-safe operations on your Firestore DB.
use ;
/// A document structure for demonstration purposes
let obj = DemoDTO ;
/// Write the given object with the document id "service_test" to the "tests" collection.
/// You do not need to provide a document id (use "None" instead) and let Firestore generate one for you.
///
/// In either way a document is created or updated (overwritten).
///
/// The method will return document metadata (including a possible generated document id)
let result = write?;
println!;
Read the document with the id "service_test" from the Firestore "tests" collection:
let obj : DemoDTO = read?;
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:
let values: List = list;
for doc_result in values
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.
For quering 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".
let objs : = query?;
Note: The query method returns a vector, because a query potentially returns multiple matching documents.
Document access via service account
- Download the service accounts credentials file and store it as "firebase-service-account.json".
The file should contain
"private_key_id": ...
. - 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 in "Project Overview -> Settings - > General".
use ;
/// Create credentials object. You may as well do that programmatically.
let cred = from_file
.expect;
/// 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.
let mut session = new
.expect;
Mutable session variable?: Access (bearer) tokens have a limited lifetime, usually about an hour. They need to be refreshed via a refresh token, which is also part of the session object. When you perform a call to an API, the session will automatically refresh your access token if necessary, and therefore requires the session object to be mutable.
Document access via a firebase user access / refresh token or via user_id
You can create a user session in various ways. If you just have the firebase Auth user_id, you would follow these steps:
use ;
/// Create credentials object. You may as well do that programmatically.
let cred = from_file
.expect;
/// 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 mut session = by_user_id
.expect;
If you have a valid refresh token already and want to generate an access token (and a session object), you do this instead:
let refresh_token = "fkjandsfbajsbfd;asbfdaosa.asduabsifdabsda,fd,a,sdbasfadfasfas.dasdasbfadusbflansf";
let mut session = by_refresh_token?;
The last way to retrieve a session object is by providing a valid access token like so:
let access_token = "fkjandsfbajsbfd;asbfdaosa.asduabsifdabsda,fd,a,sdbasfadfasfas.dasdasbfadusbflansf";
let mut session = by_access_token?;
The by_access_token
method will fail if the token is not valid anymore.
Please note that a session created this way is not able to automatically refresh its access token.
There is no refresh_token associated with it.
Firestore Auth: Background information
JWT: Firestore Auth makes use of the OAuth Grant Code Flow and uses JWTs (Json web Tokens) as access tokens. Such a token is signed by Google and consists of a few encoded fields including a valid-until field. This allows to verify access tokens locally without any database access.
The Firebase API requires an access token, it accepts two types:
- A custom created JWT, signed with the private key of a Google service account
- An access token from Firestore Auth, bound to a user (in this crate called "user session")
If you do not have an user session access token, but you need to perform an action
impersonated, this crate offers Session::by_user_id
. This will again create a custom, signed JWT,
like with option 1, but exchanges this JWT for a refresh token and access token tuple.
The actual database operation will be performed with those tokens.
About token validation:
Validation happens via the public keys of the corresponding Google service account (https://www.googleapis.com/service_accounts/v1/jwk/service.account@address).
The public keys are downloaded and cached the very first time you create a credentials::Credentials
object.
To avoid this roundtrip on start it is strongly recommended to serialize the credentials object to disk. Find more information further down.
Use your own authentication implementation
You do not need the sessions
module for using the Firestore API of this crate.
All Firestore methods in documents
expect an object that implements the FirebaseAuthBearer
trait.
That trait looks like this:
Just implement this trait for your own data structure and provide the Firestore project id and a valid access token.
Http Rocket Server integration
Because the sessions
module of this crate is already able to verify access tokens,
it was not much more work to turn this into a Rocket 0.4+ Guard.
The implemented Guard (enabled by the feature "rocket_support") allows access to http paths if the provided http "Authorization" header contains a valid "Bearer" token. The above mentioned validations on the token are performed.
Example usage:
use ;
/// And an example route could be:
Usage in cloud functions
The start up time is crucial for cloud functions. The usual start up procedure includes three IO operations:
- downloading the two public jwks keys from a Google server,
- and read in the json credentials file.
Avoid those by embedding the credentials and public key files into your application.
First download the 2 public key files:
- https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com -> Store as
securetoken.jwks
- https://www.googleapis.com/service_accounts/v1/jwk/{your-service-account-email} -> Store as
service-account.jwks
Create a Credentials
object like so:
let mut c : Credentials = from_str?
c.add_jwks_public_keys;
c.add_jwks_public_keys;
Testing
To perform a full integration test, you need a valid "firebase-service-account.json" file. The tests will create a Firebase user with the ID "Io2cPph06rUWM3ABcIHguR3CIw6v1" and write and read a document to/from "tests/test".
If you are using firebase rules (recommended!), please ensure that the mentioned user id has access to the "tests" collection.
A refresh and access token is generated. The refresh token is stored in "refresh-token-for-tests.txt" and will be reused for further tests. The reason being that Google allows only about 50 simultaneous refresh tokens at any time, so we do not want to create a new one each test run.
If the tests run through with your "firebase-service-account.json" file, you are correctly setup and ready to use this library.
Start test runs with cargo test
as usual.
Further development
Maintenance status: Stable
What can be done to make this crate more awesome:
- Data streaming via gRPC/Protobuf
- Expose more DTOs (Data transfer objects) and convenience methods.
- Nice to have: Transactions, batch_get support for Firestore
This library does not have the ambition to mirror the http/gRPC API 1:1. There are auto-generated libraries for this purpose.