Files
Automatic-Parking/Source/ProofOfConcept/Program.cs
Szakáts Alpár Zsolt e392f5a7b8
All checks were successful
Build, Push and Run Container / build (push) Successful in 25s
Configures forwarded headers for the application
Ensures the application correctly handles forwarded headers
by explicitly setting the `ForwardedHeaders` option to `ForwardedHeaders.All`.

This allows the application to properly determine the client's
IP address and other request information when behind a proxy
or load balancer.
2025-08-16 22:14:35 +02:00

155 lines
6.2 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");
// 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();
app.UseForwardedHeaders(new ForwardedHeadersOptions() { ForwardedHeaders = ForwardedHeaders.All });
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;
return $"{request.Scheme}://{request.Host}/token-exchange";
});
}
//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();