Skip to content

Commit

Permalink
Use form encode instead of json on all Tesla oauth2 calls
Browse files Browse the repository at this point in the history
  • Loading branch information
fredli74 committed Aug 20, 2024
1 parent 99fd6f4 commit 2512dc7
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 27 deletions.
1 change: 1 addition & 0 deletions providers/tesla/tesla-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export class TeslaAgent extends AbstractAgent {
// API Token check and update
const token = job.serviceData.token as TeslaToken;
if (TeslaAPI.tokenExpired(token)) {
log(LogLevel.Trace, `${job.serviceID} token expired, calling server API for refresh`);
// Token has expired, run it through server
const newToken = await this.scClient.providerMutate("tesla", {
mutation: TeslaProviderMutates.RefreshToken,
Expand Down
35 changes: 19 additions & 16 deletions providers/tesla/tesla-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,17 @@ export class TeslaAPI {
): Promise<TeslaToken> {
try {
log(LogLevel.Trace, `authorize(${code}, ${callbackURI})`);
const authResponse = (await this.authAPI.post("/oauth2/v3/token", {
grant_type: "authorization_code",
client_id: config.TESLA_CLIENT_ID,
client_secret: config.TESLA_CLIENT_SECRET,
code: code,
audience: config.TESLA_API_BASE_URL,
redirect_uri: callbackURI,
})) as any;

// Tesla authAPI expects form data in the body
const formData = new URLSearchParams();
formData.append("grant_type", "authorization_code");
formData.append("client_id", config.TESLA_CLIENT_ID);
formData.append("client_secret", config.TESLA_CLIENT_SECRET);
formData.append("code", code);
formData.append("audience", config.TESLA_API_BASE_URL);
formData.append("redirect_uri", callbackURI);

const authResponse = (await this.authAPI.post("/oauth2/v3/token", formData.toString())) as any;
return this.parseTokenResponse(authResponse);
} catch (e) {
console.debug(`TeslaAPI.authorize error: ${e}`);
Expand All @@ -81,14 +84,13 @@ export class TeslaAPI {
try {
log(LogLevel.Trace, `renewToken(${refresh_token})`);

// Tesla authentication is multi layered at the moment
// First you need to renew the new Tesla SSO access token by using
// the refresh token from previous oauth2/v3 authorization
const authResponse = (await this.authAPI.post("/oauth2/v3/token", {
grant_type: "refresh_token",
client_id: config.TESLA_CLIENT_ID,
refresh_token: refresh_token,
})) as any;
// Tesla authAPI expects form data in the body
const formData = new URLSearchParams();
formData.append("grant_type", "refresh_token");
formData.append("client_id", config.TESLA_CLIENT_ID);
formData.append("refresh_token", refresh_token);

const authResponse = (await this.authAPI.post("/oauth2/v3/token", formData.toString())) as any;
return this.parseTokenResponse(authResponse);
} catch (e) {
console.debug(`TeslaAPI.renewToken error: ${e}`);
Expand Down Expand Up @@ -231,6 +233,7 @@ const authClient = new RestClient({
headers: {
Accept: "*/*",
"User-Agent": "smartcharge.dev/1.0",
"Content-Type": "application/x-www-form-urlencoded",
}
});

Expand Down
13 changes: 10 additions & 3 deletions providers/tesla/tesla-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,20 @@ export async function maintainToken(
token.expires_at !== undefined &&
!TeslaAPI.tokenExpired(token as TeslaToken)
) {
log(LogLevel.Trace, `Token ${token.access_token} is still valid`);
return token as TeslaToken;
}
log(LogLevel.Trace, `Token ${token.access_token} is invalid, calling renewToken`);
const newToken = await teslaAPI.renewToken(token.refresh_token);
validToken(db, token.refresh_token, newToken);
return newToken;
} catch (err) {
log(LogLevel.Error, err);
invalidToken(db, token);
} catch (err:any) {
if (err && err.message === "login_required") {
log(LogLevel.Warning, `Token ${token.refresh_token} is invalid (login_required)`);
invalidToken(db, token);
} else {
log(LogLevel.Error, `Unexpected error raised when renewing token ${JSON.stringify(err)}`);
}
throw new ApolloError("Invalid token", "INVALID_TOKEN");
}
}
Expand Down Expand Up @@ -205,6 +211,7 @@ const server: IProviderServer = {
return await authorize(context.db, data.code, data.callbackURI);
}
case TeslaProviderMutates.RefreshToken: {
log(LogLevel.Trace, `TeslaProviderMutates.RefreshToken ${JSON.stringify(data)}`);
return await maintainToken(
context.db,
data.token
Expand Down
22 changes: 14 additions & 8 deletions shared/restclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export class RestClient {
method: string,
relativeURL: string,
data: any,
bearerToken?: string
bearerToken?: string,
): Promise<RestClientResponse> {
const url = mergeURL(this.options.baseURL, relativeURL);
const secure = /^https:/.test(url);
Expand All @@ -81,10 +81,10 @@ export class RestClient {
method: method,
timeout: this.options.timeout,
headers: {
...this.options.headers,
Accept: "application/json",
"Content-Type": "application/json",
"Accept-Encoding": "gzip, deflate"
"Accept-Encoding": "gzip, deflate",
...this.options.headers,
},
};
if (opt.headers["User-Agent"] === undefined) {
Expand All @@ -97,15 +97,14 @@ export class RestClient {
: "Authorization"
] = `Bearer ${bearerToken}`;
}
const postData = JSON.stringify(data);
// Check if data is a string, if not assume it is JSON
const postData = typeof data === "string" ? data : JSON.stringify(data);
if (postData) {
opt.headers["Content-Length"] = Buffer.byteLength(postData);
}
return new Promise<RestClientResponse>((resolve, reject) => {
const dispatchError = (e: any, code?: number) => {
const s = `request error: ${code ? code + " " : ""}${
typeof e === "string" ? e : JSON.stringify(e)
}`;
const s = (typeof e === "string" ? e : `${code ? code + " " : ""}${JSON.stringify(e)}`);
reject(new RestClientError(s, code || 500));
};

Expand Down Expand Up @@ -144,7 +143,14 @@ export class RestClient {
} else {
console.log(`RestClientError: Non-2xx status: ${res.statusCode}`);
console.log(`RestClientError: ${body}`);
dispatchError(res.statusMessage, res.statusCode);
try {
const data = JSON.parse(body);
const message = data.message || data.error || JSON.stringify(data);
console.log(`RestClientError: ${message}`);
dispatchError(message, res.statusCode);
} catch (e: any) {
dispatchError(res.statusMessage, res.statusCode);
}
}
});
});
Expand Down

0 comments on commit 2512dc7

Please sign in to comment.