Skip to main content

game_manager_lib/utils/oauth/
token_store.rs

1//! Armazenamento persistente de tokens OAuth2 por provedor.
2//!
3//! Reaproveita a infraestrutura de secrets já existente (`secrets.db` +
4//! AES-256-GCM via `security::encrypt`/`decrypt`), guardando cada token
5//! serializado em JSON sob a chave `oauth_<provider_id>` na tabela
6//! `encrypted_keys`. Nenhuma tabela ou schema novo é necessário.
7
8use crate::database::core::{delete_secret, get_secret, set_secret};
9use crate::errors::AppError;
10use crate::utils::oauth::config::{refresh_access_token, OAuthProviderConfig, OAuthToken};
11use tauri::AppHandle;
12
13/// Prefixo usado para não colidir com as demais chaves de `encrypted_keys` (steam_api_key, rawg_api_key, etc.).
14fn secret_key_for(provider_id: &str) -> String {
15    format!("oauth_{provider_id}")
16}
17
18/// Salva (ou substitui) o token OAuth de um provedor.
19pub fn save_oauth_token(
20    app: &AppHandle,
21    provider_id: &str,
22    token: &OAuthToken,
23) -> Result<(), AppError> {
24    let json = serde_json::to_string(token)?;
25    set_secret(app, &secret_key_for(provider_id), &json)
26}
27
28/// Carrega o token OAuth salvo de um provedor, se existir.
29///
30/// **Observação:**
31/// - `get_secret` retorna string vazia quando a chave não existe (em vez de erro)
32/// -  Sendo então tratado como "nenhum token salvo" -> `Ok(None)`.
33pub fn load_oauth_token(
34    app: &AppHandle,
35    provider_id: &str,
36) -> Result<Option<OAuthToken>, AppError> {
37    let raw = get_secret(app, &secret_key_for(provider_id))?;
38
39    if raw.is_empty() {
40        return Ok(None);
41    }
42
43    let token: OAuthToken = serde_json::from_str(&raw)?;
44    Ok(Some(token))
45}
46
47/// Remove o token OAuth salvo de um provedor (ex: logout).
48pub fn delete_oauth_token(app: &AppHandle, provider_id: &str) -> Result<(), AppError> {
49    delete_secret(app, &secret_key_for(provider_id))
50}
51
52/// Retorna um `access_token` válido para o provedor, renovando automaticamente via `refresh_token` se o token salvo estiver expirado.
53///
54/// **Erros:**
55/// - `OAuthTokenNotFound`: nenhum token salvo (usuário nunca fez login, ou fez logout).
56/// - `OAuthTokenNotFound`: token expirado e sem `refresh_token` disponível — precisa de novo login.
57/// - `OAuthRefreshError`: a renovação falhou (refresh_token revogado/expirado no provedor).
58pub async fn get_valid_access_token(
59    app: &AppHandle,
60    config: &OAuthProviderConfig,
61) -> Result<String, AppError> {
62    let stored = load_oauth_token(app, config.provider_id)?
63        .ok_or_else(|| AppError::OAuthTokenNotFound(config.provider_id.to_string()))?;
64
65    if !stored.is_expired() {
66        return Ok(stored.access_token);
67    }
68
69    let refresh_token = stored.refresh_token.as_ref().ok_or_else(|| {
70        AppError::OAuthTokenNotFound(format!(
71            "{}: token expirado e sem refresh_token, é necessário novo login",
72            config.provider_id
73        ))
74    })?;
75
76    let refreshed = refresh_access_token(config, refresh_token).await?;
77    let mut new_token: OAuthToken = refreshed.into();
78
79    // Alguns provedores não reenviam o refresh_token na renovação (esperam que o cliente mantenha o mesmo até expirar de fato).
80    // Se a resposta não trouxer um novo, preserva o que já estava salvo.
81    if new_token.refresh_token.is_none() {
82        new_token.refresh_token = stored.refresh_token.clone();
83    }
84
85    save_oauth_token(app, config.provider_id, &new_token)?;
86
87    Ok(new_token.access_token)
88}