Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Authentication #13

Open
ralfonso4 opened this issue Feb 3, 2021 · 2 comments
Open

New Authentication #13

ralfonso4 opened this issue Feb 3, 2021 · 2 comments

Comments

@ralfonso4
Copy link

ralfonso4 commented Feb 3, 2021

I am using your tesla-api class. Because the new Authentication of Tesla i had to make some changes:
Its not so nice but it works:

    public function base64url_encode($data) { 
  		return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); 
	} 

    public function getAccessToken(string $username, string $password)
    {

###Step 1: Obtain the login page

    	$code_verifier = substr(hash('sha512', mt_rand()), 0, 86);
    	$state = $this->base64url_encode(substr(hash('sha256', mt_rand()), 0, 12));
		$code_challenge = $this->base64url_encode($code_verifier);
		
		$data =[
            'client_id' => 'ownerapi',
            'code_challenge' => $code_challenge,
            'code_challenge_method' => 'S256',
            'redirect_uri' => 'https://auth.tesla.com/void/callback',
            'response_type' => 'code',
            'scope' => 'openid email offline_access',
            'state' => $state,
        ];
        $GetUrl = $this->accessUrl.'?'.http_build_query ($data);
 
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $GetUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        curl_setopt($ch, CURLOPT_HEADER, 1);
		$apiResult = curl_exec($ch);
        curl_close($ch);
        
        $dom = new DomDocument();
		@ $dom->loadHTML($apiResult);
		$child_elements = $dom->getElementsByTagName('input'); //DOMNodeList
		foreach( $child_elements as $h2 ) {
    		$hiddeninputs[$h2->getAttribute('name')]=$h2->getAttribute('value');
		}
		$headers = [];
		$output = rtrim($apiResult);
		$data = explode("\n",$output);
		$headers['status'] = $data[0];
		array_shift($data);

		foreach($data as $part){
			//some headers will contain ":" character (Location for example), and the part after ":" will be lost, Thanks to @Emanuele
    		$middle = explode(":",$part,2);

    		//Supress warning message if $middle[1] does not exist, Thanks to @crayons
    		if ( !isset($middle[1]) ) { $middle[1] = null; }
				$headers[trim($middle[0])] = trim($middle[1]);
		}
	$cookie = $headers['set-cookie'];

###Step 2: Obtain an authorization code

	$ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $GetUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        curl_setopt($ch, CURLOPT_HEADER, 1);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded','Cookie: '.$cookie));
	$postData = array(
			'_csrf' => $hiddeninputs['_csrf'],
			'_phase' => $hiddeninputs['_phase'],
			'_process' => $hiddeninputs['_process'],
			'transaction_id' => $hiddeninputs['transaction_id'],
			'cancel' =>$hiddeninputs['cancel'],
          'identity' => $username,
          'credential' => $password
        );
	curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
        curl_setopt($ch, CURLOPT_COOKIE, $cookie);
        $apiResult = curl_exec($ch);
        curl_close($ch);
	$code= explode('&',explode('https://auth.tesla.com/void/callback?code=',$apiResult)[1])[0];
		
###Step 3: Exchange authorization code for bearer token

	$ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->tokenUrlNew);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        'Accept: application/json',
        'User-Agent: Mozilla/5.0 (Linux; Android 9.0.0; VS985 4G Build/LRX21Y; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36',
        'X-Tesla-User-Agent: TeslaApp/3.4.4-350/fad4a582e/android/9.0.0',
        ]);
      curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'grant_type' => 'authorization_code',
        'client_id' => 'ownerapi',
        'code' => $code,
        'code_verifier' => $code_verifier,
        'redirect_uri' => 'https://auth.tesla.com/void/callback'
      ]));

      $apiResult = curl_exec($ch);
      curl_close($ch);
      
      $apiResultJson = json_decode($apiResult, true);
      $BearerToken = $apiResultJson['access_token'];
      $RefreshToken = $apiResultJson['refresh_token'];

      
###Step 4: Exchange bearer token for access token

$ch = curl_init();
      curl_setopt($ch, CURLOPT_URL, $this->tokenUrl);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_TIMEOUT, 30);
      curl_setopt($ch, CURLOPT_POST, true);
      curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer '.$BearerToken,
        'Content-Type: application/json'
      ]);
      curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        'client_id' => getenv('TESLA_CLIENT_ID'),
        'client_secret' => getenv('TESLA_CLIENT_SECRET'),
      ]));

      $apiResult = curl_exec($ch);
      curl_close($ch);
      
      $apiResultJson = json_decode($apiResult, true);
      $apiResultJson['refresh_token']=$RefreshToken;
	  #print_r($apiResultJson);exit;
	  $this->accessToken = $apiResultJson['access_token'];

        return $apiResultJson;
    }

    public function refreshAccessToken(string $refreshToken)
    {
    $tesla = new Tesla();
    $ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, 'https://pastebin.com/raw/pS7Z6yyP');
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	$result = curl_exec($ch);
	curl_close($ch);
	$api = explode(PHP_EOL,$result);
	$id=explode('=',$api[0]);
	$secret=explode('=',$api[1]);
	$tesla->setClientId(trim($id[1]));
	$tesla->setClientSecret(trim($secret[1]));
	
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_URL, $this->tokenUrlNew);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_TIMEOUT, 30);
      curl_setopt($ch, CURLOPT_POST, true);
      curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        'Accept: application/json',
        'User-Agent: Mozilla/5.0 (Linux; Android 9.0.0; VS985 4G Build/LRX21Y; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36',
        'X-Tesla-User-Agent: TeslaApp/3.4.4-350/fad4a582e/android/9.0.0',
      ]);
      #print 'XX '.getenv('TESLA_CLIENT_ID');exit;
      curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'grant_type' => 'refresh_token',
        'client_id' => 'ownerapi',
        'client_secret' => getenv('TESLA_CLIENT_SECRET'),
        'refresh_token' => $refreshToken,
        'scope' => 'openid email offline_access'
      ]));

      $apiResult = curl_exec($ch);
      $apiResultJson = json_decode($apiResult, true);

      curl_close($ch);
	  #print_r($apiResult);exit;
	  
      $apiResultJson = json_decode($apiResult, true);
      $BearerToken = $apiResultJson['access_token'];
      $RefreshToken = $apiResultJson['refresh_token'];

      
###Step 4: Exchange bearer token for access token

	$ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->tokenUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer '.$BearerToken,
        'Content-Type: application/json'
        ]);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        'client_id' => getenv('TESLA_CLIENT_ID'),
        'client_secret' => getenv('TESLA_CLIENT_SECRET'),
        ]));

      $apiResult = curl_exec($ch);
      curl_close($ch);
      
      $apiResultJson = json_decode($apiResult, true);
      $apiResultJson['refresh_token']=$RefreshToken;
#print_r($apiResultJson);exit;
      $this->accessToken = $apiResultJson['access_token'];

        return $apiResultJson;
    }
@ralfonso4
Copy link
Author

Please go here for the latest code:
timdorr/tesla-api#296

@corsair
Copy link
Collaborator

corsair commented Feb 15, 2021

@ralfonso4 Hey there, would you be willing to submit a PR to implement this? Thanks for your contributions.

I would encourage switching back to the original usage of environmental variables as utilizing the Pastebin URL could be utilized as a MITM attack vector (and also breaks functionality if the service goes down).

Edit: Will note this does not provide support for accounts with 2FA nor handles errors relating to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants