:drunk: AIGC steam connection support (w.i.p) (skip ci)

This commit is contained in:
2025-11-04 01:28:51 +08:00
parent b8fa5f5f24
commit 433230b495
4 changed files with 155 additions and 17 deletions

View File

@@ -353,24 +353,36 @@ public class ConnectionController(
private static async Task<OidcCallbackData> ExtractCallbackData(HttpRequest request) private static async Task<OidcCallbackData> ExtractCallbackData(HttpRequest request)
{ {
var data = new OidcCallbackData(); var data = new OidcCallbackData();
switch (request.Method)
{
case "GET":
data.Code = Uri.UnescapeDataString(request.Query["code"].FirstOrDefault() ?? "");
data.IdToken = Uri.UnescapeDataString(request.Query["id_token"].FirstOrDefault() ?? "");
data.State = Uri.UnescapeDataString(request.Query["state"].FirstOrDefault() ?? "");
break;
case "POST" when request.HasFormContentType:
{
var form = await request.ReadFormAsync();
data.Code = Uri.UnescapeDataString(form["code"].FirstOrDefault() ?? "");
data.IdToken = Uri.UnescapeDataString(form["id_token"].FirstOrDefault() ?? "");
data.State = Uri.UnescapeDataString(form["state"].FirstOrDefault() ?? "");
if (form.ContainsKey("user"))
data.RawData = Uri.UnescapeDataString(form["user"].FirstOrDefault() ?? "");
break; // Extract data based on request method
} if (request.Method == "GET")
{
// Extract from query string
data.Code = Uri.UnescapeDataString(request.Query["code"].FirstOrDefault() ?? "");
data.IdToken = Uri.UnescapeDataString(request.Query["id_token"].FirstOrDefault() ?? "");
data.State = Uri.UnescapeDataString(request.Query["state"].FirstOrDefault() ?? "");
// Populate all query parameters for providers that need them (like Steam OpenID)
foreach (var param in request.Query)
{
data.QueryParameters[param.Key] = Uri.UnescapeDataString(param.Value.FirstOrDefault() ?? "");
}
}
else if (request.Method == "POST" && request.HasFormContentType)
{
var form = await request.ReadFormAsync();
data.Code = Uri.UnescapeDataString(form["code"].FirstOrDefault() ?? "");
data.IdToken = Uri.UnescapeDataString(form["id_token"].FirstOrDefault() ?? "");
data.State = Uri.UnescapeDataString(form["state"].FirstOrDefault() ?? "");
if (form.ContainsKey("user"))
data.RawData = Uri.UnescapeDataString(form["user"].FirstOrDefault() ?? "");
// Populate all form parameters
foreach (var param in form)
{
data.QueryParameters[param.Key] = Uri.UnescapeDataString(param.Value.FirstOrDefault() ?? "");
}
} }
return data; return data;

View File

@@ -124,6 +124,7 @@ public class OidcController(
"discord" => serviceProvider.GetRequiredService<DiscordOidcService>(), "discord" => serviceProvider.GetRequiredService<DiscordOidcService>(),
"github" => serviceProvider.GetRequiredService<GitHubOidcService>(), "github" => serviceProvider.GetRequiredService<GitHubOidcService>(),
"spotify" => serviceProvider.GetRequiredService<SpotifyOidcService>(), "spotify" => serviceProvider.GetRequiredService<SpotifyOidcService>(),
"steam" => serviceProvider.GetRequiredService<SteamOidcService>(),
"afdian" => serviceProvider.GetRequiredService<AfdianOidcService>(), "afdian" => serviceProvider.GetRequiredService<AfdianOidcService>(),
_ => throw new ArgumentException($"Unsupported provider: {provider}") _ => throw new ArgumentException($"Unsupported provider: {provider}")
}; };

View File

@@ -358,4 +358,5 @@ public class OidcCallbackData
public string IdToken { get; set; } = ""; public string IdToken { get; set; } = "";
public string? State { get; set; } public string? State { get; set; }
public string? RawData { get; set; } public string? RawData { get; set; }
public Dictionary<string, string> QueryParameters { get; set; } = new();
} }

View File

