drive-v3 0.5.1

A library for interacting the Google Drive API
Documentation
use hex;
use sha256;
use nanoid::nanoid;
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};

use crate::Error;

const RANDOM_STATE_LENGTH: usize = 1024;

const CODE_VERIFIER_LENGTH: usize = 128; // Must be between 43 and 128
const CODE_VERIFIER_ALPHABET: [char; 66] = [
    '-', '.', '_', '~',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
];

/// Creates a [`code_verifier`](https://developers.google.com/identity/protocols/oauth2/native-app#step1-code-verifier) and
/// [`challenge`](https://developers.google.com/identity/protocols/oauth2/native-app#create-the-code-challenge) to be used
/// during the [PKCE](https://datatracker.ietf.org/doc/html/rfc7636) protocol required for OAuth authorization.
///
/// # Errors
///
/// Returns a [`HexDecoding`](crate::ErrorKind::HexDecoding) error, if the conversion the hashed `code_verifier` to bytes failed.
pub fn get_code_verifier() -> Result<(String, String), Error> {
    let code_verifier = nanoid!(CODE_VERIFIER_LENGTH, &CODE_VERIFIER_ALPHABET);

    let hashed_code_verifier = sha256::digest(&code_verifier);
    let hash_as_bytes = hex::decode(hashed_code_verifier)?;

    let base_64_url_encoded_hash = URL_SAFE_NO_PAD.encode(hash_as_bytes);

    Ok(( code_verifier, base_64_url_encoded_hash ))
}

/// Creates a [anti-forgery state token](https://developers.google.com/identity/openid-connect/openid-connect#createxsrftoken)
/// for additional security during authorization.
pub fn get_random_state() -> String {
    let random_string = nanoid!(RANDOM_STATE_LENGTH);

    sha256::digest(random_string)
}

#[cfg(test)]
mod tests {
    use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
    use super::{get_random_state, get_code_verifier, CODE_VERIFIER_ALPHABET};

    #[test]
    fn get_code_verifier_test() {
        let (code_verifier, challenge) = get_code_verifier().unwrap();

        assert!( code_verifier.chars().all( |c| CODE_VERIFIER_ALPHABET.contains(&c) ) );

        // Range numbers from: https://developers.google.com/identity/protocols/oauth2/native-app#step1-code-verifier
        assert!( code_verifier.len() >= 43 );
        assert!( code_verifier.len() <= 128 );

        // Test challenge
        let hashed_code_verifier = sha256::digest(&code_verifier);
        let hash_as_bytes = hex::decode(hashed_code_verifier).unwrap();

        let decoded_challenge = URL_SAFE_NO_PAD.decode( &challenge.as_bytes() ).unwrap();

        assert_eq!(hash_as_bytes, decoded_challenge)
    }

    #[test]
    fn get_random_state_test() {
        let random_state = get_random_state();

        assert_eq!( random_state.len(), 64 );
    }
}