Adds application authorization endpoint
All checks were successful
Build, Push and Run Container / build (push) Successful in 32s
All checks were successful
Build, Push and Run Container / build (push) Successful in 32s
Implements the /Authorize endpoint to redirect users to the Tesla authentication page. This allows users to grant the application permission to access their Tesla account data. Updates the public key resource to be copied on build, ensuring it is always available at runtime. Adds logic to validate the application registration by comparing the public key retrieved from the Tesla API with the public key stored locally.
This commit is contained in:
@@ -51,6 +51,7 @@ if (app.Environment.IsDevelopment())
|
||||
});
|
||||
app.MapGet("/CheckRegisteredApplication", ([FromServices] TeslaAuthenticatorService service) => service.CheckApplicationRegistrationAsync());
|
||||
app.MapGet("/RegisterApplication", ([FromServices] TeslaAuthenticatorService service) => service.RegisterApplicationAsync());
|
||||
app.MapGet("/Authorize", ([FromServices] TeslaAuthenticatorService service) => new RedirectResult(service.GetAplicationAuthorizationURL()));
|
||||
}
|
||||
|
||||
//Map static assets
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Resources\Signature\public-key.pem">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
@@ -220,12 +222,73 @@ public class TeslaAuthenticatorService
|
||||
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", partnerToken.AccessToken);
|
||||
|
||||
try
|
||||
{
|
||||
//Send request
|
||||
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);
|
||||
return new Result<bool>();
|
||||
|
||||
//Parse response
|
||||
using JsonDocument? doc = JsonDocument.Parse(responseBody);
|
||||
string publicKeyHex = doc.RootElement.GetProperty("response").GetProperty("public_key").GetString() ?? throw new JsonException("Public key not found in response");
|
||||
|
||||
//Public key bytes
|
||||
byte[] bytes = Convert.FromHexString(publicKeyHex);
|
||||
|
||||
//Get bytes from PEM key
|
||||
string pem = await File.ReadAllTextAsync("Resources/Signature/public-key.pem", cancellationToken: cancellationToken);;
|
||||
string[] lines = pem.Split('\n')
|
||||
.Select(l => l.Trim())
|
||||
.Where(l => !string.IsNullOrEmpty(l) &&
|
||||
!l.StartsWith("-----"))
|
||||
.ToArray();
|
||||
|
||||
string base64 = string.Join("", lines);
|
||||
byte[] pemBytes = Convert.FromBase64String(base64);
|
||||
|
||||
// Parse the PEM with ECDsa to get the raw Q.X||Q.Y
|
||||
using var ecdsa = ECDsa.Create();
|
||||
ecdsa.ImportSubjectPublicKeyInfo(pemBytes, out _);
|
||||
ECParameters parameters = ecdsa.ExportParameters(false);
|
||||
byte[]? x = parameters.Q.X;
|
||||
byte[]? y = parameters.Q.Y;
|
||||
|
||||
if (x is null || y is null)
|
||||
throw new CryptographicException("Invalid PEM file");
|
||||
|
||||
// Assemble into uncompressed SEC1 format
|
||||
byte[] pemKeyBytes = new byte[1 + x.Length + y.Length];
|
||||
pemKeyBytes[0] = 0x04; // uncompressed marker
|
||||
Buffer.BlockCopy(x, 0, pemKeyBytes, 1, x.Length);
|
||||
Buffer.BlockCopy(y, 0, pemKeyBytes, 1 + x.Length, y.Length);
|
||||
|
||||
// Compare
|
||||
bool match = bytes.SequenceEqual(pemKeyBytes);
|
||||
|
||||
return Result.Success(match);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, "Error while checking application registration");
|
||||
return Result.Fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
public string GetAplicationAuthorizationURL()
|
||||
{
|
||||
//https://auth.tesla.com/oauth2/v3/authorize?&client_id=$CLIENT_ID&locale=en-US&prompt=login&redirect_uri=$REDIRECT_URI&response_type=code&scope=openid%20vehicle_device_data%20offline_access&state=$STATE
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("https://auth.tesla.com/oauth2/v3/authorize?response_type=code");
|
||||
sb.AppendFormat("&client_id={0}", this.configuration.ClientID);
|
||||
sb.AppendFormat("&redirect_uri={0}");
|
||||
sb.AppendFormat("&scope=openid offline_access vehicle_device_data vehicle_location");
|
||||
sb.AppendFormat("&state=1234567890");
|
||||
sb.AppendFormat("&nonce=1234567890");
|
||||
sb.AppendFormat("&prompt_missing_scopes=true");
|
||||
sb.AppendFormat("&require_requested_scopes=true");
|
||||
sb.AppendFormat("&show_keypair_step=true");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user