@@ -0,0 +1,124 @@
using System.Text.Json;
using DysonNetwork.Shared.Cache;
namespace DysonNetwork.Pass.Auth.OpenId;
public class SteamOidcService(
IConfiguration configuration,
IHttpClientFactory httpClientFactory,
AppDatabase db,
AuthService auth,
ICacheService cache
)
: OidcService(configuration, httpClientFactory, db, auth, cache)
{
public override string ProviderName => "steam";
protected override string DiscoveryEndpoint => ""; // Steam uses OpenID 2.0, not OIDC discovery
protected override string ConfigSectionName => "Steam";
public override Task<string> GetAuthorizationUrlAsync(string state, string nonce)
{
return Task.FromResult(GetAuthorizationUrl(state, nonce));
}
public override string GetAuthorizationUrl(string state, string nonce)
{
var config = GetProviderConfig();
var returnUrl = config.RedirectUri;
// Steam OpenID 2.0 authorization URL construction
var queryParams = new Dictionary<string, string>
{
{ "openid.ns", "http://specs.openid.net/auth/2.0" },
{ "openid.mode", "checkid_setup" },
{ "openid.return_to", returnUrl },
{ "openid.realm", new Uri(returnUrl).GetLeftPart(UriPartial.Authority) },
{ "openid.identity", "http://specs.openid.net/auth/2.0/identifier_select" },
{ "openid.claimed_id", "http://specs.openid.net/auth/2.0/identifier_select" },
// Store state in the return URL as a query parameter since Steam doesn't support state directly
{ "openid.state", state }
};
var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}"));
return $"https://steamcommunity.com/openid/login?{queryString}";
}
protected override Task<OidcDiscoveryDocument?> GetDiscoveryDocumentAsync()
{
// Steam doesn't use standard OIDC discovery
return Task.FromResult<OidcDiscoveryDocument?>(null);
}
public override async Task<OidcUserInfo> ProcessCallbackAsync(OidcCallbackData callbackData)
{
// Parse Steam's OpenID 2.0 response
var queryParams = callbackData.QueryParameters;
// Verify the OpenID response
if (queryParams.GetValueOrDefault("openid.mode") != "id_res")
{
throw new InvalidOperationException("Invalid OpenID response mode");
}
// Extract Steam ID from claimed_id
var claimedId = queryParams.GetValueOrDefault("openid.claimed_id");
if (string.IsNullOrEmpty(claimedId))
{
throw new InvalidOperationException("No claimed_id in OpenID response");
}
// Steam ID is the last part of the claimed_id URL
var steamId = claimedId.Split('/')[^1];
if (!ulong.TryParse(steamId, out _))
{
throw new InvalidOperationException("Invalid Steam ID format");
}
// Fetch user information from Steam API
var userInfo = await GetUserInfoAsync(steamId);
return userInfo;
}
private async Task<OidcUserInfo> GetUserInfoAsync(string steamId)
{
var config = GetProviderConfig();
var apiKey = Configuration[$"Oidc:Steam:ApiKey"] ?? throw new InvalidOperationException("Steam API key not configured");
var client = HttpClientFactory.CreateClient();
var url = $"https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v2/?key={apiKey}&steamids={steamId}";
var response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var steamResponse = JsonDocument.Parse(json).RootElement;
var players = steamResponse.GetProperty("response").GetProperty("players");
if (players.GetArrayLength() == 0)
{
throw new InvalidOperationException("Steam user not found");
}
var player = players[0];
var steamIdStr = player.GetProperty("steamid").GetString() ?? "";
var personaName = player.GetProperty("personaname").GetString() ?? "";
var avatarUrl = player.TryGetProperty("avatarfull", out var avatarElement)
? avatarElement.GetString()
: player.TryGetProperty("avatarmedium", out var mediumAvatarElement)
? mediumAvatarElement.GetString()
: null;
return new OidcUserInfo
{
UserId = steamIdStr,
DisplayName = personaName,
PreferredUsername = personaName,
ProfilePictureUrl = avatarUrl,
Provider = ProviderName,
// Steam OpenID doesn't provide email
Email = null,
EmailVerified = false
};
}
}