mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-23 09:48:37 +00:00
Squashed commit of ASF 2FA
Check https://github.com/JustArchi/ArchiSteamFarm/wiki/Escrow for more info
This commit is contained in:
@@ -5,6 +5,8 @@ VisualStudioVersion = 14.0.23107.0
|
|||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArchiSteamFarm", "ArchiSteamFarm\ArchiSteamFarm.csproj", "{35AF7887-08B9-40E8-A5EA-797D8B60B30C}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArchiSteamFarm", "ArchiSteamFarm\ArchiSteamFarm.csproj", "{35AF7887-08B9-40E8-A5EA-797D8B60B30C}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamAuth", "SteamAuth\SteamAuth.csproj", "{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -15,6 +17,10 @@ Global
|
|||||||
{35AF7887-08B9-40E8-A5EA-797D8B60B30C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{35AF7887-08B9-40E8-A5EA-797D8B60B30C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{35AF7887-08B9-40E8-A5EA-797D8B60B30C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{35AF7887-08B9-40E8-A5EA-797D8B60B30C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{35AF7887-08B9-40E8-A5EA-797D8B60B30C}.Release|Any CPU.Build.0 = Release|Any CPU
|
{35AF7887-08B9-40E8-A5EA-797D8B60B30C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<startup>
|
<startup>
|
||||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/>
|
||||||
</startup>
|
</startup>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
<RootNamespace>ArchiSteamFarm</RootNamespace>
|
<RootNamespace>ArchiSteamFarm</RootNamespace>
|
||||||
<AssemblyName>ArchiSteamFarm</AssemblyName>
|
<AssemblyName>ArchiSteamFarm</AssemblyName>
|
||||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||||
<FileAlignment>512</FileAlignment>
|
<FileAlignment>512</FileAlignment>
|
||||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||||
<PublishUrl>publish\</PublishUrl>
|
<PublishUrl>publish\</PublishUrl>
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
|
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
|
||||||
<UseApplicationTrust>false</UseApplicationTrust>
|
<UseApplicationTrust>false</UseApplicationTrust>
|
||||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||||
|
<TargetFrameworkProfile />
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
@@ -123,6 +124,12 @@
|
|||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\SteamAuth\SteamAuth.csproj">
|
||||||
|
<Project>{5ad0934e-f6c4-4ae5-83af-c788313b2a87}</Project>
|
||||||
|
<Name>SteamAuth</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PreBuildEvent>
|
<PreBuildEvent>
|
||||||
|
|||||||
@@ -192,7 +192,9 @@ namespace ArchiSteamFarm {
|
|||||||
is_our_offer = trade["is_our_offer"].AsBoolean(),
|
is_our_offer = trade["is_our_offer"].AsBoolean(),
|
||||||
time_created = trade["time_created"].AsInteger(),
|
time_created = trade["time_created"].AsInteger(),
|
||||||
time_updated = trade["time_updated"].AsInteger(),
|
time_updated = trade["time_updated"].AsInteger(),
|
||||||
from_real_time_trade = trade["from_real_time_trade"].AsBoolean()
|
from_real_time_trade = trade["from_real_time_trade"].AsBoolean(),
|
||||||
|
escrow_end_date = trade["escrow_end_date"].AsInteger(),
|
||||||
|
confirmation_method = (SteamTradeOffer.ETradeOfferConfirmationMethod) trade["confirmation_method"].AsInteger()
|
||||||
};
|
};
|
||||||
foreach (KeyValue item in trade["items_to_give"].Children) {
|
foreach (KeyValue item in trade["items_to_give"].Children) {
|
||||||
tradeOffer.items_to_give.Add(new SteamItem {
|
tradeOffer.items_to_give.Add(new SteamItem {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using SteamAuth;
|
||||||
using SteamKit2;
|
using SteamKit2;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
@@ -37,13 +39,13 @@ namespace ArchiSteamFarm {
|
|||||||
|
|
||||||
private static readonly ConcurrentDictionary<string, Bot> Bots = new ConcurrentDictionary<string, Bot>();
|
private static readonly ConcurrentDictionary<string, Bot> Bots = new ConcurrentDictionary<string, Bot>();
|
||||||
|
|
||||||
private readonly string ConfigFile, SentryFile;
|
private readonly string ConfigFile, LoginKeyFile, MobileAuthenticatorFile, SentryFile;
|
||||||
|
|
||||||
internal readonly string BotName;
|
internal readonly string BotName;
|
||||||
|
|
||||||
private bool LoggedInElsewhere = false;
|
private bool LoggedInElsewhere = false;
|
||||||
private bool IsRunning = false;
|
private bool IsRunning = false;
|
||||||
private string AuthCode, TwoFactorAuth;
|
private string AuthCode, LoginKey, TwoFactorAuth;
|
||||||
|
|
||||||
internal ArchiHandler ArchiHandler { get; private set; }
|
internal ArchiHandler ArchiHandler { get; private set; }
|
||||||
internal ArchiWebHandler ArchiWebHandler { get; private set; }
|
internal ArchiWebHandler ArchiWebHandler { get; private set; }
|
||||||
@@ -51,6 +53,7 @@ namespace ArchiSteamFarm {
|
|||||||
internal CardsFarmer CardsFarmer { get; private set; }
|
internal CardsFarmer CardsFarmer { get; private set; }
|
||||||
internal SteamClient SteamClient { get; private set; }
|
internal SteamClient SteamClient { get; private set; }
|
||||||
internal SteamFriends SteamFriends { get; private set; }
|
internal SteamFriends SteamFriends { get; private set; }
|
||||||
|
internal SteamGuardAccount SteamGuardAccount { get; private set; }
|
||||||
internal SteamUser SteamUser { get; private set; }
|
internal SteamUser SteamUser { get; private set; }
|
||||||
internal Trading Trading { get; private set; }
|
internal Trading Trading { get; private set; }
|
||||||
|
|
||||||
@@ -64,6 +67,7 @@ namespace ArchiSteamFarm {
|
|||||||
internal ulong SteamMasterID { get; private set; } = 0;
|
internal ulong SteamMasterID { get; private set; } = 0;
|
||||||
internal ulong SteamMasterClanID { get; private set; } = 0;
|
internal ulong SteamMasterClanID { get; private set; } = 0;
|
||||||
internal bool CardDropsRestricted { get; private set; } = false;
|
internal bool CardDropsRestricted { get; private set; } = false;
|
||||||
|
internal bool UseAsfAsMobileAuthenticator { get; private set; } = false;
|
||||||
internal bool ShutdownOnFarmingFinished { get; private set; } = false;
|
internal bool ShutdownOnFarmingFinished { get; private set; } = false;
|
||||||
internal HashSet<uint> Blacklist { get; private set; } = new HashSet<uint> { 303700, 335590, 368020 };
|
internal HashSet<uint> Blacklist { get; private set; } = new HashSet<uint> { 303700, 335590, 368020 };
|
||||||
internal bool Statistics { get; private set; } = true;
|
internal bool Statistics { get; private set; } = true;
|
||||||
@@ -106,6 +110,8 @@ namespace ArchiSteamFarm {
|
|||||||
BotName = botName;
|
BotName = botName;
|
||||||
|
|
||||||
ConfigFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".xml");
|
ConfigFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".xml");
|
||||||
|
LoginKeyFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".key");
|
||||||
|
MobileAuthenticatorFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".auth");
|
||||||
SentryFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".bin");
|
SentryFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".bin");
|
||||||
|
|
||||||
if (!ReadConfig()) {
|
if (!ReadConfig()) {
|
||||||
@@ -132,10 +138,15 @@ namespace ArchiSteamFarm {
|
|||||||
CallbackManager.Subscribe<SteamFriends.FriendsListCallback>(OnFriendsList);
|
CallbackManager.Subscribe<SteamFriends.FriendsListCallback>(OnFriendsList);
|
||||||
CallbackManager.Subscribe<SteamFriends.FriendMsgCallback>(OnFriendMsg);
|
CallbackManager.Subscribe<SteamFriends.FriendMsgCallback>(OnFriendMsg);
|
||||||
|
|
||||||
|
if (UseAsfAsMobileAuthenticator && File.Exists(MobileAuthenticatorFile)) {
|
||||||
|
SteamGuardAccount = JsonConvert.DeserializeObject<SteamGuardAccount>(File.ReadAllText(MobileAuthenticatorFile));
|
||||||
|
}
|
||||||
|
|
||||||
SteamUser = SteamClient.GetHandler<SteamUser>();
|
SteamUser = SteamClient.GetHandler<SteamUser>();
|
||||||
CallbackManager.Subscribe<SteamUser.AccountInfoCallback>(OnAccountInfo);
|
CallbackManager.Subscribe<SteamUser.AccountInfoCallback>(OnAccountInfo);
|
||||||
CallbackManager.Subscribe<SteamUser.LoggedOffCallback>(OnLoggedOff);
|
CallbackManager.Subscribe<SteamUser.LoggedOffCallback>(OnLoggedOff);
|
||||||
CallbackManager.Subscribe<SteamUser.LoggedOnCallback>(OnLoggedOn);
|
CallbackManager.Subscribe<SteamUser.LoggedOnCallback>(OnLoggedOn);
|
||||||
|
CallbackManager.Subscribe<SteamUser.LoginKeyCallback>(OnLoginKey);
|
||||||
CallbackManager.Subscribe<SteamUser.UpdateMachineAuthCallback>(OnMachineAuth);
|
CallbackManager.Subscribe<SteamUser.UpdateMachineAuthCallback>(OnMachineAuth);
|
||||||
|
|
||||||
CallbackManager.Subscribe<ArchiHandler.NotificationCallback>(OnNotification);
|
CallbackManager.Subscribe<ArchiHandler.NotificationCallback>(OnNotification);
|
||||||
@@ -149,6 +160,90 @@ namespace ArchiSteamFarm {
|
|||||||
var fireAndForget = Task.Run(async () => await Start().ConfigureAwait(false));
|
var fireAndForget = Task.Run(async () => await Start().ConfigureAwait(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void AcceptAllConfirmations() {
|
||||||
|
if (SteamGuardAccount == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Confirmation confirmation in SteamGuardAccount.FetchConfirmations()) {
|
||||||
|
if (SteamGuardAccount.AcceptConfirmation(confirmation)) {
|
||||||
|
Logging.LogGenericInfo(BotName, "Accepting confirmation: Success!");
|
||||||
|
} else {
|
||||||
|
Logging.LogGenericWarning(BotName, "Accepting confirmation: Failed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool LinkMobileAuthenticator() {
|
||||||
|
if (SteamGuardAccount != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logging.LogGenericNotice(BotName, "Linking new ASF MobileAuthenticator...");
|
||||||
|
UserLogin userLogin = new UserLogin(SteamLogin, SteamPassword);
|
||||||
|
LoginResult loginResult;
|
||||||
|
while ((loginResult = userLogin.DoLogin()) != LoginResult.LoginOkay) {
|
||||||
|
switch (loginResult) {
|
||||||
|
case LoginResult.NeedEmail:
|
||||||
|
userLogin.EmailCode = Program.GetUserInput(BotName, Program.EUserInputType.SteamGuard);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Logging.LogGenericError(BotName, "Unhandled situation: " + loginResult);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticatorLinker authenticatorLinker = new AuthenticatorLinker(userLogin.Session);
|
||||||
|
|
||||||
|
AuthenticatorLinker.LinkResult linkResult = authenticatorLinker.AddAuthenticator();
|
||||||
|
switch (linkResult) {
|
||||||
|
case AuthenticatorLinker.LinkResult.AwaitingFinalization:
|
||||||
|
Logging.LogGenericInfo(BotName, "OK: " + linkResult);
|
||||||
|
break;
|
||||||
|
case AuthenticatorLinker.LinkResult.MustProvidePhoneNumber:
|
||||||
|
while (linkResult == AuthenticatorLinker.LinkResult.MustProvidePhoneNumber) {
|
||||||
|
authenticatorLinker.PhoneNumber = Program.GetUserInput(BotName, Program.EUserInputType.PhoneNumber);
|
||||||
|
linkResult = authenticatorLinker.AddAuthenticator();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Logging.LogGenericError(BotName, "Unhandled situation: " + linkResult);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SteamGuardAccount = authenticatorLinker.LinkedAccount;
|
||||||
|
|
||||||
|
try {
|
||||||
|
File.WriteAllText(MobileAuthenticatorFile, JsonConvert.SerializeObject(SteamGuardAccount));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logging.LogGenericException(BotName, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticatorLinker.FinalizeResult finalizeResult = authenticatorLinker.FinalizeAddAuthenticator(Program.GetUserInput(BotName, Program.EUserInputType.SMS));
|
||||||
|
if (finalizeResult != AuthenticatorLinker.FinalizeResult.Success) {
|
||||||
|
Logging.LogGenericError(BotName, "Unhandled situation: " + finalizeResult);
|
||||||
|
DelinkMobileAuthenticator();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logging.LogGenericInfo(BotName, "Successfully linked ASF as new mobile authenticator for this account!");
|
||||||
|
Program.GetUserInput(BotName, Program.EUserInputType.RevocationCode, SteamGuardAccount.RevocationCode);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DelinkMobileAuthenticator() {
|
||||||
|
if (SteamGuardAccount == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool result = SteamGuardAccount.DeactivateAuthenticator();
|
||||||
|
SteamGuardAccount = null;
|
||||||
|
File.Delete(MobileAuthenticatorFile);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private bool ReadConfig() {
|
private bool ReadConfig() {
|
||||||
if (!File.Exists(ConfigFile)) {
|
if (!File.Exists(ConfigFile)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -196,6 +291,9 @@ namespace ArchiSteamFarm {
|
|||||||
case "SteamMasterClanID":
|
case "SteamMasterClanID":
|
||||||
SteamMasterClanID = ulong.Parse(value);
|
SteamMasterClanID = ulong.Parse(value);
|
||||||
break;
|
break;
|
||||||
|
case "UseAsfAsMobileAuthenticator":
|
||||||
|
UseAsfAsMobileAuthenticator = bool.Parse(value);
|
||||||
|
break;
|
||||||
case "CardDropsRestricted":
|
case "CardDropsRestricted":
|
||||||
CardDropsRestricted = bool.Parse(value);
|
CardDropsRestricted = bool.Parse(value);
|
||||||
break;
|
break;
|
||||||
@@ -316,6 +414,59 @@ namespace ArchiSteamFarm {
|
|||||||
SendMessageToUser(steamID, "Currently " + Bots.Count + " bots are running");
|
SendMessageToUser(steamID, "Currently " + Bots.Count + " bots are running");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Response2FA(ulong steamID, string botName = null) {
|
||||||
|
if (steamID == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bot bot;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(botName)) {
|
||||||
|
bot = this;
|
||||||
|
} else {
|
||||||
|
if (!Bots.TryGetValue(botName, out bot)) {
|
||||||
|
SendMessageToUser(steamID, "Couldn't find any bot named " + botName + "!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bot.SteamGuardAccount == null) {
|
||||||
|
SendMessageToUser(steamID, "That bot doesn't have ASF 2FA enabled!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long timeLeft = 30 - TimeAligner.GetSteamTime() % 30;
|
||||||
|
SendMessageToUser(steamID, "2FA Token: " + bot.SteamGuardAccount.GenerateSteamGuardCode() + " (expires in " + timeLeft + " seconds)");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Response2FAOff(ulong steamID, string botName = null) {
|
||||||
|
if (steamID == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bot bot;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(botName)) {
|
||||||
|
bot = this;
|
||||||
|
} else {
|
||||||
|
if (!Bots.TryGetValue(botName, out bot)) {
|
||||||
|
SendMessageToUser(steamID, "Couldn't find any bot named " + botName + "!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bot.SteamGuardAccount == null) {
|
||||||
|
SendMessageToUser(steamID, "That bot doesn't have ASF 2FA enabled!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bot.DelinkMobileAuthenticator()) {
|
||||||
|
SendMessageToUser(steamID, "Done! Bot is no longer using ASF 2FA");
|
||||||
|
} else {
|
||||||
|
SendMessageToUser(steamID, "Something went wrong!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ResponseStart(ulong steamID, string botNameToStart) {
|
private void ResponseStart(ulong steamID, string botNameToStart) {
|
||||||
if (steamID == 0 || string.IsNullOrEmpty(botNameToStart)) {
|
if (steamID == 0 || string.IsNullOrEmpty(botNameToStart)) {
|
||||||
return;
|
return;
|
||||||
@@ -367,6 +518,10 @@ namespace ArchiSteamFarm {
|
|||||||
|
|
||||||
Logging.LogGenericInfo(BotName, "Connected to Steam!");
|
Logging.LogGenericInfo(BotName, "Connected to Steam!");
|
||||||
|
|
||||||
|
if (File.Exists(LoginKeyFile)) {
|
||||||
|
LoginKey = File.ReadAllText(LoginKeyFile);
|
||||||
|
}
|
||||||
|
|
||||||
byte[] sentryHash = null;
|
byte[] sentryHash = null;
|
||||||
if (File.Exists(SentryFile)) {
|
if (File.Exists(SentryFile)) {
|
||||||
byte[] sentryFileContent = File.ReadAllBytes(SentryFile);
|
byte[] sentryFileContent = File.ReadAllBytes(SentryFile);
|
||||||
@@ -377,7 +532,7 @@ namespace ArchiSteamFarm {
|
|||||||
SteamLogin = Program.GetUserInput(BotName, Program.EUserInputType.Login);
|
SteamLogin = Program.GetUserInput(BotName, Program.EUserInputType.Login);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SteamPassword.Equals("null")) {
|
if (SteamPassword.Equals("null") && string.IsNullOrEmpty(LoginKey)) {
|
||||||
SteamPassword = Program.GetUserInput(BotName, Program.EUserInputType.Password);
|
SteamPassword = Program.GetUserInput(BotName, Program.EUserInputType.Password);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,8 +540,10 @@ namespace ArchiSteamFarm {
|
|||||||
Username = SteamLogin,
|
Username = SteamLogin,
|
||||||
Password = SteamPassword,
|
Password = SteamPassword,
|
||||||
AuthCode = AuthCode,
|
AuthCode = AuthCode,
|
||||||
|
LoginKey = LoginKey,
|
||||||
TwoFactorCode = TwoFactorAuth,
|
TwoFactorCode = TwoFactorAuth,
|
||||||
SentryFileHash = sentryHash
|
SentryFileHash = sentryHash,
|
||||||
|
ShouldRememberPassword = true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,6 +634,12 @@ namespace ArchiSteamFarm {
|
|||||||
|
|
||||||
if (!message.Contains(" ")) {
|
if (!message.Contains(" ")) {
|
||||||
switch (message) {
|
switch (message) {
|
||||||
|
case "!2fa":
|
||||||
|
Response2FA(steamID);
|
||||||
|
break;
|
||||||
|
case "!2faoff":
|
||||||
|
Response2FAOff(steamID);
|
||||||
|
break;
|
||||||
case "!exit":
|
case "!exit":
|
||||||
await ShutdownAllBots().ConfigureAwait(false);
|
await ShutdownAllBots().ConfigureAwait(false);
|
||||||
break;
|
break;
|
||||||
@@ -498,6 +661,12 @@ namespace ArchiSteamFarm {
|
|||||||
} else {
|
} else {
|
||||||
string[] args = message.Split(' ');
|
string[] args = message.Split(' ');
|
||||||
switch (args[0]) {
|
switch (args[0]) {
|
||||||
|
case "!2fa":
|
||||||
|
Response2FA(steamID, args[1]);
|
||||||
|
break;
|
||||||
|
case "!2faoff":
|
||||||
|
Response2FAOff(steamID, args[1]);
|
||||||
|
break;
|
||||||
case "!redeem":
|
case "!redeem":
|
||||||
ArchiHandler.RedeemKey(args[1]);
|
ArchiHandler.RedeemKey(args[1]);
|
||||||
break;
|
break;
|
||||||
@@ -549,7 +718,11 @@ namespace ArchiSteamFarm {
|
|||||||
AuthCode = Program.GetUserInput(SteamLogin, Program.EUserInputType.SteamGuard);
|
AuthCode = Program.GetUserInput(SteamLogin, Program.EUserInputType.SteamGuard);
|
||||||
break;
|
break;
|
||||||
case EResult.AccountLoginDeniedNeedTwoFactor:
|
case EResult.AccountLoginDeniedNeedTwoFactor:
|
||||||
TwoFactorAuth = Program.GetUserInput(SteamLogin, Program.EUserInputType.TwoFactorAuthentication);
|
if (SteamGuardAccount == null) {
|
||||||
|
TwoFactorAuth = Program.GetUserInput(SteamLogin, Program.EUserInputType.TwoFactorAuthentication);
|
||||||
|
} else {
|
||||||
|
TwoFactorAuth = SteamGuardAccount.GenerateSteamGuardCode();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case EResult.InvalidPassword:
|
case EResult.InvalidPassword:
|
||||||
Logging.LogGenericWarning(BotName, "Unable to login to Steam: " + result + ", will retry after a longer while");
|
Logging.LogGenericWarning(BotName, "Unable to login to Steam: " + result + ", will retry after a longer while");
|
||||||
@@ -560,6 +733,10 @@ namespace ArchiSteamFarm {
|
|||||||
case EResult.OK:
|
case EResult.OK:
|
||||||
Logging.LogGenericInfo(BotName, "Successfully logged on!");
|
Logging.LogGenericInfo(BotName, "Successfully logged on!");
|
||||||
|
|
||||||
|
if (UseAsfAsMobileAuthenticator && TwoFactorAuth == null && SteamGuardAccount == null) {
|
||||||
|
LinkMobileAuthenticator();
|
||||||
|
}
|
||||||
|
|
||||||
// Reset one-time-only access tokens
|
// Reset one-time-only access tokens
|
||||||
AuthCode = null;
|
AuthCode = null;
|
||||||
TwoFactorAuth = null;
|
TwoFactorAuth = null;
|
||||||
@@ -600,6 +777,15 @@ namespace ArchiSteamFarm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnLoginKey(SteamUser.LoginKeyCallback callback) {
|
||||||
|
if (callback == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllText(LoginKeyFile, callback.LoginKey);
|
||||||
|
SteamUser.AcceptNewLoginKey(callback);
|
||||||
|
}
|
||||||
|
|
||||||
private void OnMachineAuth(SteamUser.UpdateMachineAuthCallback callback) {
|
private void OnMachineAuth(SteamUser.UpdateMachineAuthCallback callback) {
|
||||||
if (callback == null) {
|
if (callback == null) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -34,8 +34,11 @@ namespace ArchiSteamFarm {
|
|||||||
internal enum EUserInputType {
|
internal enum EUserInputType {
|
||||||
Login,
|
Login,
|
||||||
Password,
|
Password,
|
||||||
|
PhoneNumber,
|
||||||
|
SMS,
|
||||||
SteamGuard,
|
SteamGuard,
|
||||||
SteamParentalPIN,
|
SteamParentalPIN,
|
||||||
|
RevocationCode,
|
||||||
TwoFactorAuthentication,
|
TwoFactorAuthentication,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +101,7 @@ namespace ArchiSteamFarm {
|
|||||||
SteamSemaphore.Release();
|
SteamSemaphore.Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string GetUserInput(string botLogin, EUserInputType userInputType) {
|
internal static string GetUserInput(string botLogin, EUserInputType userInputType, string extraInformation = null) {
|
||||||
string result;
|
string result;
|
||||||
lock (ConsoleLock) {
|
lock (ConsoleLock) {
|
||||||
switch (userInputType) {
|
switch (userInputType) {
|
||||||
@@ -108,12 +111,23 @@ namespace ArchiSteamFarm {
|
|||||||
case EUserInputType.Password:
|
case EUserInputType.Password:
|
||||||
Console.Write("<" + botLogin + "> Please enter your password: ");
|
Console.Write("<" + botLogin + "> Please enter your password: ");
|
||||||
break;
|
break;
|
||||||
|
case EUserInputType.PhoneNumber:
|
||||||
|
Console.Write("<" + botLogin + "> Please enter your full phone number (e.g. +1234567890): ");
|
||||||
|
break;
|
||||||
|
case EUserInputType.SMS:
|
||||||
|
Console.Write("<" + botLogin + "> Please enter SMS code sent on your mobile: ");
|
||||||
|
break;
|
||||||
case EUserInputType.SteamGuard:
|
case EUserInputType.SteamGuard:
|
||||||
Console.Write("<" + botLogin + "> Please enter the auth code sent to your email: ");
|
Console.Write("<" + botLogin + "> Please enter the auth code sent to your email: ");
|
||||||
break;
|
break;
|
||||||
case EUserInputType.SteamParentalPIN:
|
case EUserInputType.SteamParentalPIN:
|
||||||
Console.Write("<" + botLogin + "> Please enter steam parental PIN: ");
|
Console.Write("<" + botLogin + "> Please enter steam parental PIN: ");
|
||||||
break;
|
break;
|
||||||
|
case EUserInputType.RevocationCode:
|
||||||
|
Console.WriteLine("<" + botLogin + "> PLEASE WRITE DOWN YOUR REVOCATION CODE: " + extraInformation);
|
||||||
|
Console.WriteLine("<" + botLogin + "> THIS IS THE ONLY WAY TO NOT GET LOCKED OUT OF YOUR ACCOUNT!");
|
||||||
|
Console.Write("<" + botLogin + "> Hit enter once ready...");
|
||||||
|
break;
|
||||||
case EUserInputType.TwoFactorAuthentication:
|
case EUserInputType.TwoFactorAuthentication:
|
||||||
Console.Write("<" + botLogin + "> Please enter your 2 factor auth code from your authenticator app: ");
|
Console.Write("<" + botLogin + "> Please enter your 2 factor auth code from your authenticator app: ");
|
||||||
break;
|
break;
|
||||||
@@ -121,8 +135,7 @@ namespace ArchiSteamFarm {
|
|||||||
result = Console.ReadLine();
|
result = Console.ReadLine();
|
||||||
Console.Clear(); // For security purposes
|
Console.Clear(); // For security purposes
|
||||||
}
|
}
|
||||||
result = result.Trim(); // Get rid of all whitespace characters
|
return result.Trim(); // Get rid of all whitespace characters
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static async void OnBotShutdown(Bot bot) {
|
internal static async void OnBotShutdown(Bot bot) {
|
||||||
|
|||||||
@@ -39,7 +39,14 @@ namespace ArchiSteamFarm {
|
|||||||
Declined,
|
Declined,
|
||||||
InvalidItems,
|
InvalidItems,
|
||||||
EmailPending,
|
EmailPending,
|
||||||
EmailCanceled
|
EmailCanceled,
|
||||||
|
OnHold
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum ETradeOfferConfirmationMethod {
|
||||||
|
Invalid,
|
||||||
|
Email,
|
||||||
|
MobileApp
|
||||||
}
|
}
|
||||||
|
|
||||||
internal string tradeofferid { get; set; }
|
internal string tradeofferid { get; set; }
|
||||||
@@ -53,6 +60,8 @@ namespace ArchiSteamFarm {
|
|||||||
internal int time_created { get; set; }
|
internal int time_created { get; set; }
|
||||||
internal int time_updated { get; set; }
|
internal int time_updated { get; set; }
|
||||||
internal bool from_real_time_trade { get; set; }
|
internal bool from_real_time_trade { get; set; }
|
||||||
|
internal int escrow_end_date { get; set; }
|
||||||
|
internal ETradeOfferConfirmationMethod confirmation_method { get; set; }
|
||||||
|
|
||||||
// Extra
|
// Extra
|
||||||
private ulong _OtherSteamID64 = 0;
|
private ulong _OtherSteamID64 = 0;
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ namespace ArchiSteamFarm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||||
|
Bot.AcceptAllConfirmations();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ParseTrade(SteamTradeOffer tradeOffer) {
|
private async Task ParseTrade(SteamTradeOffer tradeOffer) {
|
||||||
|
|||||||
@@ -49,6 +49,13 @@
|
|||||||
<!-- TIP: If you don't have your own farming group, most likely you don't want to change it, 0 means there is no master group defined -->
|
<!-- TIP: If you don't have your own farming group, most likely you don't want to change it, 0 means there is no master group defined -->
|
||||||
<SteamMasterClanID type="ulong" value="0"/>
|
<SteamMasterClanID type="ulong" value="0"/>
|
||||||
|
|
||||||
|
<!-- This switch defines if you want to use built-in ASF two-factor-authentication (ASF 2FA) for this account -->
|
||||||
|
<!-- The one and only purpose for this function is automating steam logins and steam trades, so you won't need to enter 2FA codes all the time -->
|
||||||
|
<!-- This however defeats the whole purpose of two-factor-auth, and should be used on alt accounts ONLY -->
|
||||||
|
<!-- You should read full documentation, along with explanation here: https://github.com/JustArchi/ArchiSteamFarm/wiki/Escrow -->
|
||||||
|
<!-- WARNING, this option can potentially LOCK OUT YOUR ACCOUNT! If you don't fully understand this feature, DON'T USE IT! -->
|
||||||
|
<UseAsfAsMobileAuthenticator type="bool" value="false"/>
|
||||||
|
|
||||||
<!-- This switch defines if the account has card drops restricted -->
|
<!-- This switch defines if the account has card drops restricted -->
|
||||||
<!-- Restricted card drops means that the account doesn't receive any steam cards until it plays the game for at least 2 hours -->
|
<!-- Restricted card drops means that the account doesn't receive any steam cards until it plays the game for at least 2 hours -->
|
||||||
<!-- As there is no magical way to detect it by ASF, I made this option config-based switch -->
|
<!-- As there is no magical way to detect it by ASF, I made this option config-based switch -->
|
||||||
|
|||||||
34
README.md
34
README.md
@@ -5,14 +5,14 @@ ASF is a C# application that allows you to farm steam cards using multiple steam
|
|||||||
|
|
||||||
**Core features:**
|
**Core features:**
|
||||||
|
|
||||||
- Automatically farm available games using any number of active accounts
|
- Automatically farm available games using any number of active accounts
|
||||||
- Automatically accept friend requests sent from master
|
- Automatically accept friend requests sent from master
|
||||||
- Automatically accept all trades coming from master
|
- Automatically accept all trades coming from master
|
||||||
- Automatically accept all steam cd-keys sent via chat from master
|
- Automatically accept all steam cd-keys sent via chat from master
|
||||||
- Possibility to choose the most efficient cards farming algorithm, based on given account
|
- Possibility to choose the most efficient cards farming algorithm, based on given account
|
||||||
- SteamGuard / SteamParental / 2FA support
|
- SteamGuard / SteamParental / 2FA support
|
||||||
- Update notifications
|
- Update notifications
|
||||||
- Full Mono support, cross-OS compatibility
|
- Full Mono support, cross-OS compatibility
|
||||||
|
|
||||||
**Setting up:**
|
**Setting up:**
|
||||||
|
|
||||||
@@ -26,13 +26,17 @@ ASF doesn't require and doesn't interfere in any way with Steam client, which me
|
|||||||
|
|
||||||
**Current Commands:**
|
**Current Commands:**
|
||||||
|
|
||||||
- `!exit` Stops whole ASF
|
- `!2fa` Generates temporary 2FA token for current bot instance
|
||||||
- `!farm` Restarts cards farming module. ASF automatically executes that if any cd-key is successfully claimed
|
- `!2fa <BOT>` Generates temporary 2FA token for given bot instance
|
||||||
- `!redeem <KEY>` Redeems cd-key on current bot instance. You can also paste cd-key directly to the chat
|
- `!2faoff` Deactivates 2FA for current bot instance
|
||||||
- `!start <BOT>` Starts given bot instance, after it was ```!stop```pped
|
- `!2faoff <BOT>` Deactivates 2FA for given bot instance
|
||||||
- `!status` Prints current status of ASF
|
- `!exit` Stops whole ASF
|
||||||
- `!stop` Stops current bot instance
|
- `!farm` Restarts cards farming module. ASF automatically executes that if any cd-key is successfully claimed
|
||||||
- `!stop <BOT>` Stops given bot instance
|
- `!redeem <KEY>` Redeems cd-key on current bot instance. You can also paste cd-key directly to the chat
|
||||||
|
- `!start <BOT>` Starts given bot instance
|
||||||
|
- `!status` Prints current status of ASF
|
||||||
|
- `!stop` Stops current bot instance
|
||||||
|
- `!stop <BOT>` Stops given bot instance
|
||||||
|
|
||||||
> Commands can be executed via a private chat with your bot.
|
> Commands can be executed via a private chat with your bot.
|
||||||
> Remember that bot accepts commands only from ```SteamMasterID```. That property can be configured in the config.
|
> Remember that bot accepts commands only from ```SteamMasterID```. That property can be configured in the config.
|
||||||
|
|||||||
12
SteamAuth/APIEndpoints.cs
Normal file
12
SteamAuth/APIEndpoints.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace SteamAuth
|
||||||
|
{
|
||||||
|
public static class APIEndpoints
|
||||||
|
{
|
||||||
|
public const string STEAMAPI_BASE = "https://api.steampowered.com";
|
||||||
|
public const string COMMUNITY_BASE = "https://steamcommunity.com";
|
||||||
|
public const string MOBILEAUTH_BASE = STEAMAPI_BASE + "/IMobileAuthService/%s/v0001";
|
||||||
|
public static string MOBILEAUTH_GETWGTOKEN = MOBILEAUTH_BASE.Replace("%s", "GetWGToken");
|
||||||
|
public const string TWO_FACTOR_BASE = STEAMAPI_BASE + "/ITwoFactorService/%s/v0001";
|
||||||
|
public static string TWO_FACTOR_TIME_QUERY = TWO_FACTOR_BASE.Replace("%s", "QueryTime");
|
||||||
|
}
|
||||||
|
}
|
||||||
240
SteamAuth/AuthenticatorLinker.cs
Normal file
240
SteamAuth/AuthenticatorLinker.cs
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SteamAuth
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Handles the linking process for a new mobile authenticator.
|
||||||
|
/// </summary>
|
||||||
|
public class AuthenticatorLinker
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set to register a new phone number when linking. If a phone number is not set on the account, this must be set. If a phone number is set on the account, this must be null.
|
||||||
|
/// </summary>
|
||||||
|
public string PhoneNumber = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Randomly-generated device ID. Should only be generated once per linker.
|
||||||
|
/// </summary>
|
||||||
|
public string DeviceID { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// After the initial link step, if successful, this will be the SteamGuard data for the account. PLEASE save this somewhere after generating it; it's vital data.
|
||||||
|
/// </summary>
|
||||||
|
public SteamGuardAccount LinkedAccount { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True if the authenticator has been fully finalized.
|
||||||
|
/// </summary>
|
||||||
|
public bool Finalized = false;
|
||||||
|
|
||||||
|
private SessionData _session;
|
||||||
|
private CookieContainer _cookies;
|
||||||
|
|
||||||
|
public AuthenticatorLinker(SessionData session)
|
||||||
|
{
|
||||||
|
this._session = session;
|
||||||
|
this.DeviceID = _generateDeviceID();
|
||||||
|
|
||||||
|
this._cookies = new CookieContainer();
|
||||||
|
session.AddCookies(_cookies);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinkResult AddAuthenticator()
|
||||||
|
{
|
||||||
|
bool hasPhone = _hasPhoneAttached();
|
||||||
|
if (hasPhone && PhoneNumber != null)
|
||||||
|
return LinkResult.MustRemovePhoneNumber;
|
||||||
|
if (!hasPhone && PhoneNumber == null)
|
||||||
|
return LinkResult.MustProvidePhoneNumber;
|
||||||
|
|
||||||
|
if (!hasPhone)
|
||||||
|
{
|
||||||
|
if (!_addPhoneNumber())
|
||||||
|
{
|
||||||
|
return LinkResult.GeneralFailure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var postData = new NameValueCollection();
|
||||||
|
postData.Add("access_token", _session.OAuthToken);
|
||||||
|
postData.Add("steamid", _session.SteamID.ToString());
|
||||||
|
postData.Add("authenticator_type", "1");
|
||||||
|
postData.Add("device_identifier", this.DeviceID);
|
||||||
|
postData.Add("sms_phone_id", "1");
|
||||||
|
|
||||||
|
string response = SteamWeb.MobileLoginRequest(APIEndpoints.STEAMAPI_BASE + "/ITwoFactorService/AddAuthenticator/v0001", "POST", postData);
|
||||||
|
if (response == null) return LinkResult.GeneralFailure;
|
||||||
|
|
||||||
|
var addAuthenticatorResponse = JsonConvert.DeserializeObject<AddAuthenticatorResponse>(response);
|
||||||
|
if (addAuthenticatorResponse == null || addAuthenticatorResponse.Response == null || addAuthenticatorResponse.Response.Status != 1)
|
||||||
|
{
|
||||||
|
return LinkResult.GeneralFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.LinkedAccount = addAuthenticatorResponse.Response;
|
||||||
|
LinkedAccount.Session = this._session;
|
||||||
|
LinkedAccount.DeviceID = this.DeviceID;
|
||||||
|
|
||||||
|
return LinkResult.AwaitingFinalization;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FinalizeResult FinalizeAddAuthenticator(string smsCode)
|
||||||
|
{
|
||||||
|
bool smsCodeGood = false;
|
||||||
|
|
||||||
|
var postData = new NameValueCollection();
|
||||||
|
postData.Add("steamid", _session.SteamID.ToString());
|
||||||
|
postData.Add("access_token", _session.OAuthToken);
|
||||||
|
postData.Add("activation_code", smsCode);
|
||||||
|
postData.Add("authenticator_code", "");
|
||||||
|
int tries = 0;
|
||||||
|
while (tries <= 30)
|
||||||
|
{
|
||||||
|
postData.Set("authenticator_code", tries == 0 ? "" : LinkedAccount.GenerateSteamGuardCode());
|
||||||
|
postData.Add("authenticator_time", TimeAligner.GetSteamTime().ToString());
|
||||||
|
|
||||||
|
if(smsCodeGood)
|
||||||
|
postData.Set("activation_code", "");
|
||||||
|
|
||||||
|
string response = SteamWeb.MobileLoginRequest(APIEndpoints.STEAMAPI_BASE + "/ITwoFactorService/FinalizeAddAuthenticator/v0001", "POST", postData);
|
||||||
|
if (response == null) return FinalizeResult.GeneralFailure;
|
||||||
|
|
||||||
|
var finalizeResponse = JsonConvert.DeserializeObject<FinalizeAuthenticatorResponse>(response);
|
||||||
|
|
||||||
|
if (finalizeResponse == null || finalizeResponse.Response == null)
|
||||||
|
{
|
||||||
|
return FinalizeResult.GeneralFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(finalizeResponse.Response.Status == 89)
|
||||||
|
{
|
||||||
|
return FinalizeResult.BadSMSCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(finalizeResponse.Response.Status == 88)
|
||||||
|
{
|
||||||
|
if(tries >= 30)
|
||||||
|
{
|
||||||
|
return FinalizeResult.UnableToGenerateCorrectCodes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!finalizeResponse.Response.Success)
|
||||||
|
{
|
||||||
|
return FinalizeResult.GeneralFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finalizeResponse.Response.WantMore)
|
||||||
|
{
|
||||||
|
smsCodeGood = true;
|
||||||
|
tries++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.LinkedAccount.FullyEnrolled = true;
|
||||||
|
return FinalizeResult.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FinalizeResult.GeneralFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _addPhoneNumber()
|
||||||
|
{
|
||||||
|
string response = SteamWeb.Request(APIEndpoints.COMMUNITY_BASE + "/steamguard/phoneajax?op=add_phone_number&arg=" + WebUtility.UrlEncode(PhoneNumber), "GET", null, _cookies);
|
||||||
|
if (response == null) return false;
|
||||||
|
|
||||||
|
var addPhoneNumberResponse = JsonConvert.DeserializeObject<AddPhoneResponse>(response);
|
||||||
|
return addPhoneNumberResponse.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _hasPhoneAttached()
|
||||||
|
{
|
||||||
|
var postData = new NameValueCollection();
|
||||||
|
postData.Add("op", "has_phone");
|
||||||
|
postData.Add("arg", "null");
|
||||||
|
string response = SteamWeb.MobileLoginRequest(APIEndpoints.COMMUNITY_BASE + "/steamguard/phoneajax", "GET", postData, _cookies);
|
||||||
|
if (response == null) return false;
|
||||||
|
|
||||||
|
var hasPhoneResponse = JsonConvert.DeserializeObject<HasPhoneResponse>(response);
|
||||||
|
return hasPhoneResponse.HasPhone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum LinkResult
|
||||||
|
{
|
||||||
|
MustProvidePhoneNumber, //No phone number on the account
|
||||||
|
MustRemovePhoneNumber, //A phone number is already on the account
|
||||||
|
AwaitingFinalization, //Must provide an SMS code
|
||||||
|
GeneralFailure //General failure (really now!)
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum FinalizeResult
|
||||||
|
{
|
||||||
|
BadSMSCode,
|
||||||
|
UnableToGenerateCorrectCodes,
|
||||||
|
Success,
|
||||||
|
GeneralFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AddAuthenticatorResponse
|
||||||
|
{
|
||||||
|
[JsonProperty("response")]
|
||||||
|
public SteamGuardAccount Response { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FinalizeAuthenticatorResponse
|
||||||
|
{
|
||||||
|
[JsonProperty("response")]
|
||||||
|
public FinalizeAuthenticatorInternalResponse Response { get; set; }
|
||||||
|
|
||||||
|
internal class FinalizeAuthenticatorInternalResponse
|
||||||
|
{
|
||||||
|
[JsonProperty("status")]
|
||||||
|
public int Status { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("server_time")]
|
||||||
|
public long ServerTime { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("want_more")]
|
||||||
|
public bool WantMore { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("success")]
|
||||||
|
public bool Success { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HasPhoneResponse
|
||||||
|
{
|
||||||
|
[JsonProperty("has_phone")]
|
||||||
|
public bool HasPhone { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AddPhoneResponse
|
||||||
|
{
|
||||||
|
[JsonProperty("success")]
|
||||||
|
public bool Success { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _generateDeviceID()
|
||||||
|
{
|
||||||
|
using (var sha1 = new SHA1Managed())
|
||||||
|
{
|
||||||
|
RNGCryptoServiceProvider secureRandom = new RNGCryptoServiceProvider();
|
||||||
|
byte[] randomBytes = new byte[8];
|
||||||
|
secureRandom.GetBytes(randomBytes);
|
||||||
|
|
||||||
|
byte[] hashedBytes = sha1.ComputeHash(randomBytes);
|
||||||
|
return "android:" + BitConverter.ToString(hashedBytes).Replace("-", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
SteamAuth/Confirmation.cs
Normal file
15
SteamAuth/Confirmation.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SteamAuth
|
||||||
|
{
|
||||||
|
public class Confirmation
|
||||||
|
{
|
||||||
|
public string ConfirmationID;
|
||||||
|
public string ConfirmationKey;
|
||||||
|
public string ConfirmationDescription;
|
||||||
|
}
|
||||||
|
}
|
||||||
36
SteamAuth/Properties/AssemblyInfo.cs
Normal file
36
SteamAuth/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyTitle("SteamAuth")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("SteamAuth")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2015")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("5ad0934e-f6c4-4ae5-83af-c788313b2a87")]
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
|
// by using the '*' as shown below:
|
||||||
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||||
39
SteamAuth/SessionData.cs
Normal file
39
SteamAuth/SessionData.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace SteamAuth
|
||||||
|
{
|
||||||
|
public class SessionData
|
||||||
|
{
|
||||||
|
public string SessionID { get; set; }
|
||||||
|
|
||||||
|
public string SteamLogin { get; set; }
|
||||||
|
|
||||||
|
public string SteamLoginSecure { get; set; }
|
||||||
|
|
||||||
|
public string WebCookie { get; set; }
|
||||||
|
|
||||||
|
public string OAuthToken { get; set; }
|
||||||
|
|
||||||
|
public ulong SteamID { get; set; }
|
||||||
|
|
||||||
|
public void AddCookies(CookieContainer cookies)
|
||||||
|
{
|
||||||
|
cookies.Add(new Cookie("mobileClientVersion", "0 (2.1.3)", "/", ".steamcommunity.com"));
|
||||||
|
cookies.Add(new Cookie("mobileClient", "android", "/", ".steamcommunity.com"));
|
||||||
|
|
||||||
|
cookies.Add(new Cookie("steamid", SteamID.ToString(), "/", ".steamcommunity.com"));
|
||||||
|
cookies.Add(new Cookie("steamLogin", SteamLogin, "/", ".steamcommunity.com")
|
||||||
|
{
|
||||||
|
HttpOnly = true
|
||||||
|
});
|
||||||
|
|
||||||
|
cookies.Add(new Cookie("steamLoginSecure", SteamLoginSecure, "/", ".steamcommunity.com")
|
||||||
|
{
|
||||||
|
HttpOnly = true,
|
||||||
|
Secure = true
|
||||||
|
});
|
||||||
|
cookies.Add(new Cookie("Steam_Language", "english", "/", ".steamcommunity.com"));
|
||||||
|
cookies.Add(new Cookie("dob", "", "/", ".steamcommunity.com"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
69
SteamAuth/SteamAuth.csproj
Normal file
69
SteamAuth/SteamAuth.csproj
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}</ProjectGuid>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<RootNamespace>SteamAuth</RootNamespace>
|
||||||
|
<AssemblyName>SteamAuth</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\Newtonsoft.Json.8.0.1-beta3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="APIEndpoints.cs" />
|
||||||
|
<Compile Include="AuthenticatorLinker.cs" />
|
||||||
|
<Compile Include="Confirmation.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="SessionData.cs" />
|
||||||
|
<Compile Include="SteamGuardAccount.cs" />
|
||||||
|
<Compile Include="SteamWeb.cs" />
|
||||||
|
<Compile Include="TimeAligner.cs" />
|
||||||
|
<Compile Include="UserLogin.cs" />
|
||||||
|
<Compile Include="Util.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="packages.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
<Target Name="BeforeBuild">
|
||||||
|
</Target>
|
||||||
|
<Target Name="AfterBuild">
|
||||||
|
</Target>
|
||||||
|
-->
|
||||||
|
</Project>
|
||||||
28
SteamAuth/SteamAuth.sln
Normal file
28
SteamAuth/SteamAuth.sln
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 14
|
||||||
|
VisualStudioVersion = 14.0.23107.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamAuth", "SteamAuth.csproj", "{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestBed", "..\TestBed\TestBed.csproj", "{8A732227-C090-4011-9F0A-51180CFE6271}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8A732227-C090-4011-9F0A-51180CFE6271}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8A732227-C090-4011-9F0A-51180CFE6271}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8A732227-C090-4011-9F0A-51180CFE6271}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8A732227-C090-4011-9F0A-51180CFE6271}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
333
SteamAuth/SteamGuardAccount.cs
Normal file
333
SteamAuth/SteamGuardAccount.cs
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Net;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace SteamAuth
|
||||||
|
{
|
||||||
|
public class SteamGuardAccount
|
||||||
|
{
|
||||||
|
[JsonProperty("shared_secret")]
|
||||||
|
public string SharedSecret { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("serial_number")]
|
||||||
|
public string SerialNumber { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("revocation_code")]
|
||||||
|
public string RevocationCode { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("uri")]
|
||||||
|
public string URI { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("server_time")]
|
||||||
|
public long ServerTime { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("account_name")]
|
||||||
|
public string AccountName { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("token_gid")]
|
||||||
|
public string TokenGID { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("identity_secret")]
|
||||||
|
public string IdentitySecret { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("secret_1")]
|
||||||
|
public string Secret1 { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("status")]
|
||||||
|
public int Status { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("device_id")]
|
||||||
|
public string DeviceID { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set to true if the authenticator has actually been applied to the account.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty("fully_enrolled")]
|
||||||
|
public bool FullyEnrolled { get; set; }
|
||||||
|
|
||||||
|
public SessionData Session { get; set; }
|
||||||
|
|
||||||
|
private static byte[] steamGuardCodeTranslations = new byte[] { 50, 51, 52, 53, 54, 55, 56, 57, 66, 67, 68, 70, 71, 72, 74, 75, 77, 78, 80, 81, 82, 84, 86, 87, 88, 89 };
|
||||||
|
|
||||||
|
public bool DeactivateAuthenticator()
|
||||||
|
{
|
||||||
|
var postData = new NameValueCollection();
|
||||||
|
postData.Add("steamid", this.Session.SteamID.ToString());
|
||||||
|
postData.Add("steamguard_scheme", "2");
|
||||||
|
postData.Add("revocation_code", this.RevocationCode);
|
||||||
|
postData.Add("access_token", this.Session.OAuthToken);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string response = SteamWeb.MobileLoginRequest(APIEndpoints.STEAMAPI_BASE + "/ITwoFactorService/RemoveAuthenticator/v0001", "POST", postData);
|
||||||
|
var removeResponse = JsonConvert.DeserializeObject<RemoveAuthenticatorResponse>(response);
|
||||||
|
|
||||||
|
if (removeResponse == null || removeResponse.Response == null || !removeResponse.Response.Success) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GenerateSteamGuardCode()
|
||||||
|
{
|
||||||
|
return GenerateSteamGuardCodeForTime(TimeAligner.GetSteamTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GenerateSteamGuardCodeForTime(long time)
|
||||||
|
{
|
||||||
|
if (this.SharedSecret == null || this.SharedSecret.Length == 0)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] sharedSecretArray = Convert.FromBase64String(this.SharedSecret);
|
||||||
|
byte[] timeArray = new byte[8];
|
||||||
|
|
||||||
|
time /= 30L;
|
||||||
|
|
||||||
|
for (int i = 8; i > 0; i--)
|
||||||
|
{
|
||||||
|
timeArray[i - 1] = (byte)time;
|
||||||
|
time >>= 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
HMACSHA1 hmacGenerator = new HMACSHA1();
|
||||||
|
hmacGenerator.Key = sharedSecretArray;
|
||||||
|
byte[] hashedData = hmacGenerator.ComputeHash(timeArray);
|
||||||
|
byte[] codeArray = new byte[5];
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byte b = (byte)(hashedData[19] & 0xF);
|
||||||
|
int codePoint = (hashedData[b] & 0x7F) << 24 | (hashedData[b + 1] & 0xFF) << 16 | (hashedData[b + 2] & 0xFF) << 8 | (hashedData[b + 3] & 0xFF);
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; ++i)
|
||||||
|
{
|
||||||
|
codeArray[i] = steamGuardCodeTranslations[codePoint % steamGuardCodeTranslations.Length];
|
||||||
|
codePoint /= steamGuardCodeTranslations.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return null; //Change later, catch-alls are bad!
|
||||||
|
}
|
||||||
|
return Encoding.UTF8.GetString(codeArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Confirmation[] FetchConfirmations()
|
||||||
|
{
|
||||||
|
string url = this.GenerateConfirmationURL();
|
||||||
|
|
||||||
|
CookieContainer cookies = new CookieContainer();
|
||||||
|
this.Session.AddCookies(cookies);
|
||||||
|
|
||||||
|
string response = SteamWeb.Request(url, "GET", null, cookies);
|
||||||
|
|
||||||
|
/*So you're going to see this abomination and you're going to be upset.
|
||||||
|
It's understandable. But the thing is, regex for HTML -- while awful -- makes this way faster than parsing a DOM, plus we don't need another library.
|
||||||
|
And because the data is always in the same place and same format... It's not as if we're trying to naturally understand HTML here. Just extract strings.
|
||||||
|
I'm sorry. */
|
||||||
|
|
||||||
|
Regex confIDRegex = new Regex("data-confid=\"(\\d+)\"");
|
||||||
|
Regex confKeyRegex = new Regex("data-key=\"(\\d+)\"");
|
||||||
|
Regex confDescRegex = new Regex("<div>((Confirm|Trade with|Sell -) .+)</div>");
|
||||||
|
|
||||||
|
if (response == null || !(confIDRegex.IsMatch(response) && confKeyRegex.IsMatch(response) && confDescRegex.IsMatch(response)))
|
||||||
|
{
|
||||||
|
if (response == null || !response.Contains("<div>Nothing to confirm</div>"))
|
||||||
|
{
|
||||||
|
throw new WGTokenInvalidException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Confirmation[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
MatchCollection confIDs = confIDRegex.Matches(response);
|
||||||
|
MatchCollection confKeys = confKeyRegex.Matches(response);
|
||||||
|
MatchCollection confDescs = confDescRegex.Matches(response);
|
||||||
|
|
||||||
|
List<Confirmation> ret = new List<Confirmation>();
|
||||||
|
for (int i = 0; i < confIDs.Count; i++)
|
||||||
|
{
|
||||||
|
string confID = confIDs[i].Groups[1].Value;
|
||||||
|
string confKey = confKeys[i].Groups[1].Value;
|
||||||
|
string confDesc = confDescs[i].Groups[1].Value;
|
||||||
|
Confirmation conf = new Confirmation()
|
||||||
|
{
|
||||||
|
ConfirmationDescription = confDesc,
|
||||||
|
ConfirmationID = confID,
|
||||||
|
ConfirmationKey = confKey
|
||||||
|
};
|
||||||
|
ret.Add(conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AcceptConfirmation(Confirmation conf)
|
||||||
|
{
|
||||||
|
return _sendConfirmationAjax(conf, "allow");
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DenyConfirmation(Confirmation conf)
|
||||||
|
{
|
||||||
|
return _sendConfirmationAjax(conf, "cancel");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Refreshes the Steam session. Necessary to perform confirmations if your session has expired or changed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool RefreshSession()
|
||||||
|
{
|
||||||
|
string url = APIEndpoints.MOBILEAUTH_GETWGTOKEN;
|
||||||
|
NameValueCollection postData = new NameValueCollection();
|
||||||
|
postData.Add("access_token", this.Session.OAuthToken);
|
||||||
|
|
||||||
|
string response = SteamWeb.Request(url, "POST", postData);
|
||||||
|
if (response == null) return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var refreshResponse = JsonConvert.DeserializeObject<RefreshSessionDataResponse>(response);
|
||||||
|
if (refreshResponse == null || refreshResponse.Response == null || String.IsNullOrEmpty(refreshResponse.Response.Token))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
string token = this.Session.SteamID + "%7C%7C" + refreshResponse.Response.Token;
|
||||||
|
string tokenSecure = this.Session.SteamID + "%7C%7C" + refreshResponse.Response.TokenSecure;
|
||||||
|
|
||||||
|
this.Session.SteamLogin = token;
|
||||||
|
this.Session.SteamLoginSecure = tokenSecure;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _sendConfirmationAjax(Confirmation conf, string op)
|
||||||
|
{
|
||||||
|
string url = APIEndpoints.COMMUNITY_BASE + "/mobileconf/ajaxop";
|
||||||
|
string queryString = "?op=" + op + "&";
|
||||||
|
queryString += _generateConfirmationQueryParams(op);
|
||||||
|
queryString += "&cid=" + conf.ConfirmationID + "&ck=" + conf.ConfirmationKey;
|
||||||
|
url += queryString;
|
||||||
|
|
||||||
|
CookieContainer cookies = new CookieContainer();
|
||||||
|
this.Session.AddCookies(cookies);
|
||||||
|
string referer = GenerateConfirmationURL();
|
||||||
|
|
||||||
|
string response = SteamWeb.Request(url, "GET", null, cookies, null);
|
||||||
|
if (response == null) return false;
|
||||||
|
|
||||||
|
SendConfirmationResponse confResponse = JsonConvert.DeserializeObject<SendConfirmationResponse>(response);
|
||||||
|
return confResponse.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GenerateConfirmationURL(string tag = "conf")
|
||||||
|
{
|
||||||
|
string endpoint = APIEndpoints.COMMUNITY_BASE + "/mobileconf/conf?";
|
||||||
|
string queryString = _generateConfirmationQueryParams(tag);
|
||||||
|
return endpoint + queryString;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _generateConfirmationQueryParams(string tag)
|
||||||
|
{
|
||||||
|
long time = TimeAligner.GetSteamTime();
|
||||||
|
return "p=" + this.DeviceID + "&a=" + this.Session.SteamID.ToString() + "&k=" + _generateConfirmationHashForTime(time, tag) + "&t=" + time + "&m=android&tag=" + tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _generateConfirmationHashForTime(long time, string tag)
|
||||||
|
{
|
||||||
|
byte[] decode = Convert.FromBase64String(this.IdentitySecret);
|
||||||
|
int n2 = 8;
|
||||||
|
if (tag != null)
|
||||||
|
{
|
||||||
|
if (tag.Length > 32)
|
||||||
|
{
|
||||||
|
n2 = 8 + 32;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
n2 = 8 + tag.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
byte[] array = new byte[n2];
|
||||||
|
int n3 = 8;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int n4 = n3 - 1;
|
||||||
|
if (n3 <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
array[n4] = (byte)time;
|
||||||
|
time >>= 8;
|
||||||
|
n3 = n4;
|
||||||
|
}
|
||||||
|
if (tag != null)
|
||||||
|
{
|
||||||
|
Array.Copy(Encoding.UTF8.GetBytes(tag), 0, array, 8, n2 - 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HMACSHA1 hmacGenerator = new HMACSHA1();
|
||||||
|
hmacGenerator.Key = decode;
|
||||||
|
byte[] hashedData = hmacGenerator.ComputeHash(array);
|
||||||
|
string encodedData = Convert.ToBase64String(hashedData, Base64FormattingOptions.None);
|
||||||
|
string hash = WebUtility.UrlEncode(encodedData);
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return null; //Fix soon: catch-all is BAD!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: Determine how to detect an invalid session.
|
||||||
|
public class WGTokenInvalidException : Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RefreshSessionDataResponse
|
||||||
|
{
|
||||||
|
[JsonProperty("response")]
|
||||||
|
public RefreshSessionDataInternalResponse Response { get; set; }
|
||||||
|
internal class RefreshSessionDataInternalResponse
|
||||||
|
{
|
||||||
|
[JsonProperty("token")]
|
||||||
|
public string Token { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("token_secure")]
|
||||||
|
public string TokenSecure { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RemoveAuthenticatorResponse
|
||||||
|
{
|
||||||
|
[JsonProperty("response")]
|
||||||
|
public RemoveAuthenticatorInternalResponse Response { get; set; }
|
||||||
|
|
||||||
|
internal class RemoveAuthenticatorInternalResponse
|
||||||
|
{
|
||||||
|
[JsonProperty("success")]
|
||||||
|
public bool Success { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SendConfirmationResponse
|
||||||
|
{
|
||||||
|
[JsonProperty("success")]
|
||||||
|
public bool Success { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
81
SteamAuth/SteamWeb.cs
Normal file
81
SteamAuth/SteamWeb.cs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace SteamAuth
|
||||||
|
{
|
||||||
|
public class SteamWeb
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Perform a mobile login request
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">API url</param>
|
||||||
|
/// <param name="method">GET or POST</param>
|
||||||
|
/// <param name="data">Name-data pairs</param>
|
||||||
|
/// <param name="cookies">current cookie container</param>
|
||||||
|
/// <returns>response body</returns>
|
||||||
|
public static string MobileLoginRequest(string url, string method, NameValueCollection data = null, CookieContainer cookies = null, NameValueCollection headers = null)
|
||||||
|
{
|
||||||
|
return Request(url, method, data, cookies, headers, APIEndpoints.COMMUNITY_BASE + "/mobilelogin?oauth_client_id=DE45CD61&oauth_scope=read_profile%20write_profile%20read_client%20write_client");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Request(string url, string method, NameValueCollection data = null, CookieContainer cookies = null, NameValueCollection headers = null, string referer = APIEndpoints.COMMUNITY_BASE)
|
||||||
|
{
|
||||||
|
string query = (data == null ? string.Empty : string.Join("&", Array.ConvertAll(data.AllKeys, key => String.Format("{0}={1}", WebUtility.UrlEncode(key), WebUtility.UrlEncode(data[key])))));
|
||||||
|
if (method == "GET")
|
||||||
|
{
|
||||||
|
url += (url.Contains("?") ? "&" : "?") + query;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
|
||||||
|
request.Method = method;
|
||||||
|
request.Accept = "text/javascript, text/html, application/xml, text/xml, */*";
|
||||||
|
request.UserAgent = "Mozilla/5.0 (Linux; U; Android 4.1.1; en-us; Google Nexus 4 - 4.1.1 - API 16 - 768x1280 Build/JRO03S) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30";
|
||||||
|
request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
|
||||||
|
request.Referer = referer;
|
||||||
|
|
||||||
|
if (headers != null)
|
||||||
|
{
|
||||||
|
request.Headers.Add(headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cookies != null)
|
||||||
|
{
|
||||||
|
request.CookieContainer = cookies;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method == "POST")
|
||||||
|
{
|
||||||
|
request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8";
|
||||||
|
request.ContentLength = query.Length;
|
||||||
|
|
||||||
|
StreamWriter requestStream = new StreamWriter(request.GetRequestStream());
|
||||||
|
requestStream.Write(query);
|
||||||
|
requestStream.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
|
||||||
|
{
|
||||||
|
if (response.StatusCode != HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (StreamReader responseStream = new StreamReader(response.GetResponseStream()))
|
||||||
|
{
|
||||||
|
string responseData = responseStream.ReadToEnd();
|
||||||
|
|
||||||
|
return responseData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (WebException ex)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
SteamAuth/TimeAligner.cs
Normal file
56
SteamAuth/TimeAligner.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
using System.Net;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace SteamAuth
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class to help align system time with the Steam server time. Not super advanced; probably not taking some things into account that it should.
|
||||||
|
/// Necessary to generate up-to-date codes. In general, this will have an error of less than a second, assuming Steam is operational.
|
||||||
|
/// </summary>
|
||||||
|
public class TimeAligner
|
||||||
|
{
|
||||||
|
private static bool _aligned = false;
|
||||||
|
private static int _timeDifference = 0;
|
||||||
|
|
||||||
|
public static long GetSteamTime()
|
||||||
|
{
|
||||||
|
if (!TimeAligner._aligned)
|
||||||
|
{
|
||||||
|
TimeAligner.AlignTime();
|
||||||
|
}
|
||||||
|
return Util.GetSystemUnixTime() + _timeDifference;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AlignTime()
|
||||||
|
{
|
||||||
|
long currentTime = Util.GetSystemUnixTime();
|
||||||
|
using (WebClient client = new WebClient())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string response = client.UploadString(APIEndpoints.TWO_FACTOR_TIME_QUERY, "steamid=0");
|
||||||
|
TimeQuery query = JsonConvert.DeserializeObject<TimeQuery>(response);
|
||||||
|
TimeAligner._timeDifference = (int)(query.Response.ServerTime - currentTime);
|
||||||
|
TimeAligner._aligned = true;
|
||||||
|
}
|
||||||
|
catch (WebException e)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class TimeQuery
|
||||||
|
{
|
||||||
|
[JsonProperty("response")]
|
||||||
|
internal TimeQueryResponse Response { get; set; }
|
||||||
|
|
||||||
|
internal class TimeQueryResponse
|
||||||
|
{
|
||||||
|
[JsonProperty("server_time")]
|
||||||
|
public long ServerTime { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
240
SteamAuth/UserLogin.cs
Normal file
240
SteamAuth/UserLogin.cs
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Net;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SteamAuth
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Handles logging the user into the mobile Steam website. Necessary to generate OAuth token and session cookies.
|
||||||
|
/// </summary>
|
||||||
|
public class UserLogin
|
||||||
|
{
|
||||||
|
public string Username;
|
||||||
|
public string Password;
|
||||||
|
public ulong SteamID;
|
||||||
|
|
||||||
|
public bool RequiresCaptcha;
|
||||||
|
public string CaptchaGID = null;
|
||||||
|
public string CaptchaText = null;
|
||||||
|
|
||||||
|
public bool RequiresEmail;
|
||||||
|
public string EmailDomain = null;
|
||||||
|
public string EmailCode = null;
|
||||||
|
|
||||||
|
public bool Requires2FA;
|
||||||
|
public string TwoFactorCode = null;
|
||||||
|
|
||||||
|
public SessionData Session = null;
|
||||||
|
public bool LoggedIn = false;
|
||||||
|
|
||||||
|
private CookieContainer _cookies = new CookieContainer();
|
||||||
|
|
||||||
|
public UserLogin(string username, string password)
|
||||||
|
{
|
||||||
|
this.Username = username;
|
||||||
|
this.Password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoginResult DoLogin()
|
||||||
|
{
|
||||||
|
var postData = new NameValueCollection();
|
||||||
|
var cookies = _cookies;
|
||||||
|
string response = null;
|
||||||
|
|
||||||
|
if (cookies.Count == 0)
|
||||||
|
{
|
||||||
|
//Generate a SessionID
|
||||||
|
cookies.Add(new Cookie("mobileClientVersion", "0 (2.1.3)", "/", ".steamcommunity.com"));
|
||||||
|
cookies.Add(new Cookie("mobileClient", "android", "/", ".steamcommunity.com"));
|
||||||
|
cookies.Add(new Cookie("Steam_Language", "english", "/", ".steamcommunity.com"));
|
||||||
|
|
||||||
|
NameValueCollection headers = new NameValueCollection();
|
||||||
|
headers.Add("X-Requested-With", "com.valvesoftware.android.steam.community");
|
||||||
|
|
||||||
|
SteamWeb.MobileLoginRequest("https://steamcommunity.com/login?oauth_client_id=DE45CD61&oauth_scope=read_profile%20write_profile%20read_client%20write_client", "GET", null, cookies, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
postData.Add("username", this.Username);
|
||||||
|
response = SteamWeb.MobileLoginRequest(APIEndpoints.COMMUNITY_BASE + "/login/getrsakey", "POST", postData, cookies);
|
||||||
|
if (response == null) return LoginResult.GeneralFailure;
|
||||||
|
|
||||||
|
var rsaResponse = JsonConvert.DeserializeObject<RSAResponse>(response);
|
||||||
|
|
||||||
|
if (!rsaResponse.Success)
|
||||||
|
{
|
||||||
|
return LoginResult.BadRSA;
|
||||||
|
}
|
||||||
|
|
||||||
|
RNGCryptoServiceProvider secureRandom = new RNGCryptoServiceProvider();
|
||||||
|
byte[] encryptedPasswordBytes;
|
||||||
|
using (var rsaEncryptor = new RSACryptoServiceProvider())
|
||||||
|
{
|
||||||
|
var passwordBytes = Encoding.ASCII.GetBytes(this.Password);
|
||||||
|
var rsaParameters = rsaEncryptor.ExportParameters(false);
|
||||||
|
rsaParameters.Exponent = Util.HexStringToByteArray(rsaResponse.Exponent);
|
||||||
|
rsaParameters.Modulus = Util.HexStringToByteArray(rsaResponse.Modulus);
|
||||||
|
rsaEncryptor.ImportParameters(rsaParameters);
|
||||||
|
encryptedPasswordBytes = rsaEncryptor.Encrypt(passwordBytes, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
string encryptedPassword = Convert.ToBase64String(encryptedPasswordBytes);
|
||||||
|
|
||||||
|
postData.Clear();
|
||||||
|
postData.Add("username", this.Username);
|
||||||
|
postData.Add("password", encryptedPassword);
|
||||||
|
|
||||||
|
postData.Add("twofactorcode", this.TwoFactorCode ?? "");
|
||||||
|
|
||||||
|
postData.Add("captchagid", this.RequiresCaptcha ? this.CaptchaGID : "-1");
|
||||||
|
postData.Add("captcha_text", this.RequiresCaptcha ? this.CaptchaText : "");
|
||||||
|
|
||||||
|
postData.Add("emailsteamid", (this.Requires2FA || this.RequiresEmail) ? this.SteamID.ToString() : "");
|
||||||
|
postData.Add("emailauth", this.RequiresEmail ? this.EmailCode : "");
|
||||||
|
|
||||||
|
postData.Add("rsatimestamp", rsaResponse.Timestamp);
|
||||||
|
postData.Add("remember_login", "false");
|
||||||
|
postData.Add("oauth_client_id", "DE45CD61");
|
||||||
|
postData.Add("oauth_scope", "read_profile write_profile read_client write_client");
|
||||||
|
postData.Add("loginfriendlyname", "#login_emailauth_friendlyname_mobile");
|
||||||
|
postData.Add("donotcache", Util.GetSystemUnixTime().ToString());
|
||||||
|
|
||||||
|
response = SteamWeb.MobileLoginRequest(APIEndpoints.COMMUNITY_BASE + "/login/dologin", "POST", postData, cookies);
|
||||||
|
if (response == null) return LoginResult.GeneralFailure;
|
||||||
|
|
||||||
|
var loginResponse = JsonConvert.DeserializeObject<LoginResponse>(response);
|
||||||
|
|
||||||
|
if (loginResponse.CaptchaNeeded)
|
||||||
|
{
|
||||||
|
this.RequiresCaptcha = true;
|
||||||
|
this.CaptchaGID = loginResponse.CaptchaGID;
|
||||||
|
return LoginResult.NeedCaptcha;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loginResponse.EmailAuthNeeded)
|
||||||
|
{
|
||||||
|
this.RequiresEmail = true;
|
||||||
|
this.SteamID = loginResponse.EmailSteamID;
|
||||||
|
return LoginResult.NeedEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loginResponse.TwoFactorNeeded && !loginResponse.Success)
|
||||||
|
{
|
||||||
|
this.Requires2FA = true;
|
||||||
|
return LoginResult.Need2FA;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loginResponse.OAuthData == null || loginResponse.OAuthData.OAuthToken == null || loginResponse.OAuthData.OAuthToken.Length == 0)
|
||||||
|
{
|
||||||
|
return LoginResult.GeneralFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loginResponse.LoginComplete)
|
||||||
|
{
|
||||||
|
return LoginResult.BadCredentials;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var readableCookies = cookies.GetCookies(new Uri("https://steamcommunity.com"));
|
||||||
|
var oAuthData = loginResponse.OAuthData;
|
||||||
|
|
||||||
|
SessionData session = new SessionData();
|
||||||
|
session.OAuthToken = oAuthData.OAuthToken;
|
||||||
|
session.SteamID = oAuthData.SteamID;
|
||||||
|
session.SteamLogin = session.SteamID + "%7C%7C" + oAuthData.SteamLogin;
|
||||||
|
session.SteamLoginSecure = session.SteamID + "%7C%7C" + oAuthData.SteamLoginSecure;
|
||||||
|
session.WebCookie = oAuthData.Webcookie;
|
||||||
|
session.SessionID = readableCookies["sessionid"].Value;
|
||||||
|
this.Session = session;
|
||||||
|
this.LoggedIn = true;
|
||||||
|
return LoginResult.LoginOkay;
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoginResult.GeneralFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LoginResponse
|
||||||
|
{
|
||||||
|
[JsonProperty("success")]
|
||||||
|
public bool Success { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("login_complete")]
|
||||||
|
public bool LoginComplete { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("oauth")]
|
||||||
|
public string OAuthDataString { get; set; }
|
||||||
|
|
||||||
|
public OAuth OAuthData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return OAuthDataString != null ? JsonConvert.DeserializeObject<OAuth>(OAuthDataString) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonProperty("captcha_needed")]
|
||||||
|
public bool CaptchaNeeded { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("captcha_gid")]
|
||||||
|
public string CaptchaGID { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("emailsteamid")]
|
||||||
|
public ulong EmailSteamID { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("emailauth_needed")]
|
||||||
|
public bool EmailAuthNeeded { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("requires_twofactor")]
|
||||||
|
public bool TwoFactorNeeded { get; set; }
|
||||||
|
|
||||||
|
internal class OAuth
|
||||||
|
{
|
||||||
|
[JsonProperty("steamid")]
|
||||||
|
public ulong SteamID { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("oauth_token")]
|
||||||
|
public string OAuthToken { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("wgtoken")]
|
||||||
|
public string SteamLogin { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("wgtoken_secure")]
|
||||||
|
public string SteamLoginSecure { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("webcookie")]
|
||||||
|
public string Webcookie { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RSAResponse
|
||||||
|
{
|
||||||
|
[JsonProperty("success")]
|
||||||
|
public bool Success { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("publickey_exp")]
|
||||||
|
public string Exponent { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("publickey_mod")]
|
||||||
|
public string Modulus { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("timestamp")]
|
||||||
|
public string Timestamp { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("steamid")]
|
||||||
|
public ulong SteamID { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum LoginResult
|
||||||
|
{
|
||||||
|
LoginOkay,
|
||||||
|
GeneralFailure,
|
||||||
|
BadRSA,
|
||||||
|
BadCredentials,
|
||||||
|
NeedCaptcha,
|
||||||
|
Need2FA,
|
||||||
|
NeedEmail,
|
||||||
|
}
|
||||||
|
}
|
||||||
24
SteamAuth/Util.cs
Normal file
24
SteamAuth/Util.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace SteamAuth
|
||||||
|
{
|
||||||
|
public class Util
|
||||||
|
{
|
||||||
|
public static long GetSystemUnixTime()
|
||||||
|
{
|
||||||
|
return (long)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] HexStringToByteArray(string hex)
|
||||||
|
{
|
||||||
|
int hexLen = hex.Length;
|
||||||
|
byte[] ret = new byte[hexLen / 2];
|
||||||
|
for (int i = 0; i < hexLen; i += 2)
|
||||||
|
{
|
||||||
|
ret[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
SteamAuth/packages.config
Normal file
4
SteamAuth/packages.config
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<packages>
|
||||||
|
<package id="Newtonsoft.Json" version="8.0.1-beta3" targetFramework="net452" />
|
||||||
|
</packages>
|
||||||
Reference in New Issue
Block a user