Add authorization and key-pairing
All checks were successful
Build, Push and Run Container / build (push) Successful in 30s
All checks were successful
Build, Push and Run Container / build (push) Successful in 30s
This commit is contained in:
19
Source/ProofOfConcept/Configurator.cs
Normal file
19
Source/ProofOfConcept/Configurator.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
namespace ProofOfConcept;
|
||||||
|
|
||||||
|
public class Configurator
|
||||||
|
{
|
||||||
|
private readonly IHostEnvironment environment;
|
||||||
|
private readonly IConfiguration configuration;
|
||||||
|
|
||||||
|
public Configurator(IHostEnvironment environment, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
this.environment = environment;
|
||||||
|
this.configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ConfiguratorExtensions
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
|
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||||
using ProofOfConcept.Models;
|
using ProofOfConcept.Models;
|
||||||
using ProofOfConcept.Services;
|
using ProofOfConcept.Services;
|
||||||
using ProofOfConcept.Utilities;
|
using ProofOfConcept.Utilities;
|
||||||
@@ -19,10 +23,35 @@ builder.Services.AddMemoryCache();
|
|||||||
builder.Services.AddHybridCache();
|
builder.Services.AddHybridCache();
|
||||||
builder.Services.AddHttpClient();
|
builder.Services.AddHttpClient();
|
||||||
builder.Services.AddRazorPages();
|
builder.Services.AddRazorPages();
|
||||||
|
builder.Services.AddHealthChecks()
|
||||||
|
.AddAsyncCheck("", cancellationToken => Task.FromResult(HealthCheckResult.Healthy()), ["ready"]); //TODO: Check tag
|
||||||
|
builder.Services.AddAuthentication(options =>
|
||||||
|
{
|
||||||
|
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||||
|
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
|
||||||
|
})
|
||||||
|
.AddCookie()
|
||||||
|
.AddOpenIdConnect(options =>
|
||||||
|
{
|
||||||
|
options.Authority = "https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3"; // Tesla auth
|
||||||
|
options.ClientId = "b2240ee4-332a-4252-91aa-bbcc24f78fdb";
|
||||||
|
options.ClientSecret = "ta-secret.YG+XSdlvr6Lv8U-x";
|
||||||
|
options.ResponseType = "code";
|
||||||
|
options.SaveTokens = true; // access_token, refresh_token in auth ticket
|
||||||
|
options.CallbackPath = new PathString("/token-exchange");
|
||||||
|
options.Scope.Add("openid");
|
||||||
|
options.Scope.Add("offline_access");
|
||||||
|
options.Scope.Add("vehicle_device_data");
|
||||||
|
options.Scope.Add("vehicle_location");
|
||||||
|
options.AdditionalAuthorizationParameters.Add("prompt_missing_scopes", "true");
|
||||||
|
options.AdditionalAuthorizationParameters.Add("require_requested_scopes", "true");
|
||||||
|
options.AdditionalAuthorizationParameters.Add("show_keypair_step", "true");
|
||||||
|
// PKCE, state, nonce are handled automatically
|
||||||
|
});
|
||||||
|
|
||||||
// Add own services
|
// Add own services
|
||||||
builder.Services.AddSingleton<IMessageProcessor, MessageProcessor>();
|
builder.Services.AddSingleton<IMessageProcessor, MessageProcessor>();
|
||||||
builder.Services.AddTransient<TeslaAuthenticatorService>();
|
builder.Services.AddTransient<ITeslaAuthenticatorService, TeslaAuthenticatorService>();
|
||||||
|
|
||||||
// Add hosted services
|
// Add hosted services
|
||||||
builder.Services.AddHostedService<MQTTServer>();
|
builder.Services.AddHostedService<MQTTServer>();
|
||||||
@@ -51,12 +80,15 @@ if (app.Environment.IsDevelopment())
|
|||||||
});
|
});
|
||||||
app.MapGet("/CheckRegisteredApplication", ([FromServices] TeslaAuthenticatorService service) => service.CheckApplicationRegistrationAsync());
|
app.MapGet("/CheckRegisteredApplication", ([FromServices] TeslaAuthenticatorService service) => service.CheckApplicationRegistrationAsync());
|
||||||
app.MapGet("/RegisterApplication", ([FromServices] TeslaAuthenticatorService service) => service.RegisterApplicationAsync());
|
app.MapGet("/RegisterApplication", ([FromServices] TeslaAuthenticatorService service) => service.RegisterApplicationAsync());
|
||||||
app.MapGet("/Authorize", ([FromServices] TeslaAuthenticatorService service) => new RedirectResult(service.GetAplicationAuthorizationURL()));
|
app.MapGet("/Authorize", (async context => await context.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = "/" })));
|
||||||
|
app.MapGet("/KeyPairing", () => Results.Redirect("https://tesla.com/_ak/developer-domain.com"));
|
||||||
}
|
}
|
||||||
|
|
||||||
//Map static assets
|
//Map static assets
|
||||||
app.MapStaticAssets();
|
app.MapStaticAssets();
|
||||||
|
|
||||||
|
//TODO: Build a middleware that responds with 503 if the public key is not registered at Tesla
|
||||||
|
|
||||||
app.MapRazorPages();
|
app.MapRazorPages();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.8" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0-preview.6.25358.103"/>
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0-preview.6.25358.103"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" Version="9.7.0" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" Version="9.7.0" />
|
||||||
<PackageReference Include="MQTTnet" Version="5.0.1.1416" />
|
<PackageReference Include="MQTTnet" Version="5.0.1.1416" />
|
||||||
|
|||||||
@@ -11,7 +11,30 @@ using SzakatsA.Result;
|
|||||||
|
|
||||||
namespace ProofOfConcept.Services;
|
namespace ProofOfConcept.Services;
|
||||||
|
|
||||||
public class TeslaAuthenticatorService
|
public interface ITeslaAuthenticatorService
|
||||||
|
{
|
||||||
|
/// Asynchronously retrieves an authentication token from the Tesla partner API.
|
||||||
|
/// This method sends a POST request with client credentials to obtain an
|
||||||
|
/// OAuth2 access token that grants access to Tesla partner services.
|
||||||
|
/// <return>Returns a string containing the authentication token received from the API.</return>
|
||||||
|
/// <exception cref="HttpRequestException">Thrown when the HTTP request to the API fails.</exception>
|
||||||
|
Task<Result<Token>> AcquirePartnerAuthenticationTokenAsync();
|
||||||
|
|
||||||
|
/// Retrieves a cached partner authentication token or acquires a new one if not available.
|
||||||
|
/// This method first attempts to fetch the token from the memory cache. If the token is not found
|
||||||
|
/// or has expired, it invokes the method to retrieve a new token from the Tesla partner API and stores it in the cache.
|
||||||
|
/// The cached token is set to expire slightly earlier than its actual expiration time to avoid unexpected token expiry during usage.
|
||||||
|
/// <return>Returns a Token object containing the authentication token and its expiration details.</return>
|
||||||
|
/// <exception cref="HttpRequestException">Thrown when the HTTP request to acquire a new partner token fails.</exception>
|
||||||
|
/// <exception cref="Exception">Thrown for any unexpected errors during the token retrieval or caching process.</exception>
|
||||||
|
Task<Result<Token>> GetPartnerAuthenticationTokenAsync();
|
||||||
|
|
||||||
|
Task<Result> RegisterApplicationAsync(CancellationToken cancellationToken = default(CancellationToken));
|
||||||
|
Task<Result<bool>> CheckApplicationRegistrationAsync(CancellationToken cancellationToken = default(CancellationToken));
|
||||||
|
string GetAplicationAuthorizationURL();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TeslaAuthenticatorService : ITeslaAuthenticatorService
|
||||||
{
|
{
|
||||||
private readonly ILogger<TeslaAuthenticatorService> logger;
|
private readonly ILogger<TeslaAuthenticatorService> logger;
|
||||||
private readonly TeslaAuthenticatorServiceConfiguration configuration;
|
private readonly TeslaAuthenticatorServiceConfiguration configuration;
|
||||||
@@ -281,10 +304,10 @@ public class TeslaAuthenticatorService
|
|||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.Append("https://auth.tesla.com/oauth2/v3/authorize?response_type=code");
|
sb.Append("https://auth.tesla.com/oauth2/v3/authorize?response_type=code");
|
||||||
sb.AppendFormat("&client_id={0}", this.configuration.ClientID);
|
sb.AppendFormat("&client_id={0}", this.configuration.ClientID);
|
||||||
sb.AppendFormat("&redirect_uri={0}");
|
sb.AppendFormat("&redirect_uri={0}", "https://automatic-parking.app/token-exchange");
|
||||||
sb.AppendFormat("&scope=openid offline_access vehicle_device_data vehicle_location");
|
sb.AppendFormat("&scope=openid offline_access vehicle_device_data vehicle_location");
|
||||||
sb.AppendFormat("&state=1234567890");
|
sb.AppendFormat("&state={0}", "");
|
||||||
sb.AppendFormat("&nonce=1234567890");
|
sb.AppendFormat("&nonce={0}", "");
|
||||||
sb.AppendFormat("&prompt_missing_scopes=true");
|
sb.AppendFormat("&prompt_missing_scopes=true");
|
||||||
sb.AppendFormat("&require_requested_scopes=true");
|
sb.AppendFormat("&require_requested_scopes=true");
|
||||||
sb.AppendFormat("&show_keypair_step=true");
|
sb.AppendFormat("&show_keypair_step=true");
|
||||||
|
|||||||
Reference in New Issue
Block a user