Merge pull request #401 from SteamRE/auth/accesstoken

Add support for new Steam authentication
pull/432/head
Pavel Djundik 3 years ago committed by GitHub
commit 9aaed770d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -17,8 +17,10 @@ namespace DepotDownloader
[ProtoMember(2, IsRequired = false)] [ProtoMember(2, IsRequired = false)]
public ConcurrentDictionary<string, int> ContentServerPenalty { get; private set; } public ConcurrentDictionary<string, int> ContentServerPenalty { get; private set; }
[ProtoMember(3, IsRequired = false)] // Member 3 was a Dictionary<string, string> for LoginKeys.
public Dictionary<string, string> LoginKeys { get; private set; }
[ProtoMember(4, IsRequired = false)]
public Dictionary<string, string> LoginTokens { get; private set; }
string FileName; string FileName;
@ -26,7 +28,7 @@ namespace DepotDownloader
{ {
SentryData = new Dictionary<string, byte[]>(); SentryData = new Dictionary<string, byte[]>();
ContentServerPenalty = new ConcurrentDictionary<string, int>(); ContentServerPenalty = new ConcurrentDictionary<string, int>();
LoginKeys = new Dictionary<string, string>(); LoginTokens = new Dictionary<string, string>();
} }
static bool Loaded static bool Loaded

@ -312,20 +312,20 @@ namespace DepotDownloader
public static bool InitializeSteam3(string username, string password) public static bool InitializeSteam3(string username, string password)
{ {
string loginKey = null; string loginToken = null;
if (username != null && Config.RememberPassword) if (username != null && Config.RememberPassword)
{ {
_ = AccountSettingsStore.Instance.LoginKeys.TryGetValue(username, out loginKey); _ = AccountSettingsStore.Instance.LoginTokens.TryGetValue(username, out loginToken);
} }
steam3 = new Steam3Session( steam3 = new Steam3Session(
new SteamUser.LogOnDetails new SteamUser.LogOnDetails
{ {
Username = username, Username = username,
Password = loginKey == null ? password : null, Password = loginToken == null ? password : null,
ShouldRememberPassword = Config.RememberPassword, ShouldRememberPassword = Config.RememberPassword,
LoginKey = loginKey, AccessToken = loginToken,
LoginID = Config.LoginID ?? 0x534B32, // "SK2" LoginID = Config.LoginID ?? 0x534B32, // "SK2"
} }
); );
@ -352,7 +352,6 @@ namespace DepotDownloader
if (steam3 == null) if (steam3 == null)
return; return;
steam3.TryWaitForLoginKey();
steam3.Disconnect(); steam3.Disconnect();
} }

@ -13,6 +13,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="protobuf-net" Version="3.2.16" /> <PackageReference Include="protobuf-net" Version="3.2.16" />
<PackageReference Include="SteamKit2" Version="2.4.1" /> <PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Include="SteamKit2" Version="2.5.0-Beta.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

@ -22,10 +22,11 @@ namespace DepotDownloader
public int MaxServers { get; set; } public int MaxServers { get; set; }
public int MaxDownloads { get; set; } public int MaxDownloads { get; set; }
public string SuppliedPassword { get; set; }
public bool RememberPassword { get; set; } public bool RememberPassword { get; set; }
// A Steam LoginID to allow multiple concurrent connections // A Steam LoginID to allow multiple concurrent connections
public uint? LoginID { get; set; } public uint? LoginID { get; set; }
public bool UseQrCode { get; set; }
} }
} }

