rs_firebase_admin_sdk/credentials/
mod.rs

1//! OAuth2 credential managers for GCP and Firebase Emulator
2
3pub mod emulator;
4
5use error_stack::{Report, ResultExt};
6use google_cloud_auth::credentials::{CacheableResource, Credentials};
7use headers::HeaderMapExt;
8use headers::{Header, HeaderName, HeaderValue};
9use http::{Extensions, HeaderMap};
10use std::env::var;
11
12#[derive(thiserror::Error, Debug, Clone)]
13#[error("Failed to extract GCP credentials")]
14pub struct GCPCredentialsError;
15
16static X_GOOG_USER_PROJECT: HeaderName = HeaderName::from_static("x-goog-user-project");
17
18pub struct GoogleUserProject(String);
19
20impl Header for GoogleUserProject {
21    fn name() -> &'static HeaderName {
22        &X_GOOG_USER_PROJECT
23    }
24
25    fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
26    where
27        I: Iterator<Item = &'i HeaderValue>,
28    {
29        let value = values
30            .next()
31            .ok_or_else(headers::Error::invalid)?
32            .as_bytes();
33
34        match std::str::from_utf8(value) {
35            Ok(v) => Ok(Self(v.into())),
36            Err(_) => Err(headers::Error::invalid()),
37        }
38    }
39
40    fn encode<E>(&self, values: &mut E)
41    where
42        E: Extend<HeaderValue>,
43    {
44        let value = HeaderValue::from_str(&self.0).unwrap_or_else(|_| HeaderValue::from_static(""));
45
46        values.extend(std::iter::once(value));
47    }
48}
49
50pub(crate) async fn get_project_id(
51    creds: &Credentials,
52) -> Result<String, Report<GCPCredentialsError>> {
53    if let Ok(project_id) = var("GOOGLE_CLOUD_PROJECT") {
54        return Ok(project_id);
55    }
56
57    let headers = get_headers(creds).await?;
58
59    let user_project: GoogleUserProject = headers
60        .typed_get()
61        .ok_or(Report::new(GCPCredentialsError))?;
62
63    Ok(user_project.0)
64}
65
66pub(crate) async fn get_headers(
67    creds: &Credentials,
68) -> Result<HeaderMap, Report<GCPCredentialsError>> {
69    let headers = creds
70        .headers(Extensions::new())
71        .await
72        .change_context(GCPCredentialsError)?;
73
74    let headers = match headers {
75        CacheableResource::New {
76            entity_tag: _,
77            data,
78        } => data,
79        _ => unreachable!(),
80    };
81
82    Ok(headers)
83}