game_manager_lib/utils/oauth/
core.rs1use crate::constants::OAUTH_CALLBACK_TIMEOUT_SECS;
7use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
8use rand::{distr::Alphanumeric, RngExt};
9use sha2::{Digest, Sha256};
10use std::{sync::mpsc, thread, time::Duration};
11use tiny_http::{Response, Server};
12use url::Url;
13
14pub struct PkceChallenge {
16 pub verifier: String,
17 pub challenge: String,
18}
19
20pub struct AuthCallbackResult {
22 pub code: String,
23 pub state: Option<String>,
24}
25
26impl PkceChallenge {
27 pub fn generate() -> Self {
28 let verifier: String = rand::rng()
29 .sample_iter(&Alphanumeric)
30 .take(64)
31 .map(char::from)
32 .collect();
33
34 let hash = Sha256::digest(verifier.as_bytes());
35 let challenge = URL_SAFE_NO_PAD.encode(hash);
36
37 Self {
38 verifier,
39 challenge,
40 }
41 }
42}
43
44pub fn generate_state() -> String {
46 rand::rng()
47 .sample_iter(&Alphanumeric)
48 .take(32)
49 .map(char::from)
50 .collect()
51}
52
53pub fn wait_for_auth_code(port: u16) -> Result<AuthCallbackResult, String> {
56 let address = format!("127.0.0.1:{}", port);
57 let server =
59 Server::http(&address).map_err(|e| format!("Porta ocupada ou erro de rede: {}", e))?;
60
61 let (tx, rx) = mpsc::channel::<AuthCallbackResult>();
62
63 thread::spawn(move || {
64 if let Ok(Some(request)) =
66 server.recv_timeout(Duration::from_secs(OAUTH_CALLBACK_TIMEOUT_SECS))
67 {
68 let url_string = format!("http://localhost{}", request.url());
70 if let Ok(url) = Url::parse(&url_string) {
71 if let Some((_, code)) = url.query_pairs().find(|(k, _)| k == "code") {
73 let state = url
74 .query_pairs()
75 .find(|(k, _)| k == "state")
76 .map(|(_, v)| v.to_string());
77 let _ = tx.send(AuthCallbackResult {
78 code: code.to_string(),
79 state,
80 });
81
82 let html = "
84 <html>
85 <body style='background:#1a1a1a; color:#fff; font-family:sans-serif; text-align:center; padding-top:50px;'>
86 <h1 style='color:#4ade80'>Login Concluído!</h1>
87 <p>Você já pode fechar esta janela e voltar para o Playlite.</p>
88 <script>window.close();</script>
89 </body>
90 </html>
91 ";
92 let _ = request.respond(
93 Response::from_string(html).with_header(
94 tiny_http::Header::from_bytes(&b"Content-Type"[..], &b"text/html"[..])
95 .unwrap(),
96 ),
97 );
98 return;
99 }
100 }
101 let _ = request.respond(Response::from_string("Erro: Código não encontrado."));
103 }
104 });
105
106 rx.recv_timeout(Duration::from_secs(OAUTH_CALLBACK_TIMEOUT_SECS))
108 .map_err(|_| "Tempo limite de login excedido.".to_string())
109}