@@ -16,9 +16,12 @@ import (
1616)
1717
1818type TokenResponse struct {
19- AccessToken string `json:"access_token"`
20- TokenType string `json:"token_type"`
21- ExpiresIn int `json:"expires_in"`
19+ AccessToken string `json:"access_token"`
20+ TokenType string `json:"token_type"`
21+ ExpiresIn int `json:"expires_in"`
22+ RefreshToken string `json:"refresh_token,omitempty"`
23+ IdToken string `json:"id_token,omitempty"`
24+ Scope string `json:"scope,omitempty"`
2225}
2326
2427type RegistrationResponse struct {
@@ -38,6 +41,13 @@ type TeslaAPIResponse struct {
3841 Error string `json:"error"`
3942}
4043
44+ type AuthConfig struct {
45+ ClientID string `json:"client_id"`
46+ RedirectURI string `json:"redirect_uri"`
47+ State string `json:"state"`
48+ Scope string `json:"scope"`
49+ }
50+
4151func generateToken () (* TokenResponse , error ) {
4252 data := url.Values {}
4353 data .Set ("grant_type" , "client_credentials" )
@@ -178,6 +188,76 @@ var (
178188 domainName []byte
179189)
180190
191+ func getBaseURL (c * fiber.Ctx ) string {
192+ protocol := "http"
193+ if c .Protocol () == "https" || c .Get ("X-Forwarded-Proto" ) == "https" {
194+ protocol = "https"
195+ }
196+ return fmt .Sprintf ("%s://%s" , protocol , c .Hostname ())
197+ }
198+
199+ func getRedirectURI (c * fiber.Ctx ) string {
200+ host := c .Hostname ()
201+ // Use exact URIs as registered in Tesla Developer Portal
202+ if host == "localhost" || host == "localhost:3000" || strings .HasPrefix (host , "127.0.0.1" ) {
203+ return "http://localhost:3000/callback"
204+ }
205+ return "https://tesla.rajsingh.info/callback"
206+ }
207+
208+ func generateAuthURL (c * fiber.Ctx ) string {
209+ redirectURI := getRedirectURI (c )
210+ log .Printf ("Using redirect URI: %s" , redirectURI )
211+
212+ // Build the URL in the exact same order as the example
213+ params := url.Values {}
214+ params .Set ("client_id" , strings .TrimSpace (string (clientID )))
215+ params .Set ("locale" , "en-US" )
216+ params .Set ("prompt" , "login" )
217+ params .Set ("redirect_uri" , redirectURI )
218+ params .Set ("response_type" , "code" )
219+ params .Set ("scope" , "openid user_data vehicle_device_data vehicle_cmds vehicle_charging_cmds energy_device_data energy_cmds offline_access" )
220+ params .Set ("state" , "abc123" ) // Match the example's state value
221+
222+ authURL := fmt .Sprintf ("https://auth.tesla.com/oauth2/v3/authorize?%s" , params .Encode ())
223+ log .Printf ("Generated auth URL: %s" , authURL )
224+ return authURL
225+ }
226+
227+ func exchangeAuthCode (code string , c * fiber.Ctx ) (* TokenResponse , error ) {
228+ redirectURI := getRedirectURI (c )
229+ log .Printf ("Using redirect URI for token exchange: %s" , redirectURI )
230+
231+ data := url.Values {}
232+ data .Set ("grant_type" , "authorization_code" )
233+ data .Set ("client_id" , strings .TrimSpace (string (clientID )))
234+ data .Set ("client_secret" , strings .TrimSpace (string (clientSecret )))
235+ data .Set ("code" , code )
236+ data .Set ("redirect_uri" , redirectURI )
237+ data .Set ("audience" , "https://fleet-api.prd.na.vn.cloud.tesla.com" )
238+
239+ resp , err := http .PostForm ("https://auth.tesla.com/oauth2/v3/token" , data )
240+ if err != nil {
241+ return nil , err
242+ }
243+ defer resp .Body .Close ()
244+
245+ body , err := io .ReadAll (resp .Body )
246+ if err != nil {
247+ return nil , fmt .Errorf ("error reading response: %v" , err )
248+ }
249+
250+ log .Printf ("Token exchange response status: %d" , resp .StatusCode )
251+ log .Printf ("Token exchange response body: %s" , string (body ))
252+
253+ var tokenResp TokenResponse
254+ if err := json .NewDecoder (resp .Body ).Decode (& tokenResp ); err != nil {
255+ return nil , err
256+ }
257+
258+ return & tokenResp , nil
259+ }
260+
181261func main () {
182262 var err error
183263 // Read credentials
@@ -268,6 +348,53 @@ func main() {
268348 })
269349 })
270350
351+ // Add the authorization endpoint
352+ app .Get ("/auth" , func (c * fiber.Ctx ) error {
353+ authURL := generateAuthURL (c )
354+ log .Printf ("Generated auth URL: %s" , authURL )
355+ return c .Redirect (authURL )
356+ })
357+
358+ // Add the callback endpoint
359+ app .Get ("/callback" , func (c * fiber.Ctx ) error {
360+ code := c .Query ("code" )
361+ state := c .Query ("state" )
362+
363+ if state != "abc123" { // Match the state from the auth request
364+ return c .Status (400 ).JSON (fiber.Map {
365+ "error" : "Invalid state parameter" ,
366+ })
367+ }
368+
369+ if code == "" {
370+ return c .Status (400 ).JSON (fiber.Map {
371+ "error" : "No authorization code provided" ,
372+ })
373+ }
374+
375+ token , err := exchangeAuthCode (code , c )
376+ if err != nil {
377+ return c .Status (500 ).JSON (fiber.Map {
378+ "error" : "Failed to exchange authorization code: " + err .Error (),
379+ })
380+ }
381+
382+ // Check if the request accepts JSON
383+ accepts := c .Accepts ("application/json" )
384+ if accepts == "application/json" {
385+ return c .JSON (fiber.Map {
386+ "token" : token ,
387+ "message" : "Successfully obtained user access token" ,
388+ })
389+ }
390+
391+ // Otherwise, render the success page
392+ return c .Render ("callback" , fiber.Map {
393+ "Title" : "Authorization Successful" ,
394+ "Token" : token ,
395+ })
396+ })
397+
271398 // Serve the main page
272399 app .Get ("/" , func (c * fiber.Ctx ) error {
273400 return c .Render ("index" , fiber.Map {
0 commit comments