using AchiesUtilities.Models; using Microsoft.IdentityModel.JsonWebTokens; using SteamLib.Account; using SteamLib.Core.Enums; using System.Collections.ObjectModel; using System.Net.Http.Headers; using System.Text.RegularExpressions; namespace SteamLib.Authentication; public static class SteamTokenHelper { public static readonly Regex SteamTokenRegex = new(@"^(?(?7\d{16,})(?:%7C%7C))?(?.+)$", RegexOptions.Compiled); public static readonly IReadOnlyDictionary AudDomains = new ReadOnlyDictionary(new Dictionary { {"web:community", SteamDomain.Community}, {"web:store", SteamDomain.Store}, {"web:help", SteamDomain.Help}, {"web:steamtv", SteamDomain.TV}, {"web:checkout", SteamDomain.Checkout}, }); public static bool TryParse(string input, out SteamAuthToken result) { return Parse(input, out result, true); } public static SteamAuthToken Parse(string input) { if (Parse(input, out var result, false)) { return result; } else { throw new ArgumentException("Provided Token is not valid JWT token", nameof(input)); } } private static bool Parse(string input, out SteamAuthToken result, bool trying) { result = default; var match = SteamTokenRegex.Match(input); if (match.Success == false) { if (trying) return false; throw new ArgumentException("Provided Token is not valid SteamLoginSecure or SteamAccess token"); } var jwtStr = match.Groups["jwt"].Value; JsonWebToken jwt; try { jwt = new JsonWebToken(jwtStr); } catch (Exception e) { if (trying) return false; throw new ArgumentException("Provided Token is not valid JWT token", nameof(input), e); } var steamIdStr = jwt.Subject; if (steamIdStr == null || long.TryParse(steamIdStr, out var steamId) == false || steamId < SteamId64.SEED) { if (trying) return false; throw new ArgumentException( $"Provided Token is not valid JWT token. Subject claim is null or not valid SteamId '{steamIdStr}'", nameof(input)); } SteamId id; try { id = SteamId.FromSteam64(steamId); } catch (Exception ex) { if (trying) return false; throw new ArgumentException( $"Provided Token is not valid JWT token. Subject is claim not valid SteamId '{steamIdStr}'", nameof(input), ex); } var exp = jwt.ValidTo; if (exp == DateTime.MinValue) { if (trying) return false; throw new ArgumentException("Provided Token has no 'exp' claim.", nameof(input)); } var expires = UnixTimeStamp.FromDateTime(exp); var domain = SteamDomain.Undefined; var audiences = jwt.Audiences.ToList(); var tokenType = SteamAccessTokenType.Unknown; if (jwt.Audiences.ToList().Count > 0) { var aud = audiences[0]; if (AudDomains.TryGetValue(aud, out domain) == false && aud != "web") { if (trying) { result = new SteamAuthToken(jwtStr, id, expires, domain, tokenType); return false; } throw new ArgumentException($"Provided Token has invalid 'aud' claim. Value: {aud}", nameof(input)); } tokenType = SteamAccessTokenType.AccessToken; if (aud == "web") { var isMobile = audiences.Any(a => a.Equals("mobile")); var isRefresh = audiences.Any(a => a.Equals("renew")); if (isMobile ^ isRefresh) { tokenType = isMobile ? SteamAccessTokenType.Mobile : SteamAccessTokenType.Refresh; } else if (isMobile && isRefresh) { tokenType = SteamAccessTokenType.MobileRefresh; } else if (audiences.Count == 1) { tokenType = SteamAccessTokenType.Web; } else { if (trying) { result = new SteamAuthToken(jwtStr, id, expires, domain, SteamAccessTokenType.Unknown); return false; } var auds = string.Join(", ", audiences); throw new ArgumentException($"Provided Token has invalid 'aud' claim combination. Values: {auds}", nameof(input)); } } } result = new SteamAuthToken(jwtStr, id, expires, domain, tokenType); return true; } public static string ExtractJwtFromSetCookiesHeader(HttpHeaders headers) { var setCookies = headers.GetValues("Set-Cookie"); var steamLoginSecureHeader = setCookies.FirstOrDefault(x => x.StartsWith("steamLoginSecure=")); if (steamLoginSecureHeader == null) throw new ArgumentException("Can't find steamLoginSecure cookie in Set-Cookie header"); var firstCookiePart = steamLoginSecureHeader .ToCharArray() .SkipWhile(ch => !ch.Equals('=')) .Skip(1) .TakeWhile(ch => !ch.Equals(';')) .ToArray(); var steamLoginSecure = new string(firstCookiePart); return ExtractJwtToken(steamLoginSecure); } public static string CombineJwtWithSteamId(long steamId, string loginValue) => steamId + "%7C%7C" + loginValue; internal static bool CheckIfProperLoginValue(string value) => value.Contains("%7C%7C"); internal static string CombineLoginValueIfNeeded(long steamId, string loginValue) { return CheckIfProperLoginValue(loginValue) == false ? CombineJwtWithSteamId(steamId, loginValue) : loginValue; } public static string ExtractJwtToken(string steamLoginSecure) { var match = SteamTokenRegex.Match(steamLoginSecure); if (match.Success == false) throw new ArgumentException("Can't extract JWT Access Token from steamLoginSecure"); return match.Groups["jwt"].Value; } }