Skip to content

Commit 872c137

Browse files
committed
Working redirects
1 parent fe82740 commit 872c137

File tree

3 files changed

+284
-3
lines changed

3 files changed

+284
-3
lines changed

main.go

Lines changed: 130 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ import (
1616
)
1717

1818
type 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

2427
type 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+
4151
func 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+
181261
func 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{

views/callback.html

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>{{.Title}}</title>
7+
<style>
8+
body {
9+
margin: 0;
10+
padding: 0;
11+
font-family: "Gotham Medium", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
12+
background-color: #fff;
13+
color: #171a20;
14+
}
15+
.container {
16+
max-width: 1200px;
17+
margin: 0 auto;
18+
padding: 40px 20px;
19+
text-align: center;
20+
}
21+
h1 {
22+
font-size: 40px;
23+
font-weight: 500;
24+
margin-bottom: 30px;
25+
color: #2e7d32;
26+
}
27+
.success-icon {
28+
font-size: 64px;
29+
color: #2e7d32;
30+
margin-bottom: 20px;
31+
}
32+
.description {
33+
max-width: 600px;
34+
margin: 0 auto 40px;
35+
font-size: 16px;
36+
line-height: 1.5;
37+
color: #393c41;
38+
}
39+
.token-display {
40+
margin-top: 20px;
41+
padding: 20px;
42+
background-color: rgba(244, 244, 244, 0.65);
43+
border-radius: 20px;
44+
text-align: left;
45+
word-break: break-all;
46+
backdrop-filter: blur(8px);
47+
}
48+
.token-display h3 {
49+
margin-top: 0;
50+
margin-bottom: 15px;
51+
color: #171a20;
52+
}
53+
.token-display pre {
54+
margin: 0;
55+
white-space: pre-wrap;
56+
background-color: #fff;
57+
padding: 15px;
58+
border-radius: 12px;
59+
border: 1px solid #ddd;
60+
}
61+
.tesla-button {
62+
background-color: #3e6ae1;
63+
color: #fff;
64+
border: none;
65+
padding: 16px 48px;
66+
border-radius: 20px;
67+
font-size: 15px;
68+
font-weight: 500;
69+
cursor: pointer;
70+
transition: all 0.3s ease;
71+
text-transform: uppercase;
72+
letter-spacing: 0.5px;
73+
margin: 10px;
74+
min-width: 264px;
75+
backdrop-filter: blur(8px);
76+
box-shadow: inset 0 0 0 2px transparent;
77+
text-decoration: none;
78+
display: inline-block;
79+
}
80+
.tesla-button:hover {
81+
background-color: #3457b1;
82+
}
83+
</style>
84+
</head>
85+
<body>
86+
<div class="container">
87+
<div class="success-icon"></div>
88+
<h1>Authorization Successful</h1>
89+
<p class="description">
90+
Your Tesla account has been successfully connected. You can now use the Fleet Telemetry service.
91+
</p>
92+
<div class="token-display">
93+
<h3>Authorization Details:</h3>
94+
<pre>{{.Token | printf "%+v"}}</pre>
95+
</div>
96+
<a href="/" class="tesla-button">Return to Dashboard</a>
97+
</div>
98+
</body>
99+
</html>

views/index.html

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@
5252
background-color: #393c41;
5353
color: #fff;
5454
}
55+
.tesla-button.highlight {
56+
background-color: #e82127;
57+
}
58+
.tesla-button.highlight:hover {
59+
background-color: #d01a1f;
60+
}
5561
.description {
5662
max-width: 600px;
5763
margin: 0 auto 40px;
@@ -110,6 +116,12 @@
110116
border-radius: 20px;
111117
backdrop-filter: blur(8px);
112118
}
119+
.section-divider {
120+
width: 100%;
121+
height: 1px;
122+
background-color: #eee;
123+
margin: 30px 0;
124+
}
113125
</style>
114126
</head>
115127
<body>
@@ -129,6 +141,19 @@ <h1>Tesla Fleet Telemetry Operator</h1>
129141
Download Public Key
130142
</button>
131143
</div>
144+
145+
<div class="section-divider"></div>
146+
147+
<div class="button-container">
148+
<h2>User Authorization</h2>
149+
<p class="description">
150+
Authorize access to your Tesla vehicles to enable fleet telemetry.
151+
</p>
152+
<button class="tesla-button highlight" onclick="window.location.href='/auth'">
153+
Authorize Tesla Account
154+
</button>
155+
</div>
156+
132157
<div id="responseDisplay" class="response-display">
133158
<div class="response-section">
134159
<h3>Partner Authentication Token:</h3>
@@ -142,12 +167,27 @@ <h3>Registration Response:</h3>
142167
<h3>Verification Response:</h3>
143168
<pre id="verificationContent"></pre>
144169
</div>
170+
<div class="response-section" id="userTokenSection" style="display: none;">
171+
<h3>User Authorization Token:</h3>
172+
<pre id="userTokenContent"></pre>
173+
</div>
145174
</div>
146175
<div id="errorMessage" class="error-message"></div>
147176
<div id="successMessage" class="success-message"></div>
148177
</div>
149178

150179
<script>
180+
// Check for callback response in URL hash or search params
181+
window.onload = function() {
182+
const urlParams = new URLSearchParams(window.location.search);
183+
const code = urlParams.get('code');
184+
const state = urlParams.get('state');
185+
186+
if (code && state) {
187+
displayUserToken({ code, state });
188+
}
189+
}
190+
151191
async function generateToken() {
152192
const responseDisplay = document.getElementById('responseDisplay');
153193
const tokenContent = document.getElementById('tokenContent');
@@ -214,6 +254,21 @@ <h3>Verification Response:</h3>
214254
responseDisplay.style.display = 'none';
215255
}
216256
}
257+
258+
function displayUserToken(data) {
259+
const responseDisplay = document.getElementById('responseDisplay');
260+
const userTokenSection = document.getElementById('userTokenSection');
261+
const userTokenContent = document.getElementById('userTokenContent');
262+
const successMessage = document.getElementById('successMessage');
263+
const errorMessage = document.getElementById('errorMessage');
264+
265+
userTokenContent.textContent = JSON.stringify(data, null, 2);
266+
userTokenSection.style.display = 'block';
267+
responseDisplay.style.display = 'block';
268+
successMessage.textContent = 'User authorization successful!';
269+
successMessage.style.display = 'block';
270+
errorMessage.style.display = 'none';
271+
}
217272
</script>
218273
</body>
219274
</html>

0 commit comments

Comments
 (0)