diff --git a/src/NebulaAuth/Model/MAAC/PortableMaClient.cs b/src/NebulaAuth/Model/MAAC/PortableMaClient.cs index 6395256..23555e6 100644 --- a/src/NebulaAuth/Model/MAAC/PortableMaClient.cs +++ b/src/NebulaAuth/Model/MAAC/PortableMaClient.cs @@ -62,7 +62,7 @@ public partial class PortableMaClient : ObservableObject, IDisposable SetStatus(newStatus); ClientHandler.CookieContainer.ClearAllCookies(); - ClientHandler.CookieContainer.SetSteamMobileCookiesWithMobileToken(sessionData); + ClientHandler.CookieContainer.SetSteamMobileCookies(sessionData); } diff --git a/src/NebulaAuth/Model/MaClient.cs b/src/NebulaAuth/Model/MaClient.cs index 115f5e3..74598a2 100644 --- a/src/NebulaAuth/Model/MaClient.cs +++ b/src/NebulaAuth/Model/MaClient.cs @@ -38,7 +38,7 @@ public static class MaClient { ClientHandler.CookieContainer.ClearAllCookies(); if (account == null) return; - ClientHandler.CookieContainer.SetSteamMobileCookiesWithMobileToken(account.SessionData); + ClientHandler.CookieContainer.SetSteamMobileCookies(account.SessionData); Proxy.SetData(account.Proxy?.Data); } diff --git a/src/NebulaAuth/Model/SessionHandler/SessionHandler_API.cs b/src/NebulaAuth/Model/SessionHandler/SessionHandler_API.cs index b644462..fe40011 100644 --- a/src/NebulaAuth/Model/SessionHandler/SessionHandler_API.cs +++ b/src/NebulaAuth/Model/SessionHandler/SessionHandler_API.cs @@ -30,7 +30,7 @@ public partial class SessionHandler //API //Trigger PropertyChanged event for PortableMaClient handling session updated from MaClient mafile.SetSessionData(mafile.SessionData); await Storage.UpdateMafileAsync(mafile); - chp.Handler.CookieContainer.SetSteamMobileCookiesWithMobileToken(mafile.SessionData); + chp.Handler.CookieContainer.SetSteamMobileCookies(mafile.SessionData); } public static async Task LoginAgain(HttpClientHandlerPair chp, Mafile mafile, string password, bool savePassword) diff --git a/src/SteamLibForked/Authentication/AdmissionHelper.cs b/src/SteamLibForked/Authentication/AdmissionHelper.cs index 30ab7b2..57ece1c 100644 --- a/src/SteamLibForked/Authentication/AdmissionHelper.cs +++ b/src/SteamLibForked/Authentication/AdmissionHelper.cs @@ -54,33 +54,20 @@ public static class AdmissionHelper return; } - - AddRefreshToken(container, sessionData.RefreshToken); + container.SetSteamRefreshToken(sessionData.RefreshToken); var community = SteamDomains.GetDomainUri(SteamDomain.Community); container.Add(community, new Cookie(SESSION_ID_COOKIE_NAME, sessionData.SessionId, "/")); container.Add(community, new Cookie(LANGUAGE_COOKIE_NAME, setLanguage, "/")); TransferCommunityCookies(container); - foreach (var domain in SteamDomains.AuthDomains) + foreach (var domain in SteamDomains.WebDomains) { var token = sessionData.GetToken(domain); if (token == null) continue; - AddTokenCookie(container, token.Value); + container.SetSteamAccessToken(token.Value); } } - public static void SetDomainCookie(this CookieContainer container, SteamDomain domain, SteamAuthToken token) - { - var uri = SteamDomains.GetDomainUri(domain); - foreach (var cookie in container.GetCookies(uri) - .Where(c => !c.Expired && c.Name.EqualsIgnoreCase(ACCESS_COOKIE_NAME))) - { - cookie.Expired = true; - } - - AddTokenCookie(container, token); - } - /// /// Clear and set new session /// @@ -95,70 +82,22 @@ public static class AdmissionHelper return; } - AddRefreshToken(container, mobileSession.RefreshToken); + container.SetSteamRefreshToken(mobileSession.RefreshToken); var community = SteamDomains.GetDomainUri(SteamDomain.Community); container.Add(community, new Cookie("steamid", mobileSession.SteamId.Steam64.ToString())); container.Add(community, new Cookie(SESSION_ID_COOKIE_NAME, mobileSession.SessionId)); container.Add(community, new Cookie(LANGUAGE_COOKIE_NAME, setLanguage)); TransferCommunityCookies(container); - foreach (var domain in SteamDomains.AuthDomains) + foreach (var domain in SteamDomains.WebDomains) { var token = mobileSession.GetToken(domain); if (token == null) continue; - AddTokenCookie(container, token.Value); + var domainUri = SteamDomains.GetDomainUri(domain); + container.SetSteamAccessTokenUnsafe(token.Value, domainUri); } } - /// - /// Clear and set new session. Not recommended. Uses for domain - /// instead of its own cookie. It's okay to use it only for confirmations. But - /// Market, Trading and other pages won't be authorized - /// - public static void SetSteamMobileCookiesWithMobileToken(this CookieContainer container, - IMobileSessionData? mobileSession, - string setLanguage = "english") - { - container.ClearSteamCookies(setLanguage); - container.AddMinimalMobileCookies(); - - if (mobileSession == null) - { - TransferCommunityCookies(container); - return; - } - - - AddRefreshToken(container, mobileSession.RefreshToken); - - var community = SteamDomains.GetDomainUri(SteamDomain.Community); - container.Add(community, new Cookie("steamid", mobileSession.SteamId.Steam64.ToString())); - container.Add(community, new Cookie(SESSION_ID_COOKIE_NAME, mobileSession.SessionId)); - container.Add(community, new Cookie(LANGUAGE_COOKIE_NAME, setLanguage)); - TransferCommunityCookies(container); - - var domainCookieSet = false; - foreach (var domain in SteamDomains.AllDomains) - { - var token = mobileSession.GetToken(domain); - if (token == null || token.Value.IsExpired) continue; - if (domain == SteamDomain.Community) - domainCookieSet = true; - AddTokenCookie(container, token.Value); - } - - var mobileToken = mobileSession.GetMobileToken(); - if (!domainCookieSet && mobileToken is {IsExpired: false}) - { - var domain = SteamDomains.GetDomainUri(SteamDomain.Community); - container.Add(domain, new Cookie(ACCESS_COOKIE_NAME, mobileToken.Value.SignedToken) - { - HttpOnly = true, - Secure = true, - Expires = mobileToken.Value.Expires.ToLocalDateTime() - }); - } - } public static void AddMinimalMobileCookies(this CookieContainer container) { @@ -229,27 +168,93 @@ public static class AdmissionHelper } } - public static void AddRefreshToken(CookieContainer container, SteamAuthToken token) + /// + /// Sets a Steam refresh token as a cookie to the specified cookie container. + /// + /// + /// The added cookie will have its expiration set according to the token's expiration time. This + /// method is typically used to enable authenticated requests to Steam services that require a refresh + /// token. + /// + /// The cookie container to which the Steam refresh token cookie will be added. Cannot be null. + /// + /// The Steam authentication token to add as a refresh cookie. Must be of type Refresh or + /// MobileRefresh. + /// + /// Thrown if the token is not of type Refresh or MobileRefresh. + public static void SetSteamRefreshToken(this CookieContainer container, SteamAuthToken token) { if (token.Type is not (SteamAccessTokenType.Refresh or SteamAccessTokenType.MobileRefresh)) throw new ArgumentException( $"Token must be of type Refresh or MobileRefresh. Provided token has type: {token.Type}", nameof(token)); - var refreshToken = token.SignedToken; - container.Add(SteamLoginUri, new Cookie(REFRESH_COOKIE_NAME, refreshToken) + SetSteamRefreshTokenUnsafe(container, token, SteamLoginUri); + } + + /// + /// Sets a Steam refresh token as a cookie to the specified cookie container for the given domain. + /// + /// + /// This method does not perform validation on the input parameters. Callers must ensure that the + /// provided values are valid and appropriate for use. + /// + /// The cookie container to which the refresh token cookie will be added. Cannot be null. + /// + /// The Steam authentication token containing the signed token value and expiration information. Cannot + /// be null. + /// + /// The URI of the domain for which the refresh token cookie should be set. Cannot be null. + public static void SetSteamRefreshTokenUnsafe(CookieContainer container, SteamAuthToken token, Uri domainUri) + { + container.Add(domainUri, new Cookie(REFRESH_COOKIE_NAME, token.SignedToken) { Expires = token.Expires.ToLocalDateTime() }); } - public static void AddTokenCookie(CookieContainer container, SteamAuthToken token) + /// + /// Sets a Steam access token to the specified cookie container for use with Steam web requests. + /// + /// + /// This method is intended for standard web access tokens bound to a specific Steam web domain. + /// Mobile tokens are intentionally rejected, since their audiences are capability-based rather + /// than domain-based and may grant access to multiple web domains. + /// + /// The cookie container to which the Steam access token will be added. Cannot be null. + /// + /// The Steam access token to add. Must be of type AccessToken and associated with a valid Steam + /// domain. + /// + /// Thrown if the token is not of type AccessToken. + public static void SetSteamAccessToken(this CookieContainer container, SteamAuthToken token) { + if (token.Type == SteamAccessTokenType.Mobile) + throw new ArgumentException( + $"Mobile access tokens cannot be added using this method. Use SetSteamAccessTokenUnsafe instead. Provided token has type: {token.Type}", + nameof(token)); if (token.Type is not SteamAccessTokenType.AccessToken) throw new ArgumentException($"Token must be of type AccessToken. Provided token has type: {token.Type}", nameof(token)); - var domain = SteamDomains.GetDomainUri(token.Domain); - container.Add(domain, new Cookie(ACCESS_COOKIE_NAME, token.SignedToken) + var domainUri = SteamDomains.GetDomainUri(token.Domain); + container.SetSteamAccessTokenUnsafe(token, domainUri); + } + + + /// + /// Sets a Steam access token as a secure, HTTP-only cookie to the specified cookie container for the given domain. + /// + /// + /// This method does not perform validation on the input parameters and should only be used when + /// input values are trusted. The added cookie is marked as secure and HTTP-only, and its expiration is set + /// according to the token's expiration time. + /// + /// The cookie container to which the Steam access token cookie will be added. Cannot be null. + /// The Steam access token to add as a cookie. Must contain a valid signed token and expiration. + /// The URI of the domain for which the cookie will be set. Cannot be null. + public static void SetSteamAccessTokenUnsafe(this CookieContainer container, SteamAuthToken token, Uri domainUri) + { + container.Add(domainUri, new Cookie(ACCESS_COOKIE_NAME, token.SignedToken) { HttpOnly = true, Secure = true, @@ -257,7 +262,6 @@ public static class AdmissionHelper }); } - public static bool IsSteamCookie(Cookie cookie) { return cookie.Domain.Contains("steamcommunity.com") || cookie.Domain.Contains("steampowered.com") || diff --git a/src/SteamLibForked/Core/SteamDomains.cs b/src/SteamLibForked/Core/SteamDomains.cs index a74737c..5be37e7 100644 --- a/src/SteamLibForked/Core/SteamDomains.cs +++ b/src/SteamLibForked/Core/SteamDomains.cs @@ -1,49 +1,43 @@ -using System.Collections.ObjectModel; -using SteamLibForked.Models.Core; +using SteamLibForked.Models.Core; +using System.Collections.Immutable; namespace SteamLib.Core; public static class SteamDomains { public static IReadOnlyDictionary Domains { get; } = - new ReadOnlyDictionary( - new Dictionary - { - {SteamDomain.Community, SteamConstants.STEAM_COMMUNITY}, - {SteamDomain.Store, SteamConstants.STEAM_STORE}, - {SteamDomain.Help, SteamConstants.STEAM_HELP}, - {SteamDomain.TV, SteamConstants.STEAM_TV}, - {SteamDomain.Checkout, SteamConstants.STEAM_CHECKOUT}, - {SteamDomain.Login, SteamConstants.STEAM_LOGIN}, - {SteamDomain.API, SteamConstants.STEAM_API} - }); + new Dictionary + { + {SteamDomain.Community, SteamConstants.STEAM_COMMUNITY}, + {SteamDomain.Store, SteamConstants.STEAM_STORE}, + {SteamDomain.Help, SteamConstants.STEAM_HELP}, + {SteamDomain.TV, SteamConstants.STEAM_TV}, + {SteamDomain.Checkout, SteamConstants.STEAM_CHECKOUT}, + {SteamDomain.Login, SteamConstants.STEAM_LOGIN}, + {SteamDomain.API, SteamConstants.STEAM_API} + }.ToImmutableDictionary(); public static IReadOnlyDictionary DomainUris { get; } - = new ReadOnlyDictionary( - Domains.ToDictionary(x => x.Key, x => new Uri(x.Value)) - ); + = Domains + .ToDictionary(x => x.Key, x => new Uri(x.Value)) + .ToImmutableDictionary(); - public static IEnumerable AllDomains { get; } = - [ - SteamDomain.Community, - SteamDomain.Store, - SteamDomain.Help, - SteamDomain.TV, - SteamDomain.Checkout, - SteamDomain.Login, - SteamDomain.API - ]; - - public static IEnumerable AuthDomains { get; } = - [ - SteamDomain.Community, - SteamDomain.Store, - SteamDomain.Help, - SteamDomain.TV, - SteamDomain.Checkout - ]; + /// + /// All known public Steam domains. + /// + public static IEnumerable AllDomains { get; } = ImmutableHashSet.Create(SteamDomain.Community, + SteamDomain.Store, SteamDomain.Help, SteamDomain.TV, SteamDomain.Checkout, SteamDomain.Login, SteamDomain.API); + /// + /// Steam web domains that participate in the standard login authorization flow. + /// + /// These domains use the web:* audience format and receive + /// authentication cookies/tokens during session initialization. + /// + /// + public static IReadOnlySet WebDomains { get; } = ImmutableHashSet.Create(SteamDomain.Community, + SteamDomain.Store, SteamDomain.Help, SteamDomain.TV, SteamDomain.Checkout); public static Uri GetDomainUri(SteamDomain domain) { diff --git a/src/SteamLibForked/Models/Core/Session/MobileSessionData.cs b/src/SteamLibForked/Models/Core/Session/MobileSessionData.cs index 5c228fc..e02b88c 100644 --- a/src/SteamLibForked/Models/Core/Session/MobileSessionData.cs +++ b/src/SteamLibForked/Models/Core/Session/MobileSessionData.cs @@ -1,13 +1,14 @@ -using System.Diagnostics.CodeAnalysis; -using Newtonsoft.Json; +using Newtonsoft.Json; +using SteamLib.Core; using SteamLibForked.Abstractions; using SteamLibForked.Models.Core; +using System.Diagnostics.CodeAnalysis; namespace SteamLibForked.Models.Session; //WARNING: Any changes here should be reflected in MafileSerializer.cs -public sealed class MobileSessionData : SessionData, IMobileSessionData +public class MobileSessionData : SessionData, IMobileSessionData { public SteamAuthToken? MobileToken { get; private set; } @@ -31,26 +32,39 @@ public sealed class MobileSessionData : SessionData, IMobileSessionData return MobileToken; } + public override SteamAuthToken? GetToken(SteamDomain domain) + { + var isWeb = SteamDomains.WebDomains.Contains(domain); + if (isWeb) + { + // Mobile-issued tokens usually contain the "web" audience, + // so we assume they can also be used for all web:* domains. + // See: SteamAuthToken 'TODO' for more details + return MobileToken ?? base.GetToken(domain); + } + return base.GetToken(domain); + } + [MemberNotNull(nameof(MobileToken))] - public void SetMobileToken(SteamAuthToken token) + public virtual void SetMobileToken(SteamAuthToken token) { if (token.Type != SteamAccessTokenType.Mobile) throw new ArgumentException("Token must be of type MobileAccess", nameof(token)) { - Data = {{"ActualType", token.Type}} + Data = { { "ActualType", token.Type } } }; MobileToken = token; } - public override MobileSessionData Clone() - { - return (MobileSessionData) ((ISessionData) this).Clone(); - } - object ICloneable.Clone() { return new MobileSessionData(SessionId, SteamId, RefreshToken, MobileToken, Tokens); } + + public override MobileSessionData Clone() + { + return (MobileSessionData)((ISessionData)this).Clone(); + } } \ No newline at end of file diff --git a/src/SteamLibForked/Web/ClientBuilder.cs b/src/SteamLibForked/Web/ClientBuilder.cs index 9118d7a..28640fd 100644 --- a/src/SteamLibForked/Web/ClientBuilder.cs +++ b/src/SteamLibForked/Web/ClientBuilder.cs @@ -31,7 +31,7 @@ public static class ClientBuilder } else { - container.SetSteamMobileCookiesWithMobileToken(sessionData); + container.SetSteamMobileCookies(sessionData); } //Nebula tweak: