22.04.2021, 12:32
This should get you a request token and refresh token which can be used for further API calls. But this can stop working anytime if the login page structure changes since this example emulates user interaction with the login page.
Code:
username = 'user@domain.com'
password = 'password'
client_id = 'ID'
client_secret = 'SECRET'
encdec = require('encdec')
http = require('socket.http')
mime = require('mime')
ltn12 = require('ltn12')
json = require('json')
function mt()
local ts, tu = os.microtime()
return ts .. '.' .. tu
end
function b64url(str)
return mime.b64(str):gsub('.', {
['+'] = '-',
['/'] = '_',
['='] = '',
})
end
function encodeargs(t)
local res = {}
local esc = require('socket.url').escape
for k, v in pairs(t) do
res[ #res + 1 ] = esc(k) .. '=' .. esc(v)
end
return table.concat(res, '&')
end
code_verifier = encdec.sha512(mt()):sub(1, 86)
state = b64url(encdec.sha256(mt()):sub(1, 12))
code_challenge = b64url(code_verifier)
args = encodeargs({
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,
})
url = 'https://auth.tesla.com/oauth2/v3/authorize?' .. args
res, code, headers = http.request(url)
if not res or code ~= 200 then
log('request 1 failed', res, code)
return
end
postdata = {}
regexp = '<input type="hidden" name="([^"]+)" value="([^"]*)"'
for name, value in res:gmatch(regexp) do
postdata[ name ] = value
end
postdata.identity = username
postdata.credential = password
cookie = headers['Set-Cookie'] or headers['set-cookie'] or ''
body = encodeargs(postdata)
res, code, headers = http.request({
url = url,
method = 'POST',
source = ltn12.source.string(body),
headers = {
['Content-Type'] = 'application/x-www-form-urlencoded',
['Content-Length'] = #body,
['Cookie'] = cookie,
}
})
if not res or code ~= 302 then
log('request 2 failed', res, code)
return
end
hdr = headers.Location or headers.location
resp_code = hdr:match('code=([^&]+)')
body = json.encode({
grant_type = 'authorization_code',
client_id = 'ownerapi',
code = resp_code,
code_verifier = code_verifier,
redirect_uri = 'https://auth.tesla.com/void/callback',
})
resp = {}
res, code, headers = http.request({
url = 'https://auth.tesla.com/oauth2/v3/token',
method = 'POST',
source = ltn12.source.string(body),
sink = ltn12.sink.table(resp),
headers = {
['Content-Type'] = 'application/json',
['Accept'] = 'application/json',
['Content-Length'] = #body,
['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',
}
})
if not res or code ~= 200 then
log('request 3 failed', res, code)
return
end
resp = table.concat(resp)
resp = json.pdecode(resp)
bearer_token = resp.access_token
refresh_token = resp.refresh_token
body = json.encode({
grant_type = 'urn:ietf:params:oauth:grant-type:jwt-bearer',
client_id = client_id,
client_secret = client_secret,
})
resp = {}
res, code, headers = http.request({
url = 'https://owner-api.teslamotors.com/oauth/token',
method = 'POST',
source = ltn12.source.string(body),
sink = ltn12.sink.table(resp),
headers = {
['Content-Type'] = 'application/json',
['Authorization'] = 'Bearer ' .. bearer_token,
['Content-Length'] = #body,
}
})
print(res, code)
if not res or code ~= 200 then
log('request 4 failed', res, code)
return
end
resp = table.concat(resp)
resp = json.pdecode(resp)
access_token = resp.access_token
log(access_token)