Editor/Core/SteamTokenHttpVerifier.cs
using System.Threading;
using System.Threading.Tasks;
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace TeamCreate;
public readonly record struct SteamTokenHttpResponse(int? Status, string Body);
public interface ISteamTokenTransport
{
Task<SteamTokenHttpResponse> PostAsync(string url, string jsonBody, CancellationToken cancellationToken);
}
public static class SteamTokenEndpoint
{
private sealed record SteamTokenRequest([property: JsonPropertyName("steamid")] long SteamId, [property: JsonPropertyName("token")] string Token);
private sealed record SteamTokenResponse
{
[JsonPropertyName("SteamId")]
public long? SteamId { get; set; }
[JsonPropertyName("Status")]
public string Status { get; set; }
}
public const string VerifyUrl = "https://services.facepunch.com/sbox/auth/token";
public const string RequestSteamIdField = "steamid";
public const string RequestTokenField = "token";
public const string ResponseSteamIdField = "SteamId";
public const string ResponseStatusField = "Status";
public const int MaxResponseBytes = 65536;
private static readonly JsonSerializerOptions ResponseOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
public static string BuildRequestBody(long claimedSteamId, string token)
{
return JsonSerializer.Serialize(new SteamTokenRequest(claimedSteamId, token));
}
public static long? ReadVerifiedSteamId(string body)
{
if (!TryRead(body, out var parsed))
{
return null;
}
return parsed.SteamId;
}
public static string ReadStatus(string body)
{
if (!TryRead(body, out var parsed))
{
return null;
}
return parsed.Status;
}
private static bool TryRead(string body, out SteamTokenResponse parsed)
{
parsed = null;
if (string.IsNullOrWhiteSpace(body) || body.Length > 65536)
{
return false;
}
try
{
parsed = JsonSerializer.Deserialize<SteamTokenResponse>(body, ResponseOptions);
return (object)parsed != null;
}
catch (JsonException)
{
return false;
}
catch (NotSupportedException)
{
return false;
}
}
}
public sealed class SteamTokenHttpVerifier
{
private readonly ISteamTokenTransport transport;
public SteamTokenHttpVerifier(ISteamTokenTransport transport)
{
this.transport = transport ?? throw new ArgumentNullException("transport");
}
public async Task<SteamTokenVerdict> VerifyAsync(Packet hello, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(hello?.SteamToken))
{
return SteamTokenVerdict.TokenMissing;
}
if (!SteamTokenRules.IsPlausibleToken(hello.SteamToken))
{
return SteamTokenVerdict.TokenMalformed;
}
if (!SteamIdentityRules.TryParse(hello.SteamId, out var identity))
{
return SteamTokenVerdict.SteamIdMismatch;
}
if (identity.Value == 0)
{
return SteamTokenVerdict.SteamIdMismatch;
}
if (identity.Value > long.MaxValue)
{
return SteamTokenVerdict.SteamIdMismatch;
}
SteamTokenHttpResponse response;
try
{
response = await transport.PostAsync("https://services.facepunch.com/sbox/auth/token", SteamTokenEndpoint.BuildRequestBody((long)identity.Value, hello.SteamToken), cancellationToken);
}
catch (OperationCanceledException)
{
return SteamTokenVerdict.ServiceUnavailable;
}
catch (Exception)
{
return SteamTokenVerdict.ServiceUnavailable;
}
if (!response.Status.HasValue)
{
return SteamTokenVerdict.ServiceUnavailable;
}
long? verified = null;
string status = null;
if (response.Status.Value == 200)
{
verified = SteamTokenEndpoint.ReadVerifiedSteamId(response.Body);
status = SteamTokenEndpoint.ReadStatus(response.Body);
}
return SteamTokenRules.Decide((long)identity.Value, hello.SteamToken, response.Status, verified, status);
}
}