google_auth_helpers/
service_account.rs1use std::time::SystemTime;
2
3use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
4use serde::Deserialize;
5use serde_json::Value;
6
7use crate::trait_definitions::GoogleWorkspaceCredentials;
8
9#[derive(Debug, Clone, Deserialize)]
10pub struct ServiceAccount {
11 pub client_email: String,
12 pub private_key_id: String,
13 pub private_key: String,
14 pub token_uri: String,
15}
16
17#[derive(Debug, Clone)]
18pub struct ServiceAccountGoogleWorkspaceCredentials {
19 pub scopes: &'static [&'static str],
20 pub subject: Option<String>,
21 pub credentials: ServiceAccount,
22}
23
24#[derive(serde::Serialize)]
25struct Claims<'a> {
26 iss: &'a str,
27 scope: &'a str,
28 aud: &'a str,
29 iat: u64,
30 exp: u64,
31 sub: Option<&'a str>,
32}
33
34impl ServiceAccountGoogleWorkspaceCredentials {
35 pub fn new(service_account: ServiceAccount) -> Self {
36 Self {
37 credentials: service_account,
38 scopes: &["https://www.googleapis.com/auth/admin.directory.user"],
39 subject: None,
40 }
41 }
42
43 pub fn with_subject(mut self, subject: &str) -> Self {
44 self.subject = Some(subject.to_string());
45 self
46 }
47 pub async fn token(&self) -> anyhow::Result<Value> {
48 const EXPIRE: u64 = 60 * 60;
49
50 let iat = issued_at();
51 let scope = &self.scopes.join(" ");
52 let claims = Claims {
53 iss: &self.credentials.client_email,
54 scope: scope.as_str(),
55 aud: &self.credentials.token_uri,
56 iat,
57 exp: iat + EXPIRE,
58 sub: self.subject.as_deref(),
59 };
60
61 let header = Header {
62 typ: Some("JWT".into()),
63 alg: Algorithm::RS256,
64 kid: Some(self.credentials.private_key_id.clone().into()),
65 ..Default::default()
66 };
67 let private_key = EncodingKey::from_rsa_pem(self.credentials.private_key.as_bytes())?;
68
69 let client: reqwest::RequestBuilder =
70 reqwest::Client::new().post(&self.credentials.token_uri);
71 let assertion = encode(&header, &claims, &private_key)?;
72 let req = client.json(&serde_json::json!({
73
74 "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
75 "assertion": assertion,
76 }));
77 let output = req
78 .send()
79 .await?
80 .error_for_status()?
81 .json::<Value>()
82 .await?;
83 Ok(output)
84 }
85}
86
87#[async_trait::async_trait]
88impl GoogleWorkspaceCredentials for ServiceAccountGoogleWorkspaceCredentials {
89 async fn get_access_token(&self) -> anyhow::Result<String> {
90 let token = self.token().await?;
91 let access_token = token
92 .get("access_token")
93 .ok_or(anyhow::anyhow!("Failed to retrieve access token"))?
94 .as_str()
95 .ok_or(anyhow::anyhow!("Failed to convert access token to string"))?
96 .to_owned();
97 Ok(access_token)
98 }
99}
100
101fn issued_at() -> u64 {
102 SystemTime::UNIX_EPOCH.elapsed().unwrap().as_secs() - 10
103}