google_api_service.rs
1 use crate::util::config::GoogleEmailConfig; 2 use base64::engine::general_purpose::STANDARD; 3 use base64::Engine; 4 use chrono::{Duration, Utc}; 5 use jwt::{PKeyWithDigest, SignWithKey}; 6 use openssl::hash::MessageDigest; 7 use openssl::pkey::PKey; 8 use serde::{Deserialize, Serialize}; 9 use std::collections::BTreeMap; 10 11 #[derive(Debug, thiserror::Error)] 12 pub enum GoogleApiError { 13 #[error("OpenSSL error stack")] 14 OpenSSLError(#[from] openssl::error::ErrorStack), 15 #[error("JWT error")] 16 JwtError(#[from] jwt::Error), 17 #[error("Reqwest error `{0}`")] 18 ReqwestError(#[from] reqwest::Error), 19 #[error("Google api error")] 20 GoogleApiError, 21 } 22 23 const GRANT_TYPE_SERVICE_ACCOUNT: &str = "urn:ietf:params:oauth:grant-type:jwt-bearer"; 24 #[derive(Serialize, Deserialize)] 25 struct GoogleAuthRequest { 26 grant_type: String, 27 assertion: String, 28 } 29 30 impl GoogleAuthRequest { 31 fn new(assertion: String) -> Self { 32 Self { 33 grant_type: GRANT_TYPE_SERVICE_ACCOUNT.to_string(), 34 assertion, 35 } 36 } 37 } 38 39 const GMAIL_SEND_EMAIL_SCOPE: &str = "https://www.googleapis.com/auth/gmail.send"; 40 41 const GOOGLE_AUD_VALUE: &str = "https://oauth2.googleapis.com/token"; 42 43 fn create_jwt(config: &GoogleEmailConfig) -> Result<String, GoogleApiError> { 44 let private_key = PKey::private_key_from_pem(config.service_account.private_key.as_bytes())?; 45 let key_with_digest = PKeyWithDigest { 46 digest: MessageDigest::sha256(), 47 key: private_key, 48 }; 49 50 let mut claims: BTreeMap<&str, &str> = BTreeMap::new(); 51 52 claims.insert("iss", &config.service_account.client_email); 53 claims.insert("scope", GMAIL_SEND_EMAIL_SCOPE); 54 claims.insert("aud", GOOGLE_AUD_VALUE); 55 56 let now = Utc::now(); 57 let now_timestamp = now.timestamp().to_string(); 58 claims.insert("iat", &now_timestamp); 59 60 let exp_time = now + Duration::hours(1); 61 let exp_time_timestamp = exp_time.timestamp().to_string(); 62 claims.insert("exp", &exp_time_timestamp); 63 claims.insert("sub", &config.send_from_email_address); 64 65 Ok(claims.sign_with_key(&key_with_digest)?) 66 } 67 68 #[derive(Serialize, Deserialize)] 69 struct GoogleAuthResponse { 70 access_token: String, 71 expires_in: u32, 72 token_type: String, 73 } 74 75 pub async fn retrieve_token(config: &GoogleEmailConfig) -> Result<String, GoogleApiError> { 76 let jwt = create_jwt(config)?; 77 78 let client = reqwest::Client::new(); 79 80 // GoogleAuthResponse 81 let response_text = client 82 .post(&config.service_account.token_uri) 83 .form(&GoogleAuthRequest::new(jwt)) 84 .send() 85 .await? 86 .text() 87 .await?; 88 89 let response: GoogleAuthResponse = serde_json::from_str(&response_text).map_err(|_| { 90 println!( 91 "GOOGLE API ERR: Failed to retrieve token, err: {}", 92 response_text 93 ); 94 GoogleApiError::GoogleApiError 95 })?; 96 97 Ok(response.access_token) 98 } 99 100 #[derive(Serialize, Deserialize)] 101 struct GoogleSendEmailRequest { 102 raw: String, 103 } 104 105 impl GoogleSendEmailRequest { 106 fn new(from: &str, to: &str, subject: &str, content: &str) -> Self { 107 let raw_message = STANDARD.encode(format!( 108 "From: {}\r\nTo: {}\r\nSubject: {}\r\n\r\n{}\r\n", 109 from, to, subject, content 110 )); 111 Self { raw: raw_message } 112 } 113 } 114 115 // Note: the `/me/` is a parameter for the CLIENT_ID 116 const SEND_EMAIL_ENDPOINT: &str = "https://gmail.googleapis.com/gmail/v1/users/me/messages/send"; 117 118 #[derive(Serialize, Deserialize)] 119 #[allow(dead_code)] 120 #[serde(rename_all = "camelCase")] 121 struct GoogleSendEmailResponse { 122 id: String, 123 thread_id: String, 124 label_ids: Vec<String>, 125 } 126 127 pub async fn send_email( 128 receiver_email: &str, 129 subject: &str, 130 content: &str, 131 token: &str, 132 config: &GoogleEmailConfig, 133 ) -> Result<(), GoogleApiError> { 134 let send_email_request = GoogleSendEmailRequest::new( 135 &config.send_from_email_address, 136 receiver_email, 137 subject, 138 content, 139 ); 140 141 let client = reqwest::Client::new(); 142 let response_text = client 143 .post(SEND_EMAIL_ENDPOINT) 144 .query(&[("alt", "json"), ("prettyPrint", "false")]) 145 .header(reqwest::header::AUTHORIZATION, format!("Bearer {}", token)) 146 .json(&send_email_request) 147 .send() 148 .await? 149 .text() 150 .await?; 151 152 let _response: GoogleSendEmailResponse = 153 serde_json::from_str(&response_text).map_err(|_| { 154 println!( 155 "GOOGLE API ERR: Failed to send email, err: {}", 156 response_text 157 ); 158 GoogleApiError::GoogleApiError 159 })?; 160 161 Ok(()) 162 }