googauth-lib 0.9.0

A library that uses OpenID Connect to sign in to a Google account, and store the credentials locally for ease of use
Documentation
use crate::config_file::{ConfigBasePath, ConfigFile, Token};
use crate::errors::LibError;
use openidconnect::core::{CoreClient, CoreIdTokenVerifier, CoreProviderMetadata};
use openidconnect::{
    reqwest, ClientId, ClientSecret, IssuerUrl, OAuth2TokenResponse, RefreshToken, Scope,
    TokenResponse,
};
use std::time::{SystemTime, UNIX_EPOCH};

pub async fn refresh_google_login(
    config: &mut ConfigFile,
    config_base_path: &ConfigBasePath,
) -> Result<(), LibError> {
    let google_client_id = ClientId::new(config.client_id.to_string());
    let google_client_secret = ClientSecret::new(config.client_secret.to_string());
    let issuer_url =
        IssuerUrl::new("https://accounts.google.com".to_string()).expect("Invalid issuer URL");
    let http_client = reqwest::ClientBuilder::new()
        // Following redirects opens the client up to SSRF vulnerabilities.
        .redirect(reqwest::redirect::Policy::none())
        .build()
        .map_err(|openid_error| LibError::OpenIdError(openid_error.to_string()))?;

    // Fetch Google's OpenID Connect discovery document.
    let provider_metadata = CoreProviderMetadata::discover_async(issuer_url, &http_client)
        .await
        .map_err(|_| LibError::OpenIdError("Failed to discover OpenID Provider".to_string()))?;

    let refresh_token = match &config.refresh_token {
        Some(refresh_token) => RefreshToken::new(refresh_token.to_string()),
        None => {
            return Err(LibError::NoRefreshTokenForConfig(config.name.clone()));
        }
    };

    let client = CoreClient::from_provider_metadata(
        provider_metadata,
        google_client_id,
        Some(google_client_secret),
    );

    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_secs();

    let mut refresh_token_request = client
        .exchange_refresh_token(&refresh_token)
        .map_err(|openid_err| LibError::OpenIdError(openid_err.to_string()))?;

    for scope in &config.scopes {
        refresh_token_request = refresh_token_request.add_scope(Scope::new(scope.to_string()));
    }

    let refresh_token_result = refresh_token_request.request_async(&http_client).await;

    let token_response = match refresh_token_result {
        Ok(rt) => rt,
        Err(_) => {
            return Err(LibError::CouldNotRefreshToken);
        }
    };

    let access_token = token_response.access_token().secret().to_string();
    let access_token_exp = match token_response.expires_in() {
        None => 0,
        Some(expires_in) => now + expires_in.as_secs(),
    };
    config.access_token = Some(Token::new(access_token, access_token_exp));

    let id_token = token_response.id_token().ok_or(LibError::NoIdToken)?;
    let id_token_verifier: CoreIdTokenVerifier = client.id_token_verifier();
    let id_token_claims = match id_token.claims(&id_token_verifier, |_: Option<&_>| Ok(())) {
        Ok(claims) => claims,
        Err(e) => {
            println!("ERR {:?}", e);
            return Err(LibError::CouldNotReadClaims);
        }
    };
    let id_token_exp = id_token_claims.expiration().timestamp() as u64;
    config.id_token = Some(Token::new(id_token.to_string(), id_token_exp));

    config.save_config(config_base_path)
}