use std::time::Duration; use thiserror::Error; use tokio::{ sync::broadcast::{Receiver, Sender}, time::sleep, }; use crate::{ config::{Config, ConfigError}, models::*, pipe::Pipe, state_machine::Event, BASE_URL, CLIENT_ID, REDIRECT_URI, }; #[derive(Debug, Error)] pub enum OpenBrowserError { #[error(transparent)] SerdeQs(#[from] serde_qs::Error), #[error(transparent)] Io(#[from] std::io::Error), #[error(transparent)] Config(#[from] ConfigError), } pub fn open_browser(config: Config) -> Result<(), OpenBrowserError> { let code_verifier = CodeVerifier::new(); let code_challenge_method = CodeChallengeMethod::Sha256; config.set_code_verifier(Some(code_verifier.clone()))?; config.set_code_challenge_method(Some(code_challenge_method.clone()))?; let code_challenge = match code_challenge_method { CodeChallengeMethod::Plain => { use base64::prelude::*; BASE64_URL_SAFE_NO_PAD.encode(code_verifier.to_string()) } CodeChallengeMethod::Sha256 => { use base64::prelude::*; BASE64_URL_SAFE_NO_PAD.encode(sha256::digest(code_verifier.to_string())) } }; let request = AuthorizeRequest::new( CLIENT_ID, ResponseType::Code, None, code_challenge.clone(), Some(code_challenge_method.clone()), RedirectUri::new(REDIRECT_URI), None, ); let qs = serde_qs::to_string(&request)?; open::that(format!("{}/oauth2/authorize?{}", BASE_URL, qs))?; Ok(()) } pub async fn start_code_listener( pipe_sender: Sender, event_receiver: Receiver, ) -> Result<(), anyhow::Error> { let pipe = Pipe::new(event_receiver.resubscribe()); pipe.listen(pipe_sender).await?; Ok(()) } pub async fn start_code_to_token( config: Config, mut pipe_receiver: Receiver, event_sender: Sender, mut event_receiver: Receiver, ) -> Result<(), anyhow::Error> { loop { tokio::select! { _ = sleep(Duration::from_millis(100)) => { if let Ok(Event::Quit) = event_receiver.try_recv() { tracing::info!("Shutting down Code Transformer"); break; } } Ok(response) = pipe_receiver.recv() => { let r = AuthorizationCodeRequest::new( GrantType::AuthorizationCode, response.code(), REDIRECT_URI.into(), CLIENT_ID, config.code_verifier().unwrap() ); let qs = serde_qs::to_string(&r)?; let client = reqwest::Client::new(); let response = client .post(format!("{}/oauth2/token", BASE_URL)) .body(qs) .send() .await?; let response: AuthorizationCodeResponse = response.json().await?; let token = response.token(); config.set_token(Some(token.clone()))?; event_sender.send(Event::TokenReceived)?; } } } tracing::info!("Code Transformer Shutdown"); Ok(()) }