gcp_service_oauth2 0.1.1

Rust implementation of service account authentication via OAuth 2.0 for Google Cloud Platform
Documentation
use std::fs;
use std::io::Error as IOError;
use serde::{Deserialize, Serialize};
use serde_json::{Error as JSONError, Value};
use std::time::{SystemTime, UNIX_EPOCH, SystemTimeError};
use jsonwebtoken::{encode, Algorithm, Header, EncodingKey};
use jsonwebtoken::errors::Error as JWTError;
use reqwest::{Error as APIError};

#[derive(Debug)]
pub enum Error {
    IO(IOError),
    JSON(JSONError),
    SystemTime(SystemTimeError),
    JWT(JWTError),
    API(APIError),
}

#[derive(Debug, Deserialize)]
pub struct ClientSecret {
    #[serde(rename = "type")]
    secret_type: String,
    project_id: String,
    private_key_id: String,
    private_key: String,
    client_email: String,
    client_id: String,
    auth_uri: String,
    token_uri: String,
    auth_provider_x509_cert_url: String,
    client_x509_cert_url: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct Claims {
    iss: String,
    scope: String,
    aud: String,
    exp: u64,
    iat: u64,
}

#[derive(Debug, Deserialize)]
struct APIResponse {
    access_token: String,
    token_type: String,
    expires_in: u64,
}

impl From<IOError> for Error {
    fn from(err: IOError) -> Error {
        Error::IO(err)
    }
}

impl From<JSONError> for Error {
    fn from(err: JSONError) -> Error {
        Error::JSON(err)
    }
}

impl From<SystemTimeError> for Error {
    fn from(err: SystemTimeError) -> Error {
        Error::SystemTime(err)
    }
}

impl From<JWTError> for Error {
    fn from(err: JWTError) -> Error {
        Error::JWT(err)
    }
}

impl From<APIError> for Error {
    fn from(err: APIError) -> Error {
        Error::API(err)
    }
}

pub async fn get_access_token(client_secret: &ClientSecret, scope: String) -> Result<String, Error> {
    let jwt = create_jwt_token(client_secret, scope)?;
    let body = vec![
        ("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"),
        ("assertion", &jwt),
    ];
    let response = reqwest::Client::new()
        .post(client_secret.token_uri.as_str())
        .form(&body)
        .send()
        .await?
        .json::<APIResponse>()
        .await?;
    Ok(response.access_token)
}

pub fn read_secret_from_file(path: String) -> Result<ClientSecret, Error> {
    let client_secret_json: String = fs::read_to_string(path)?;
    let client_secret: ClientSecret = serde_json::from_str(&client_secret_json)?;
    Ok(client_secret)
}

fn create_jwt_token(client_secret: &ClientSecret, scope: String) -> Result<String, Error> {
    let claims = create_jwt_claims(client_secret, scope)?;
    let encoding_key = EncodingKey::from_rsa_pem(client_secret.private_key.as_bytes())?;
    let token = encode(&Header::new(Algorithm::RS256), &claims, &encoding_key)?;
    Ok(token)
}

fn create_jwt_claims(client_secret: &ClientSecret, scope: String) -> Result<Claims, Error> {
    let issue_time = SystemTime::now()
        .duration_since(UNIX_EPOCH)?.as_secs();
    let expiration_time = issue_time + 3600;

    Ok(Claims {
        iss: client_secret.client_email.clone(),
        scope,
        aud: client_secret.token_uri.clone(),
        exp: expiration_time,
        iat: issue_time,
    })
}