#service-account #firestore #document #google #oauth #google-cloud

firestore-db-and-auth

此包允许您通过服务帐户或OAuth模拟的Google Firebase身份验证凭据轻松访问Google Firestore数据库。最低Rust版本:1.38

12个版本 (6个重大更改)

0.8.0 2024年1月22日
0.6.1 2020年12月28日
0.6.0 2020年1月22日
0.5.0 2019年9月12日
0.1.0 2019年8月21日

#107 in 身份验证

Download history 73/week @ 2024-04-07 113/week @ 2024-04-14 121/week @ 2024-04-21 111/week @ 2024-04-28 68/week @ 2024-05-05 118/week @ 2024-05-12 104/week @ 2024-05-19 95/week @ 2024-05-26 107/week @ 2024-06-02 77/week @ 2024-06-09 101/week @ 2024-06-16 71/week @ 2024-06-23 38/week @ 2024-06-30 49/week @ 2024-07-07 62/week @ 2024-07-14 76/week @ 2024-07-21

每月下载量232

MIT许可协议

135KB
2.5K SLoC

Firestore API和身份验证

Firestore Logo, Copyright by Google

Build Status Build Status

此包允许您通过服务帐户或OAuth模拟的Google Firebase身份验证凭据轻松访问Google Firestore数据库。最低Rust版本:1.38

功能

用例

  • 严格类型文档读写/查询访问
  • 云函数(Google Compute,AWS Lambda)访问Firestore

限制

  • 尚不支持监听文档/集合更改

Cargo功能

  • native-tlsdefault-tlsrustls-tls:选择这些功能之一用于加密连接(https)。rustls-tls是默认选项(将使用rustls包)。

  • rocket_supportRocket是一个Web框架。此功能启用Rocket集成并添加了一个请求守卫。只有经过Firestore Auth授权的请求才能通过此守卫。

文档操作

此包使用DTOs(数据传输对象)进行类型安全的Firestore数据库操作。

use firestore_db_and_auth::{Credentials, ServiceSession, documents, errors::Result};
use serde::{Serialize,Deserialize};

 #[derive(Serialize, Deserialize)]
 struct DemoDTO {
    a_string: String,
    an_int: u32,
    another_int: u32,
 }
 #[derive(Serialize, Deserialize)]
 struct DemoPartialDTO {
    #[serde(skip_serializing_if = "Option::is_none")]
    a_string: Option<String>,
    an_int: u32,
 }

/// 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 write method will return document metadata (including a possible generated document id)
fn write(session: &ServiceSession) -> Result<()> {
    let obj = DemoDTO { a_string: "abcd".to_owned(), an_int: 14, another_int: 10 };
    let result = documents::write(session, "tests", Some("service_test"), &obj, documents::WriteOptions::default())?;
    println!("id: {}, created: {}, updated: {}", result.document_id, result.create_time.unwrap(), result.update_time.unwrap());
    Ok(())
}

/// Only write some fields and do not overwrite the entire document.
/// Either via Option<> or by not having the fields in the structure, see DemoPartialDTO.
fn write_partial(session: &ServiceSession) -> Result<()> {
    let obj = DemoPartialDTO { a_string: None, an_int: 16 };
    let result = documents::write(session, "tests", Some("service_test"), &obj, documents::WriteOptions{merge:true})?;
    println!("id: {}, created: {}, updated: {}", result.document_id, result.create_time.unwrap(), result.update_time.unwrap());
    Ok(())
}

读取"tests"集合中ID为"service_test"的文档

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

要列出"tests"集合中的所有文档,您需要使用list方法,它实现了一个异步流。这隐藏了分页API的复杂性,并在必要时获取新文档。

use firestore_db_and_auth::{documents};

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: DemoDTO = doc;
    println!("{:?}", doc);
}

注意:结果列表或游标是具有有限生命期的快照视图。您不能长时间保留迭代器/流,也不能期望在持续迭代中出现新文档。

要查询数据库,您将使用query方法。以下示例中,查询"tests"集合,查找"ID"字段等于"Sam Weiss"的文档。

use firestore_db_and_auth::{documents, dto};

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
    // The data is wrapped in a Result<> because fetching new data could have failed
    let doc : DemoDTO = documents::read_by_name(&session, metadata.name.as_ref().unwrap())?;
    println!("{:?}", doc);
}

你注意到 into 吗?在 "Sam Weiss".into() 上?Firestore 以强类型存储文档字段。查询值可以是字符串、整数、浮点数,甚至可能是数组或对象(未测试)。

注意:查询方法返回一个向量,因为查询可能会返回多个匹配的文档。

错误处理

返回的 Result 在任何错误情况下都将设置一个 FirebaseError。这种自定义错误类型封装了所有可能的错误(IO、Reqwest、JWT 错误等)以及 Google REST API 错误。如果您想特别检查 API 错误,可以这样做

use firestore_db_and_auth::{documents, errors::FirebaseError};

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);
        assert!(message.contains("No document to update"));
        assert_eq!(context, "tests/non_existing");
    }
}

代码是数字的,消息是 Google 服务器返回的消息。上下文字符串取决于调用方法。可能是集合或文档 ID 或其他任何上下文信息。

通过服务帐户访问文档

  1. 下载服务帐户凭据文件并将其存储为 "firebase-service-account.json"。文件应包含 "private_key_id": ...
  2. 添加另一个字段 "api_key" : "YOUR_API_KEY" 并将 YOUR_API_KEY 替换为您的 Web API 密钥,可在 Google Firebase 控制台 的 "项目概览 -> 设置 -> 通用" 中找到。
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").await
    .expect("Read credentials file")
    .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.
let session = ServiceSession::new(&cred)
    .expect("Create a service account session");

通过 Firebase 用户访问令牌/刷新令牌或用户 ID 访问文档

您可以通过多种方式创建用户会话。如果您只有 firebase Auth 用户_id,您将遵循以下步骤

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").await
    .expect("Read credentials file")
    .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").await
    .expect("Create a user session");

如果您已经有一个有效的刷新令牌并想生成一个访问令牌(以及一个会话对象),您将这样做

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

获取会话对象的另一种方式是提供有效的访问令牌,如下所示

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

by_access_token 方法将失败,如果令牌不再有效。请注意,以这种方式创建的会话无法自动刷新其访问令牌。(没有与之关联的 refresh_token。)

云函数:改善冷启动时间

通常的启动程序包括三个 IO 操作

  • 从 Google 服务器下载两个公共 jwks 密钥,
  • 并读取 json 凭据文件。

通过将凭据和公钥文件嵌入到您的应用程序中,避免这些操作。

首先下载 2 个公钥文件

创建一个 Credentials 对象,如下所示

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

请注意,Google 的 JWK 密钥定期更改。您可能希望大约每三周重新部署服务,并使用新的公钥。

对于长期运行的服务,您需要定期调用 Credentials::download_google_jwks()。

更多信息

测试

要执行完整的集成测试(cargo test),您需要一个有效的“firebase-service-account.json”文件。测试期望一个ID在examples/test_user_id.txt中给出的Firebase用户存在。更多信息

如何使这个crate更加出色

这个库没有雄心去完全映射http/gRPC API 1:1。为此有自动生成的库。但是以下内容符合crates架构

  • 期望的功能:Firestore的批量获取支持、事务处理

依赖

~14–48MB
~1M SLoC