From 1b8fda32d9b63e3abaacf6ab915e23a35a196b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szak=C3=A1ts=20Alp=C3=A1r=20Zsolt?= Date: Wed, 13 Aug 2025 19:36:32 +0200 Subject: [PATCH] Steps --- Source/ProofOfConcept/Models/Token.cs | 2 +- .../Services/TeslaAuthenticatorService.cs | 148 +++++++++++++----- 2 files changed, 110 insertions(+), 40 deletions(-) diff --git a/Source/ProofOfConcept/Models/Token.cs b/Source/ProofOfConcept/Models/Token.cs index b4b67b1..a43cd07 100644 --- a/Source/ProofOfConcept/Models/Token.cs +++ b/Source/ProofOfConcept/Models/Token.cs @@ -9,5 +9,5 @@ public record Token public string TokenType { get; init; } = String.Empty; //For JSON deserialization - public int ExpiresIn { get => (int)DateTimeOffset.Now.Subtract(Expires).TotalSeconds; init => Expires = DateTimeOffset.Now.AddSeconds(value); } + public int ExpiresIn { get => (int)Expires.Subtract(DateTimeOffset.Now).TotalSeconds; init => Expires = DateTimeOffset.Now.AddSeconds(value); } } \ No newline at end of file diff --git a/Source/ProofOfConcept/Services/TeslaAuthenticatorService.cs b/Source/ProofOfConcept/Services/TeslaAuthenticatorService.cs index 8fb8ef9..faecc32 100644 --- a/Source/ProofOfConcept/Services/TeslaAuthenticatorService.cs +++ b/Source/ProofOfConcept/Services/TeslaAuthenticatorService.cs @@ -1,5 +1,6 @@ using System.Net.Http.Headers; using System.Text.Json; +using System.Text.Json.Nodes; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using ProofOfConcept.Models; @@ -18,20 +19,12 @@ public class TeslaAuthenticatorService const string authEndpointURL = "https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3/token"; const string euBaseURL = "https://fleet-api.prd.eu.vn.cloud.tesla.com"; - private JsonSerializerOptions serializerOptions; - public TeslaAuthenticatorService(ILogger logger, IOptions options, IHttpClientFactory httpClientFactory, IMemoryCache memoryCache) { this.logger = logger; this.httpClientFactory = httpClientFactory; this.memoryCache = memoryCache; this.configuration = options.Value; - - serializerOptions = new JsonSerializerOptions() - { - PropertyNameCaseInsensitive = true, - PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower - }; } /// Asynchronously retrieves an authentication token from the Tesla partner API. @@ -39,7 +32,7 @@ public class TeslaAuthenticatorService /// OAuth2 access token that grants access to Tesla partner services. /// Returns a string containing the authentication token received from the API. /// Thrown when the HTTP request to the API fails. - public async Task AcquirePartnerAuthenticationTokenAsync() + public async Task> AcquirePartnerAuthenticationTokenAsync() { this.logger.LogTrace("Acquiring authentication token from Tesla partner API..."); @@ -60,30 +53,65 @@ public class TeslaAuthenticatorService FormUrlEncodedContent content = new FormUrlEncodedContent(formData); - // Send POST request - HttpResponseMessage response = await client.PostAsync(authEndpointURL, content); - this.logger.LogTrace("Response status code: {0}", response.StatusCode); - - // Throw if not successful - response.EnsureSuccessStatusCode(); - - // Read the response body as a string - string json = await response.Content.ReadAsStringAsync(); - - // Deserialize + string json = ""; try { - Token? result = JsonSerializer.Deserialize(json); - - if (result is null) - throw new FormatException("Response could not be deserialized (null)."); - - // Return the token response - return result; + // Send POST request + HttpResponseMessage response = await client.PostAsync(authEndpointURL, content); + this.logger.LogTrace("Response status code: {0}", response.StatusCode); + + // Throw if not successful + response.EnsureSuccessStatusCode(); + + // Read the response body as a string + json = await response.Content.ReadAsStringAsync(); } + + catch (HttpRequestException httpRequestException) + { + this.logger.LogError(httpRequestException, "HTTP Request error while acquiring partner authentication token from Tesla API"); + return new Result(httpRequestException); + } + catch (Exception e) { - throw new AggregateException($"Could not deserialize token response: {json}", e); + this.logger.LogError(e, "Unexpected communication error while acquiring partner authentication token from Tesla API"); + return new Result(e); + } + + try + { + // Check if the response is empty + if (String.IsNullOrWhiteSpace(json)) + throw new JsonException("Response could not be deserialized (empty)."); + + // Deserializer options: {"access_token":"999 random chars including some special characters","expires_in":28800,"token_type":"Bearer"} + JsonSerializerOptions serializerOptions = new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower + }; + + // Deserialize the JSON response + Token? result = JsonSerializer.Deserialize(json, serializerOptions); + + if (result is null) + throw new JsonException("Response could not be deserialized (null)."); + + this.logger.LogInformation("Authentication token acquired from Tesla partner API"); + return new Result(result); + } + + catch (JsonException jsonException) + { + this.logger.LogError(jsonException, "JSON deserialization error while acquiring partner authentication token from Tesla API"); + return new Result(jsonException); + } + + catch (Exception e) + { + this.logger.LogError(e, "Unexpected serialization error while acquiring partner authentication token from Tesla API"); + return new Result(e); } } @@ -94,7 +122,7 @@ public class TeslaAuthenticatorService /// Returns a Token object containing the authentication token and its expiration details. /// Thrown when the HTTP request to acquire a new partner token fails. /// Thrown for any unexpected errors during the token retrieval or caching process. - public async Task GetPartnerAuthenticationTokenAsync() + public async Task> GetPartnerAuthenticationTokenAsync() { this.logger.LogTrace("Getting partner authentication token..."); @@ -102,17 +130,25 @@ public class TeslaAuthenticatorService if (this.memoryCache.TryGetValue(Keys.TeslaPartnerToken, out Token? token) && token is not null) { this.logger.LogInformation("Partner authentication token provided from cache"); - return token; + return Result.Success(token); } //Acquire token - token = await AcquirePartnerAuthenticationTokenAsync(); + Result aquirationResult = await AcquirePartnerAuthenticationTokenAsync(); //Save to cache - this.memoryCache.Set(Keys.TeslaPartnerToken, token, token.Expires.Subtract(this.configuration.MemoryCacheDelta)); - - this.logger.LogInformation("Partner authentication token provided provided by acquiring from API"); - return token; + if (aquirationResult.IsSuccessful) + { + token = aquirationResult.Value; + this.memoryCache.Set(Keys.TeslaPartnerToken, token, token.Expires.Subtract(this.configuration.MemoryCacheDelta)); + + this.logger.LogInformation("Partner authentication token provided by acquiring from API"); + } + + else + this.logger.LogError("Failed to get partner authentication token"); + + return aquirationResult; } public async Task RegisterApplicationAsync(CancellationToken cancellationToken = default(CancellationToken)) @@ -121,8 +157,14 @@ public class TeslaAuthenticatorService //Create client and get partner token HttpClient httpClient = httpClientFactory.CreateClient(); - Token partnerToken = await GetPartnerAuthenticationTokenAsync(); + Result partnerTokenResult = await GetPartnerAuthenticationTokenAsync(); + if (!partnerTokenResult.IsSuccessful) + return Result.Fail(partnerTokenResult.Exception ?? new Exception("Partner authentication token not found")); + + //Set partner token + Token partnerToken = partnerTokenResult.Value; + //Set headers httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", partnerToken.AccessToken); @@ -135,7 +177,29 @@ public class TeslaAuthenticatorService bool success = response.IsSuccessStatusCode; logger.LogInformation("Application registration result: {Success}", success ? "Success" : "Failed"); - return new Result(success); + + //Load response from server + string json = await response.Content.ReadAsStringAsync(cancellationToken); + this.logger.LogTrace("Registration response from server: {Response}", json); + + //{"isSuccessful":true,"isFailed":false,"exceptions":[],"exception":null} + //Parse JSON + JsonNode? root = JsonNode.Parse(json); + bool isSuccessful = root?["isSuccessful"]?.GetValue() ?? false; + bool isFailed = root?["isFailed"]?.GetValue() ?? false; + + if (success && isSuccessful && !isFailed) + { + logger.LogInformation("Application registered successfully"); + return Result.Success(); + } + + else + { + logger.LogError("Application registration failed"); + + return Result.Fail(new AggregateException(new Exception(root?["exception"]?.ToJsonString()), new Exception(root?["exceptions"]?.ToJsonString()))); + } } public async Task> CheckApplicationRegistrationAsync(CancellationToken cancellationToken = default(CancellationToken)) @@ -144,14 +208,20 @@ public class TeslaAuthenticatorService //Create client and get partner token HttpClient httpClient = httpClientFactory.CreateClient(); - Token partnerToken = await GetPartnerAuthenticationTokenAsync(); + Result partnerTokenResult = await GetPartnerAuthenticationTokenAsync(); + + if (!partnerTokenResult.IsSuccessful) + return Result.Fail(partnerTokenResult.Exception ?? new Exception("Partner authentication token not found")); + + //Set partner token + Token partnerToken = partnerTokenResult.Value; //Set headers httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", partnerToken.AccessToken); //Send request - HttpResponseMessage response = await httpClient.GetAsync($"{euBaseURL}/api/1/partner_accounts", cancellationToken); + HttpResponseMessage response = await httpClient.GetAsync($"{euBaseURL}/api/1/partner_accounts/public_key?domain={this.configuration.Domain}", cancellationToken); string responseBody = await response.Content.ReadAsStringAsync(cancellationToken); logger.LogInformation("Application registration result: {Result}", responseBody);