added auth with a simple UI
This commit is contained in:
36
api/OED.Api/Infrastructure/Esi/EsiAuthHandler.cs
Normal file
36
api/OED.Api/Infrastructure/Esi/EsiAuthHandler.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Net.Http.Headers;
|
||||
using OED.Api.Core.Interfaces.Services;
|
||||
|
||||
namespace OED.Api.Infrastructure.Esi;
|
||||
|
||||
public class EsiAuthHandler(
|
||||
ITokenStore tokenStore,
|
||||
IEsiTokenRefreshService refreshService,
|
||||
IHttpContextAccessor httpContextAccessor
|
||||
) : DelegatingHandler
|
||||
{
|
||||
protected override async Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var characterId = GetCharacterIdFromSession();
|
||||
if (characterId is null)
|
||||
return await base.SendAsync(request, cancellationToken);
|
||||
|
||||
var accessToken = await tokenStore.GetAccessTokenAsync(characterId.Value);
|
||||
|
||||
if (accessToken is null)
|
||||
accessToken = await refreshService.RefreshAsync(characterId.Value);
|
||||
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
|
||||
|
||||
return await base.SendAsync(request, cancellationToken);
|
||||
}
|
||||
|
||||
private long? GetCharacterIdFromSession()
|
||||
{
|
||||
var context = httpContextAccessor.HttpContext;
|
||||
// CharacterId is set on the HttpContext by session middleware
|
||||
return context?.Items["CharacterId"] as long?;
|
||||
}
|
||||
}
|
||||
61
api/OED.Api/Infrastructure/Esi/EsiTokenRefreshService.cs
Normal file
61
api/OED.Api/Infrastructure/Esi/EsiTokenRefreshService.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using OED.Api.Core.Interfaces.Services;
|
||||
|
||||
namespace OED.Api.Infrastructure.Esi;
|
||||
|
||||
public interface IEsiTokenRefreshService
|
||||
{
|
||||
Task<string> RefreshAsync(long characterId);
|
||||
}
|
||||
|
||||
public class EsiTokenRefreshService(
|
||||
IHttpClientFactory httpClientFactory,
|
||||
ITokenStore tokenStore,
|
||||
IConfiguration config
|
||||
) : IEsiTokenRefreshService
|
||||
{
|
||||
public async Task<string> RefreshAsync(long characterId)
|
||||
{
|
||||
var refreshToken = await tokenStore.GetRefreshTokenAsync(characterId)
|
||||
?? throw new InvalidOperationException($"No refresh token found for character {characterId}");
|
||||
|
||||
var clientId = config["Eve:ClientId"]!;
|
||||
var secretKey = config["Eve:SecretKey"]!;
|
||||
|
||||
var credentials = Convert.ToBase64String(
|
||||
System.Text.Encoding.UTF8.GetBytes($"{clientId}:{secretKey}")
|
||||
);
|
||||
|
||||
var client = httpClientFactory.CreateClient();
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, "https://login.eveonline.com/v2/oauth/token");
|
||||
request.Headers.Add("Authorization", $"Basic {credentials}");
|
||||
request.Content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
["grant_type"] = "refresh_token",
|
||||
["refresh_token"] = refreshToken
|
||||
});
|
||||
|
||||
var response = await client.SendAsync(request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<EsiTokenResponse>()
|
||||
?? throw new InvalidOperationException("Empty token response from EVE SSO");
|
||||
|
||||
// Always overwrite — EVE may rotate the refresh token
|
||||
await tokenStore.SetRefreshTokenAsync(characterId, result.RefreshToken);
|
||||
await tokenStore.SetAccessTokenAsync(characterId, result.AccessToken, TimeSpan.FromSeconds(result.ExpiresIn - 30));
|
||||
|
||||
return result.AccessToken;
|
||||
}
|
||||
}
|
||||
|
||||
public record EsiTokenResponse(
|
||||
string access_token,
|
||||
int expires_in,
|
||||
string token_type,
|
||||
string refresh_token
|
||||
)
|
||||
{
|
||||
public string AccessToken => access_token;
|
||||
public int ExpiresIn => expires_in;
|
||||
public string RefreshToken => refresh_token;
|
||||
}
|
||||
Reference in New Issue
Block a user