Files
Automatic-Parking/Source/ProofOfConcept/Program.cs
Szakáts Alpár Zsolt b6cd5e404e
All checks were successful
Build, Push and Run Container / build (push) Successful in 25s
Disables issuer validation
Disables issuer validation during token authentication.

The token validation parameters are adjusted to bypass
issuer validation, since the issuer is already validated via the `ValidIssuers` parameter.
2025-08-16 22:36:35 +02:00

195 lines
9.1 KiB
C#

using System.Text.Json;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using ProofOfConcept.Models;
using ProofOfConcept.Services;
using ProofOfConcept.Utilities;
var builder = WebApplication.CreateSlimBuilder(args);
// Load static web assets manifest (referenced libs + your wwwroot)
builder.WebHost.UseStaticWebAssets();
// builder.Services.ConfigureHttpJsonOptions(options => { options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); });
// Add services
builder.Services.AddOpenApi();
builder.Services.AddMediator();
builder.Services.AddMemoryCache();
builder.Services.AddHybridCache();
builder.Services.AddHttpClient();
builder.Services.AddRazorPages();
builder.Services.AddHealthChecks()
.AddAsyncCheck("", cancellationToken => Task.FromResult(HealthCheckResult.Healthy()), ["ready"]); //TODO: Check tag
builder.Services.AddHttpContextAccessor();
builder.Services.AddAuthentication(o =>
{
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(o =>
{
const string TeslaAuthority = "https://auth.tesla.com/oauth2/v3";
const string TeslaMetadataEndpoint = $"{TeslaAuthority}/.well-known/openid-configuration";
const string FleetAuthTokenEndpoint = "https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3/token";
const string FleetApiAudience = "https://fleet-api.prd.eu.vn.cloud.tesla.com";
// Let the middleware do discovery/JWKS (on demand), but override token endpoint
o.ConfigurationManager = new TeslaOIDCConfigurationManager(TeslaMetadataEndpoint, FleetAuthTokenEndpoint);
// Standard OIDC settings
o.Authority = TeslaAuthority; // discovery + /authorize
o.ClientId = "b2240ee4-332a-4252-91aa-bbcc24f78fdb";
o.ClientSecret = "ta-secret.YG+XSdlvr6Lv8U-x";
o.ResponseType = OpenIdConnectResponseType.Code;
o.UsePkce = true;
o.SaveTokens = true;
// This must match exactly what you register at Tesla
o.CallbackPath = new PathString("/token-exchange");
o.TokenValidationParameters.ValidateIssuer = false;
o.TokenValidationParameters.ValidIssuers = ["https://fleet-auth.tesla.com/oauth2/v3/nts", "https://auth.tesla.com/oauth2/v3", "https://fleet-auth.prd.vn.cloud.tesla.com/oauth2/v3/nts"];
// Scopes you actually need
o.Scope.Clear();
o.Scope.Add("openid");
o.Scope.Add("offline_access");
o.Scope.Add("vehicle_device_data");
o.Scope.Add("vehicle_location");
// Optional Tesla parameters
o.AdditionalAuthorizationParameters.Add("prompt_missing_scopes", "true");
o.AdditionalAuthorizationParameters.Add("require_requested_scopes", "true");
o.AdditionalAuthorizationParameters.Add("show_keypair_step", "true");
// If keys rotate during runtime, auto-refresh JWKS
o.RefreshOnIssuerKeyNotFound = true;
// Add Tesla's required audience to the token request
o.Events = new OpenIdConnectEvents
{
OnAuthorizationCodeReceived = ctx =>
{
if (ctx.TokenEndpointRequest is not null)
ctx.TokenEndpointRequest.Parameters["audience"] = FleetApiAudience;
return Task.CompletedTask;
}
};
});
// Add own services
builder.Services.AddSingleton<IMessageProcessor, MessageProcessor>();
builder.Services.AddTransient<ITeslaAuthenticatorService, TeslaAuthenticatorService>();
// Add hosted services
builder.Services.AddHostedService<MQTTServer>();
builder.Services.AddHostedService<MQTTClient>();
//Build app
WebApplication app = builder.Build();
ForwardedHeadersOptions forwardedHeadersOptions = new ForwardedHeadersOptions() { ForwardedHeaders = ForwardedHeaders.All };
forwardedHeadersOptions.KnownNetworks.Clear();
forwardedHeadersOptions.KnownProxies.Clear();
app.UseForwardedHeaders(forwardedHeadersOptions);
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapGet("/GetPartnerAuthenticationToken", ([FromServices] TeslaAuthenticatorService service) => service.GetPartnerAuthenticationTokenAsync());
app.MapGet("/PartnerToken", ([FromQueryAttribute] string json, [FromServices] IMemoryCache memoryCache) =>
{
var serializerOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
Token? token = JsonSerializer.Deserialize<Token>(json, serializerOptions);
if (token is not null)
memoryCache.Set(Keys.TeslaPartnerToken, token, token.Expires.Subtract(TimeSpan.FromSeconds(5)));
return JsonSerializer.Serialize(token, new JsonSerializerOptions() { WriteIndented = true });
});
app.MapGet("/CheckRegisteredApplication", ([FromServices] TeslaAuthenticatorService service) => service.CheckApplicationRegistrationAsync());
app.MapGet("/RegisterApplication", ([FromServices] TeslaAuthenticatorService service) => service.RegisterApplicationAsync());
app.MapGet("/Authorize", async (IHttpContextAccessor contextAccessor) => await (contextAccessor.HttpContext!).ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = "/tokens" }));
app.MapGet("/KeyPairing", () => Results.Redirect("https://tesla.com/_ak/developer-domain.com"));
app.MapGet("/Tokens", async (IHttpContextAccessor httpContextAccessor) =>
{
var ctx = httpContextAccessor.HttpContext!;
var accessToken = await ctx.GetTokenAsync("access_token");
var idToken = await ctx.GetTokenAsync("id_token");
var refreshToken = await ctx.GetTokenAsync("refresh_token");
var expiresAtRaw = await ctx.GetTokenAsync("expires_at"); // ISO 8601 string
JsonSerializer.Serialize(new
{
AccessToken = accessToken,
IDToken = idToken,
RefreshToken = refreshToken,
ExpiresAtRaw = expiresAtRaw
});
});
app.MapGet("DebugProxy", (IHttpContextAccessor httpContextAccessor) =>
{
var ctx = httpContextAccessor.HttpContext!;
var request = ctx.Request;
Dictionary<string, string> headers = new Dictionary<string, string>();
headers.Add("Host", request.Host.Value ?? "");
headers.Add("Scheme", request.Scheme);
headers.Add("Method", request.Method);
headers.Add("Path", request.Path.Value ?? "");
headers.Add("QueryString", request.QueryString.Value ?? "");
headers.Add("RemoteIpAddress", ctx.Connection.RemoteIpAddress?.ToString() ?? "");
headers.Add("RemotePort", ctx.Connection.RemotePort.ToString());
headers.Add("LocalIpAddress", ctx.Connection.LocalIpAddress?.ToString() ?? "");
headers.Add("LocalPort", ctx.Connection.LocalPort.ToString());
headers.Add("IsHttps", request.IsHttps.ToString());
headers.Add("X-Forwarded-For", request.Headers["X-Forwarded-For"].ToString());
headers.Add("X-Forwarded-Proto", request.Headers["X-Forwarded-Proto"].ToString());
headers.Add("X-Forwarded-Host", request.Headers["X-Forwarded-Host"].ToString());
headers.Add("X-Forwarded-Port", request.Headers["X-Forwarded-Port"].ToString());
headers.Add("X-Forwarded-Prefix", request.Headers["X-Forwarded-Prefix"].ToString());
headers.Add("X-Forwarded-Server", request.Headers["X-Forwarded-Server"].ToString());
headers.Add("X-Forwarded-Path", request.Headers["X-Forwarded-Path"].ToString());
headers.Add("X-Forwarded-PathBase", request.Headers["X-Forwarded-PathBase"].ToString());
headers.Add("X-Forwarded-Query", request.Headers["X-Forwarded-Query"].ToString());
headers.Add("X-Forwarded-Query-String", request.Headers["X-Forwarded-Query-String"].ToString());
headers.Add("Connection", request.Headers["Connection"].ToString());
headers.Add("Accept", request.Headers["Accept"].ToString());
headers.Add("Accept-Encoding", request.Headers["Accept-Encoding"].ToString());
headers.Add("Accept-Language", request.Headers["Accept-Language"].ToString());
headers.Add("Cache-Control", request.Headers["Cache-Control"].ToString());
headers.Add("Content-Length", request.Headers["Content-Length"].ToString());
headers.Add("Content-Type", request.Headers["Content-Type"].ToString());
headers.Add("Cookie", request.Headers["Cookie"].ToString());
headers.Add("Pragma", request.Headers["Pragma"].ToString());
headers.Add("Referer", request.Headers["Referer"].ToString());
String json = JsonSerializer.Serialize(headers, new JsonSerializerOptions() { WriteIndented = true });
return json;
});
}
//Map static assets
app.MapStaticAssets();
//TODO: Build a middleware that responds with 503 if the public key is not registered at Tesla
app.MapRazorPages();
app.Run();