@ -47,6 +47,7 @@ namespace DepotDownloader
var username = GetParameter<string>(args, "-username") ?? GetParameter<string>(args, "-user"); var username = GetParameter<string>(args, "-username") ?? GetParameter<string>(args, "-user");
var password = GetParameter<string>(args, "-password") ?? GetParameter<string>(args, "-pass"); var password = GetParameter<string>(args, "-password") ?? GetParameter<string>(args, "-pass");
ContentDownloader.Config.RememberPassword = HasParameter(args, "-remember-password"); ContentDownloader.Config.RememberPassword = HasParameter(args, "-remember-password");
ContentDownloader.Config.UseQrCode = HasParameter(args, "-qr");
ContentDownloader.Config.DownloadManifestOnly = HasParameter(args, "-manifest-only"); ContentDownloader.Config.DownloadManifestOnly = HasParameter(args, "-manifest-only");
@ -268,32 +269,32 @@ namespace DepotDownloader
static bool InitializeSteam(string username, string password) static bool InitializeSteam(string username, string password)
{ {
if (username != null && password == null && (!ContentDownloader.Config.RememberPassword || !AccountSettingsStore.Instance.LoginKeys.ContainsKey(username))) if (!ContentDownloader.Config.UseQrCode)
{ {
do if (username != null && password == null && (!ContentDownloader.Config.RememberPassword || !AccountSettingsStore.Instance.LoginTokens.ContainsKey(username)))
{ {
Console.Write("Enter account password for \"{0}\": ", username); do
if (Console.IsInputRedirected)
{ {
password = Console.ReadLine(); Console.Write("Enter account password for \"{0}\": ", username);
} if (Console.IsInputRedirected)
else {
{ password = Console.ReadLine();
// Avoid console echoing of password }
password = Util.ReadPassword(); else
} {
// Avoid console echoing of password
password = Util.ReadPassword();
}
Console.WriteLine(); Console.WriteLine();
} while (string.Empty == password); } while (string.Empty == password);
} }
else if (username == null) else if (username == null)
{ {
Console.WriteLine("No username given. Using anonymous account with dedicated server subscription."); Console.WriteLine("No username given. Using anonymous account with dedicated server subscription.");
}
} }
// capture the supplied password in case we need to re-use it after checking the login key
ContentDownloader.Config.SuppliedPassword = password;
return ContentDownloader.InitializeSteam3(username, password); return ContentDownloader.InitializeSteam3(username, password);
} }

@ -6,7 +6,9 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using QRCoder;
using SteamKit2; using SteamKit2;
using SteamKit2.Authentication;
using SteamKit2.Internal; using SteamKit2.Internal;
namespace DepotDownloader namespace DepotDownloader
@ -53,11 +55,11 @@ namespace DepotDownloader
bool bAborted; bool bAborted;
bool bExpectingDisconnectRemote; bool bExpectingDisconnectRemote;
bool bDidDisconnect; bool bDidDisconnect;
bool bDidReceiveLoginKey;
bool bIsConnectionRecovery; bool bIsConnectionRecovery;
int connectionBackoff; int connectionBackoff;
int seq; // more hack fixes int seq; // more hack fixes
DateTime connectTime; DateTime connectTime;
AuthSession authSession;
// input // input
readonly SteamUser.LogOnDetails logonDetails; readonly SteamUser.LogOnDetails logonDetails;
@ -72,14 +74,13 @@ namespace DepotDownloader
{ {
this.logonDetails = details; this.logonDetails = details;
this.authenticatedUser = details.Username != null; this.authenticatedUser = details.Username != null || ContentDownloader.Config.UseQrCode;
this.credentials = new Credentials(); this.credentials = new Credentials();
this.bConnected = false; this.bConnected = false;
this.bConnecting = false; this.bConnecting = false;
this.bAborted = false; this.bAborted = false;
this.bExpectingDisconnectRemote = false; this.bExpectingDisconnectRemote = false;
this.bDidDisconnect = false; this.bDidDisconnect = false;
this.bDidReceiveLoginKey = false;
this.seq = 0; this.seq = 0;
this.AppTokens = new Dictionary<uint, ulong>(); this.AppTokens = new Dictionary<uint, ulong>();
@ -112,11 +113,10 @@ namespace DepotDownloader
this.callbacks.Subscribe<SteamUser.SessionTokenCallback>(SessionTokenCallback); this.callbacks.Subscribe<SteamUser.SessionTokenCallback>(SessionTokenCallback);
this.callbacks.Subscribe<SteamApps.LicenseListCallback>(LicenseListCallback); this.callbacks.Subscribe<SteamApps.LicenseListCallback>(LicenseListCallback);
this.callbacks.Subscribe<SteamUser.UpdateMachineAuthCallback>(UpdateMachineAuthCallback); this.callbacks.Subscribe<SteamUser.UpdateMachineAuthCallback>(UpdateMachineAuthCallback);
this.callbacks.Subscribe<SteamUser.LoginKeyCallback>(LoginKeyCallback);
Console.Write("Connecting to Steam3..."); Console.Write("Connecting to Steam3...");
if (authenticatedUser) if (details.Username != null)
{ {
var fi = new FileInfo(String.Format("{0}.sentryFile", logonDetails.Username)); var fi = new FileInfo(String.Format("{0}.sentryFile", logonDetails.Username));
if (AccountSettingsStore.Instance.SentryData != null && AccountSettingsStore.Instance.SentryData.ContainsKey(logonDetails.Username)) if (AccountSettingsStore.Instance.SentryData != null && AccountSettingsStore.Instance.SentryData.ContainsKey(logonDetails.Username))
@ -419,7 +419,6 @@ namespace DepotDownloader
bExpectingDisconnectRemote = false; bExpectingDisconnectRemote = false;
bDidDisconnect = false; bDidDisconnect = false;
bIsConnectionRecovery = false; bIsConnectionRecovery = false;
bDidReceiveLoginKey = false;
} }
void Connect() void Connect()
@ -428,6 +427,7 @@ namespace DepotDownloader
bConnected = false; bConnected = false;
bConnecting = true; bConnecting = true;
connectionBackoff = 0; connectionBackoff = 0;
authSession = null;
ResetConnectionFlags(); ResetConnectionFlags();
@ -466,23 +466,6 @@ namespace DepotDownloader
steamClient.Disconnect(); steamClient.Disconnect();
} }
public void TryWaitForLoginKey()
{
if (logonDetails.Username == null || !credentials.LoggedOn || !ContentDownloader.Config.RememberPassword) return;
var totalWaitPeriod = DateTime.Now.AddSeconds(3);
while (true)
{
var now = DateTime.Now;
if (now >= totalWaitPeriod) break;
if (bDidReceiveLoginKey) break;
callbacks.RunWaitAllCallbacks(TimeSpan.FromMilliseconds(100));
}
}
private void WaitForCallbacks() private void WaitForCallbacks()
{ {
callbacks.RunWaitCallbacks(TimeSpan.FromSeconds(1)); callbacks.RunWaitCallbacks(TimeSpan.FromSeconds(1));
@ -496,11 +479,17 @@ namespace DepotDownloader
} }
} }
private void ConnectedCallback(SteamClient.ConnectedCallback connected) private async void ConnectedCallback(SteamClient.ConnectedCallback connected)
{ {
Console.WriteLine(" Done!"); Console.WriteLine(" Done!");
bConnecting = false; bConnecting = false;
bConnected = true; bConnected = true;
// Update our tracking so that we don't time out, even if we need to reconnect multiple times,
// e.g. if the authentication phase takes a while and therefore multiple connections.
connectTime = DateTime.Now;
connectionBackoff = 0;
if (!authenticatedUser) if (!authenticatedUser)
{ {
Console.Write("Logging anonymously into Steam3..."); Console.Write("Logging anonymously into Steam3...");
@ -508,7 +497,102 @@ namespace DepotDownloader
} }
else else
{ {
Console.Write("Logging '{0}' into Steam3...", logonDetails.Username); if (logonDetails.Username != null)
{
Console.WriteLine("Logging '{0}' into Steam3...", logonDetails.Username);
}
if (authSession is null)
{
if (logonDetails.Username != null && logonDetails.Password != null && logonDetails.AccessToken is null)
{
try
{
authSession = await steamClient.Authentication.BeginAuthSessionViaCredentialsAsync(new SteamKit2.Authentication.AuthSessionDetails
{
Username = logonDetails.Username,
Password = logonDetails.Password,
IsPersistentSession = ContentDownloader.Config.RememberPassword,
Authenticator = new UserConsoleAuthenticator(),
});
}
catch (TaskCanceledException)
{
return;
}
catch (Exception ex)
{
Console.Error.WriteLine("Failed to authenticate with Steam: " + ex.Message);
Abort(false);
return;
}
}
else if (logonDetails.AccessToken is null && ContentDownloader.Config.UseQrCode)
{
Console.WriteLine("Logging in with QR code...");
try
{
var session = await steamClient.Authentication.BeginAuthSessionViaQRAsync(new AuthSessionDetails
{
IsPersistentSession = ContentDownloader.Config.RememberPassword,
Authenticator = new UserConsoleAuthenticator(),
});
authSession = session;
// Steam will periodically refresh the challenge url, so we need a new QR code.
session.ChallengeURLChanged = () =>
{
Console.WriteLine();
Console.WriteLine("The QR code has changed:");
DisplayQrCode(session.ChallengeURL);
};
// Draw initial QR code immediately
DisplayQrCode(session.ChallengeURL);
}
catch (TaskCanceledException)
{
return;
}
catch (Exception ex)
{
Console.Error.WriteLine("Failed to authenticate with Steam: " + ex.Message);
Abort(false);
return;
}
}
}
if (authSession != null)
{
try
{
var result = await authSession.PollingWaitForResultAsync();
logonDetails.Username = result.AccountName;
logonDetails.Password = null;
logonDetails.AccessToken = result.RefreshToken;
AccountSettingsStore.Instance.LoginTokens[result.AccountName] = result.RefreshToken;
AccountSettingsStore.Save();
}
catch (TaskCanceledException)
{
return;
}
catch (Exception ex)
{
Console.Error.WriteLine("Failed to authenticate with Steam: " + ex.Message);
Abort(false);
return;
}
authSession = null;
}
steamUser.LogOn(logonDetails); steamUser.LogOn(logonDetails);
} }
} }
@ -517,6 +601,8 @@ namespace DepotDownloader
{ {
bDidDisconnect = true; bDidDisconnect = true;
DebugLog.WriteLine(nameof(Steam3Session), $"Disconnected: bIsConnectionRecovery = {bIsConnectionRecovery}, UserInitiated = {disconnected.UserInitiated}, bExpectingDisconnectRemote = {bExpectingDisconnectRemote}");
// When recovering the connection, we want to reconnect even if the remote disconnects us // When recovering the connection, we want to reconnect even if the remote disconnects us
if (!bIsConnectionRecovery && (disconnected.UserInitiated || bExpectingDisconnectRemote)) if (!bIsConnectionRecovery && (disconnected.UserInitiated || bExpectingDisconnectRemote))
{ {
@ -553,14 +639,14 @@ namespace DepotDownloader
{ {
var isSteamGuard = loggedOn.Result == EResult.AccountLogonDenied; var isSteamGuard = loggedOn.Result == EResult.AccountLogonDenied;
var is2FA = loggedOn.Result == EResult.AccountLoginDeniedNeedTwoFactor; var is2FA = loggedOn.Result == EResult.AccountLoginDeniedNeedTwoFactor;
var isLoginKey = ContentDownloader.Config.RememberPassword && logonDetails.LoginKey != null && loggedOn.Result == EResult.InvalidPassword; var isAccessToken = ContentDownloader.Config.RememberPassword && logonDetails.AccessToken != null && loggedOn.Result == EResult.InvalidPassword; // TODO: Get EResult for bad access token
if (isSteamGuard || is2FA || isLoginKey) if (isSteamGuard || is2FA || isAccessToken)
{ {
bExpectingDisconnectRemote = true; bExpectingDisconnectRemote = true;
Abort(false); Abort(false);
if (!isLoginKey) if (!isAccessToken)
{ {
Console.WriteLine("This account is protected by Steam Guard."); Console.WriteLine("This account is protected by Steam Guard.");
} }
@ -573,23 +659,15 @@ namespace DepotDownloader
logonDetails.TwoFactorCode = Console.ReadLine(); logonDetails.TwoFactorCode = Console.ReadLine();
} while (String.Empty == logonDetails.TwoFactorCode); } while (String.Empty == logonDetails.TwoFactorCode);
} }
else if (isLoginKey) else if (isAccessToken)
{ {
AccountSettingsStore.Instance.LoginKeys.Remove(logonDetails.Username); AccountSettingsStore.Instance.LoginTokens.Remove(logonDetails.Username);
AccountSettingsStore.Save(); AccountSettingsStore.Save();
logonDetails.LoginKey = null; // TODO: Handle gracefully by falling back to password prompt?
Console.WriteLine("Access token was rejected.");
if (ContentDownloader.Config.SuppliedPassword != null) Abort(false);
{ return;
Console.WriteLine("Login key was expired. Connecting with supplied password.");
logonDetails.Password = ContentDownloader.Config.SuppliedPassword;
}
else
{
Console.Write("Login key was expired. Please enter your password: ");
logonDetails.Password = Util.ReadPassword();
}
} }
else else
{ {
@ -700,16 +778,16 @@ namespace DepotDownloader
steamUser.SendMachineAuthResponse(authResponse); steamUser.SendMachineAuthResponse(authResponse);
} }
private void LoginKeyCallback(SteamUser.LoginKeyCallback loginKey) private static void DisplayQrCode(string challengeUrl)
{ {
Console.WriteLine("Accepted new login key for account {0}", logonDetails.Username); // Encode the link as a QR code
using var qrGenerator = new QRCodeGenerator();
AccountSettingsStore.Instance.LoginKeys[logonDetails.Username] = loginKey.LoginKey; var qrCodeData = qrGenerator.CreateQrCode(challengeUrl, QRCodeGenerator.ECCLevel.L);
AccountSettingsStore.Save(); using var qrCode = new AsciiQRCode(qrCodeData);
var qrCodeAsAsciiArt = qrCode.GetGraphic(1, drawQuietZones: false);
steamUser.AcceptNewLoginKey(loginKey);
Console.WriteLine("Use the Steam Mobile App to sign in with this QR code:");
bDidReceiveLoginKey = true; Console.WriteLine(qrCodeAsAsciiArt);
} }
} }
} }

Loading…
Cancel
Save