google_api_auth/
lib.rs

1use models::{AuthResponse, Claims, ServiceAccountKey, ServiceCredentialsInput};
2use reqwest::blocking::Client;
3use std::{fs, time::{SystemTime, UNIX_EPOCH}};
4use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
5
6mod models;
7
8fn create_jwt(key: &ServiceAccountKey, scopes: Vec<String>) -> String {
9    let iat = SystemTime::now()
10        .duration_since(UNIX_EPOCH)
11        .expect("Time went backwards")
12        .as_secs() as usize;
13    let exp = iat + 3600;
14
15    let scopes = {
16        let count = scopes.iter().count();
17        if count > 1 {
18            let mut scopes_string = scopes.iter().take(count - 1).map(|x| x.to_string() + ",").collect::<String>();
19            scopes_string.push_str(&scopes[count - 1]);
20            scopes_string
21        }
22        else {
23            let scope = scopes[0].clone();
24            drop(scopes);
25            scope
26        }
27    };
28
29    let claims = Claims {
30        iss: key.client_email.clone(),
31        scope: scopes,
32        aud: key.token_uri.clone(),
33        exp,
34        iat,
35    };
36
37    let encoding_key = EncodingKey::from_rsa_pem(key.private_key.as_bytes()).expect("Invalid RSA Key");
38    encode(&Header::new(Algorithm::RS256), &claims, &encoding_key).expect("JWT Encoding failed")
39}
40
41fn get_access_token(key: &ServiceAccountKey, scopes: Vec<String>) -> AuthResponse {
42    let jwt = create_jwt(key, scopes);
43
44    let params = format!("grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion={}",jwt);
45
46    let client = Client::new();
47    
48    let res = client.post(format!("https://oauth2.googleapis.com/token"))
49        .header("content-type", "application/x-www-form-urlencoded")
50        .body(params)
51        .send()
52        .expect("Failed to get access token");
53
54    let content = res.text();
55
56    let token_response: AuthResponse = serde_json::from_str(
57        &content.expect("Failed to get text out of response.")
58    ).expect("Failed to parse token response");
59    token_response
60}
61
62/// Authentication handler for storing json credentials and requesting new access_token then necessary.
63/// 
64/// More details: https://developers.google.com/identity/protocols/oauth2/service-account
65/// 
66/// ```rust
67/// //Example if json credentials are stored at the same directory where the program is contained.
68/// let mut dir = env::current_exe().unwrap();
69/// dir.pop();
70/// dir.push("some-name-431008-92e3a679a62f.json");
71/// 
72/// let json_string = json!({
73///     "type": "service_account",
74///     "project_id": "some-name-0000000",
75///     "private_key_id": "somerandomuuid000000000",
76///     "private_key": "-----BEGIN PRIVATE KEY-----\n SOME CERT DATA \n-----END PRIVATE KEY-----\n",
77///     "client_email": "some-name@some-account-0000000.iam.gserviceaccount.com",
78///     "client_id": "000000000000000",
79///     "auth_uri": "https://accounts.google.com/o/oauth2/auth",
80///     "token_uri": "https://oauth2.googleapis.com/token",
81///     "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
82///     "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/some-account.iam.gserviceaccount.com",
83///     "universe_domain": "googleapis.com"
84/// }).to_string();
85/// 
86/// //Create the handler.
87/// let handler = AuthenticationHandler::new(dir.into());
88/// 
89/// //Handler using json `String`
90/// let handler_2 = AuthenticationHandler::new(json_string.into());
91/// 
92/// //Get a token with scoped read / write access to GCP DNS API.
93/// let token = handler.get_access_token_model(
94///     vec!["https://www.googleapis.com/auth/ndev.clouddns.readwrite".into()]);
95/// 
96/// println!("Access Token: {}", token.access_token);
97/// ```
98pub struct AuthenticationHandler {
99    service_credentials: ServiceAccountKey
100}
101
102impl AuthenticationHandler {
103    /// Creates new `AuthenticationHandler`. Requires a `PathBuf` or json `String` containing the service account credentials (key).
104    pub fn new(creds: ServiceCredentialsInput) -> AuthenticationHandler {
105        match creds {
106            ServiceCredentialsInput::PathBuf(creds) => {
107                let key_data = fs::read_to_string(creds)
108                    .expect("Failed to read service account key file");
109                let service_account_key: ServiceAccountKey = serde_json::from_str(&key_data)
110                    .expect("Failed to parse service account key");
111                AuthenticationHandler {
112                    service_credentials: service_account_key
113                }
114            },
115            ServiceCredentialsInput::String(creds) => {
116                let service_account_key: ServiceAccountKey = serde_json::from_str(&creds)
117                    .expect("Failed to parse service account key");
118                AuthenticationHandler {
119                    service_credentials: service_account_key
120                }
121            }
122        }
123    }
124
125    /// Creates new `access_token` with specific access. Please for complete scopes list refer to: `https://developers.google.com/identity/protocols/oauth2/scopes`. Make sure to give the respective access /role to the service account. 
126    pub fn get_access_token_model(&self, scopes: Vec<String>) -> AuthResponse {
127        let token = get_access_token(&self.service_credentials, scopes);
128        token
129    }
130}