Skip to main content

gog_api/
client.rs

1// gog-api client module
2// Authenticated client builder.
3// Ported from internal/googleapi/client.go
4
5use gog_auth::Service;
6use gog_core::config;
7use crate::error::ApiError;
8
9const TOKEN_ENDPOINT: &str = "https://oauth2.googleapis.com/token";
10
11/// An authenticated reqwest client for a Google service.
12pub struct AuthenticatedClient {
13    pub client: reqwest::Client,
14    pub access_token: String,
15    pub email: String,
16}
17
18impl AuthenticatedClient {
19    /// Create an authenticated client for the given service and account.
20    ///
21    /// Steps:
22    /// 1. Read client credentials from config.
23    /// 2. Get refresh token from keyring.
24    /// 3. Exchange refresh token for access token via OAuth2 token endpoint.
25    /// 4. Return client with auth header.
26    pub async fn new(
27        service: Service,
28        email: &str,
29        oauth_client: &str,
30    ) -> Result<Self, ApiError> {
31        // 1. Read client credentials from config
32        let creds = config::read_client_credentials_for(oauth_client)?;
33
34        // 2. Get refresh token from store
35        let store = gog_secrets::open_store()?;
36        let token = store.get_token(oauth_client, email)?;
37        let refresh_token = token.refresh_token;
38
39        // 3. Exchange refresh token for access token
40        let scopes = service.scopes().join(" ");
41        let http_client = reqwest::Client::new();
42        let params = [
43            ("client_id", creds.client_id.as_str()),
44            ("client_secret", creds.client_secret.as_str()),
45            ("refresh_token", refresh_token.as_str()),
46            ("grant_type", "refresh_token"),
47            ("scope", scopes.as_str()),
48        ];
49
50        let resp = http_client
51            .post(TOKEN_ENDPOINT)
52            .form(&params)
53            .send()
54            .await?;
55
56        if !resp.status().is_success() {
57            let status = resp.status().as_u16();
58            let msg = resp.text().await.unwrap_or_default();
59            return Err(ApiError::GoogleApi {
60                status,
61                message: msg,
62            });
63        }
64
65        let token_resp: serde_json::Value = resp.json().await?;
66        let access_token = token_resp["access_token"]
67            .as_str()
68            .ok_or_else(|| ApiError::GoogleApi {
69                status: 0,
70                message: "missing access_token in response".to_string(),
71            })?
72            .to_string();
73
74        // 4. Build client with Authorization header
75        let mut headers = reqwest::header::HeaderMap::new();
76        headers.insert(
77            reqwest::header::AUTHORIZATION,
78            format!("Bearer {}", access_token)
79                .parse()
80                .expect("valid header value"),
81        );
82
83        let client = reqwest::Client::builder()
84            .default_headers(headers)
85            .build()?;
86
87        Ok(AuthenticatedClient {
88            client,
89            access_token,
90            email: email.to_string(),
91        })
92    }
93}