mod.rs (7887B)
1 use std::{ 2 fs::{self, OpenOptions}, 3 net::SocketAddr, 4 path::{Path, PathBuf}, 5 }; 6 7 use crate::{ 8 errors::Error, 9 google_calendar::types::{Calendars, ClientSecrets}, 10 }; 11 use axum::{extract, handler::get, http::StatusCode, Router}; 12 use matrix_sdk::events::{room::message::MessageEventContent, AnyMessageEventContent}; 13 use oauth2::{ 14 basic::{BasicClient, BasicTokenResponse}, 15 reqwest::async_http_client, 16 AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, PkceCodeChallenge, RedirectUrl, 17 RevocationUrl, Scope, TokenResponse, TokenUrl, 18 }; 19 use reqwest::{header, ClientBuilder}; 20 use serde::Deserialize; 21 use tracing::info; 22 23 pub mod types; 24 25 trait LoadTokenResponse { 26 fn get_from_file(mxid: String) -> Option<BasicTokenResponse>; 27 fn save_to_file(&self, mxid: String); 28 } 29 30 impl LoadTokenResponse for BasicTokenResponse { 31 fn get_from_file(mxid: String) -> Option<BasicTokenResponse> { 32 let path: PathBuf = format!("./tokens/{}.json", mxid).into(); 33 let file = std::fs::File::open(path); 34 match file { 35 Ok(file) => { 36 let clientsecrets: Result<Self, serde_json::Error> = serde_json::from_reader(&file); 37 match clientsecrets { 38 Ok(clientsecrets) => Some(clientsecrets), 39 Err(_) => None, 40 } 41 } 42 Err(_) => None, 43 } 44 } 45 46 fn save_to_file(&self, mxid: String) { 47 use std::io::Write; 48 49 let mut path = Path::new("./tokens/").to_path_buf(); 50 if !path.exists() { 51 fs::create_dir_all(&path).unwrap(); 52 } 53 path.push(format!("{}.json", mxid)); 54 let mut file = OpenOptions::new() 55 .write(true) 56 .create(true) 57 .open(path) 58 .unwrap(); 59 let j = serde_json::to_string(self).unwrap(); 60 file.write_all(j.as_bytes()).unwrap(); 61 } 62 } 63 64 pub async fn login(mxid: String, tx: mrsbfh::Sender) -> Result<(), Error> { 65 let loaded = BasicTokenResponse::get_from_file(mxid.clone()); 66 if let Some(token) = loaded { 67 crate::GOOGLE_SESSIONS 68 .write() 69 .await 70 .insert(mxid.clone(), token); 71 }; 72 73 let reader = crate::GOOGLE_SESSIONS.read().await; 74 if let Some(token) = reader.get(&mxid) { 75 if let Some(expiry) = token.expires_in() { 76 if expiry.as_secs() != 0 { 77 return Ok(()); 78 } else { 79 // TODO do refresh flow 80 let _lalalala = ""; 81 return Ok(()); 82 } 83 } 84 } 85 86 // Create an OAuth2 client by specifying the client ID, client secret, authorization URL and 87 // token URL. 88 if let Some(secrets) = ClientSecrets::load_clientsecrets() { 89 let redirect_uri = if let Some(uri) = secrets.web.redirect_uris.first() { 90 uri.clone() 91 } else { 92 String::from("http://localhost") 93 }; 94 let client = BasicClient::new( 95 ClientId::new(secrets.web.client_id.clone()), 96 Some(ClientSecret::new(secrets.web.client_secret.clone())), 97 AuthUrl::new(secrets.web.auth_uri.clone())?, 98 Some(TokenUrl::new(secrets.web.token_uri.clone())?), 99 ) 100 // Google supports OAuth 2.0 Token Revocation (RFC-7009) 101 .set_revocation_uri( 102 RevocationUrl::new("https://oauth2.googleapis.com/revoke".to_string()) 103 .expect("Invalid revocation endpoint URL"), 104 ) 105 // Set the URL the user will be redirected to after the authorization process. 106 .set_redirect_uri(RedirectUrl::new(redirect_uri)?); 107 108 // Generate a PKCE challenge. 109 let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256(); 110 111 // Generate the full authorization URL. 112 let (auth_url, csrf_token) = client 113 .authorize_url(CsrfToken::new_random) 114 // Set the desired scopes. 115 .add_scope(Scope::new( 116 "https://www.googleapis.com/auth/calendar.readonly".to_string(), 117 )) 118 .add_scope(Scope::new( 119 "https://www.googleapis.com/auth/calendar".to_string(), 120 )) 121 .add_scope(Scope::new( 122 "https://www.googleapis.com/auth/calendar.events".to_string(), 123 )) 124 .add_scope(Scope::new( 125 "https://www.googleapis.com/auth/calendar.events.readonly".to_string(), 126 )) 127 .add_scope(Scope::new( 128 "https://www.googleapis.com/auth/calendar.settings.readonly".to_string(), 129 )) 130 // Set the PKCE code challenge. 131 .set_pkce_challenge(pkce_challenge) 132 .url(); 133 134 { 135 crate::STATE_TO_MXID 136 .write() 137 .await 138 .insert(csrf_token.secret().clone(), mxid.clone()); 139 crate::LOGIN_SESSIONS_VERIFIER 140 .write() 141 .await 142 .insert(mxid, (client, pkce_verifier, csrf_token)); 143 }; 144 145 let content = 146 AnyMessageEventContent::RoomMessage(MessageEventContent::notice_plain(format!( 147 "Please login using this URL: {}\n\nAfterwards rerun the command!", 148 auth_url 149 ))); 150 tx.send(content).await?; 151 } 152 153 Ok(()) 154 } 155 156 pub async fn start_webserver() { 157 let app = Router::new().route("/", get(root)); 158 let addr = SocketAddr::from(([127, 0, 0, 1], 55555)); 159 tracing::debug!("listening on {}", addr); 160 axum::Server::bind(&addr) 161 .serve(app.into_make_service()) 162 .await 163 .unwrap(); 164 } 165 166 async fn root(query_params: extract::Query<GoogleResponse>) -> (StatusCode, &'static str) { 167 let mut writer = crate::LOGIN_SESSIONS_VERIFIER.write().await; 168 let reader = crate::STATE_TO_MXID.read().await; 169 let mxid = reader.get(&query_params.state.clone()).unwrap(); 170 if let Some((client, pkce_verifier, crsftoken)) = writer.remove(mxid) { 171 let request_state = CsrfToken::new(query_params.state.clone()); 172 let auth_code = AuthorizationCode::new(query_params.code.clone()); 173 174 if request_state.secret() == crsftoken.secret() { 175 let token_result = client 176 .exchange_code(auth_code) 177 // Set the PKCE code verifier. 178 .set_pkce_verifier(pkce_verifier) 179 .request_async(async_http_client) 180 .await; 181 if let Ok(token) = token_result { 182 info!("{}", token.access_token().secret()); 183 let token: BasicTokenResponse = token; 184 token.save_to_file(mxid.clone()); 185 crate::GOOGLE_SESSIONS 186 .write() 187 .await 188 .insert(mxid.clone(), token); 189 return (StatusCode::OK, "Success"); 190 } 191 } 192 } 193 194 (StatusCode::INTERNAL_SERVER_ERROR, "Unable to authenticate") 195 } 196 197 #[derive(Deserialize)] 198 struct GoogleResponse { 199 code: String, 200 state: String, 201 } 202 203 pub async fn list_calendars(mxid: String) -> Result<Calendars, crate::errors::Error> { 204 let reader = crate::GOOGLE_SESSIONS.read().await; 205 let auth_token = reader.get(&mxid).unwrap(); 206 let access_token = auth_token.access_token(); 207 208 info!("Got token"); 209 210 let mut headers = header::HeaderMap::new(); 211 let auth_token_val = 212 header::HeaderValue::from_str(&format!("Bearer {}", access_token.secret())).unwrap(); 213 headers.insert("Authorization", auth_token_val); 214 let client = ClientBuilder::new() 215 .default_headers(headers) 216 .build() 217 .unwrap(); 218 219 info!("Got client"); 220 221 let res = client 222 .get("https://www.googleapis.com/calendar/v3/users/me/calendarList") 223 .send() 224 .await? 225 .json::<Calendars>() 226 .await?; 227 228 info!("Got response"); 229 Ok(res) 230 }