Skip to content

Commit

Permalink
Merge pull request #3 from AN0NCER/framework
Browse files Browse the repository at this point in the history
Adding Library Framework 4.8
  • Loading branch information
AN0NCER authored Aug 12, 2022
2 parents cdf9d38 + 1599ed0 commit 9bcb4aa
Show file tree
Hide file tree
Showing 12 changed files with 1,535 additions and 3 deletions.
12 changes: 12 additions & 0 deletions StaemAuthFramework/APIEndpoints.cs
Original file line number Diff line number Diff line change
@@ -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");
}
}
288 changes: 288 additions & 0 deletions StaemAuthFramework/AuthenticatorLinker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
using Newtonsoft.Json;
using System;
using System.Collections.Specialized;
using System.Net;
using System.Security.Cryptography;
using System.Threading;

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;
private bool confirmationEmailSent = false;

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 (confirmationEmailSent) {
if (!_checkEmailConfirmation()) {
return LinkResult.GeneralFailure;
}
} else if (!_addPhoneNumber()) {
return LinkResult.GeneralFailure;
} else {
confirmationEmailSent = true;
return LinkResult.MustConfirmEmail;
}
}

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)
{
return LinkResult.GeneralFailure;
}

if (addAuthenticatorResponse.Response.Status == 29)
{
return LinkResult.AuthenticatorPresent;
}

if (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)
{
//The act of checking the SMS code is necessary for Steam to finalize adding the phone number to the account.
//Of course, we only want to check it if we're adding a phone number in the first place...

if (!String.IsNullOrEmpty(this.PhoneNumber) && !this._checkSMSCode(smsCode))
{
return FinalizeResult.BadSMSCode;
}

var postData = new NameValueCollection();
postData.Add("steamid", _session.SteamID.ToString());
postData.Add("access_token", _session.OAuthToken);
postData.Add("activation_code", smsCode);
int tries = 0;
while (tries <= 30)
{
postData.Set("authenticator_code", LinkedAccount.GenerateSteamGuardCode());
postData.Set("authenticator_time", TimeAligner.GetSteamTime().ToString());

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)
{
tries++;
continue;
}

this.LinkedAccount.FullyEnrolled = true;
return FinalizeResult.Success;
}

return FinalizeResult.GeneralFailure;
}

private bool _checkSMSCode(string smsCode)
{
var postData = new NameValueCollection();
postData.Add("op", "check_sms_code");
postData.Add("arg", smsCode);
postData.Add("checkfortos", "0");
postData.Add("skipvoip", "1");
postData.Add("sessionid", _session.SessionID);

string response = SteamWeb.Request(APIEndpoints.COMMUNITY_BASE + "/steamguard/phoneajax", "POST", postData, _cookies);
if (response == null) return false;

var addPhoneNumberResponse = JsonConvert.DeserializeObject<AddPhoneResponse>(response);

if (!addPhoneNumberResponse.Success)
{
Thread.Sleep(3500); //It seems that Steam needs a few seconds to finalize the phone number on the account.
return _hasPhoneAttached();
}

return true;
}

private bool _addPhoneNumber()
{
var postData = new NameValueCollection();
postData.Add("op", "add_phone_number");
postData.Add("arg", PhoneNumber);
postData.Add("sessionid", _session.SessionID);

string response = SteamWeb.Request(APIEndpoints.COMMUNITY_BASE + "/steamguard/phoneajax", "POST", postData, _cookies);
if (response == null) return false;

var addPhoneNumberResponse = JsonConvert.DeserializeObject<AddPhoneResponse>(response);
return addPhoneNumberResponse.Success;
}

private bool _checkEmailConfirmation() {
var postData = new NameValueCollection();
postData.Add("op", "email_confirmation");
postData.Add("arg", "");
postData.Add("sessionid", _session.SessionID);

string response = SteamWeb.Request(APIEndpoints.COMMUNITY_BASE + "/steamguard/phoneajax", "POST", postData, _cookies);
if (response == null) return false;

var emailConfirmationResponse = JsonConvert.DeserializeObject<AddPhoneResponse>(response);
return emailConfirmationResponse.Success;
}

private bool _hasPhoneAttached() {
var postData = new NameValueCollection();
postData.Add("op", "has_phone");
postData.Add("arg", "null");
postData.Add("sessionid", _session.SessionID);

string response = SteamWeb.Request(APIEndpoints.COMMUNITY_BASE + "/steamguard/phoneajax", "POST", 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
MustConfirmEmail, //User need to click link from confirmation email
AwaitingFinalization, //Must provide an SMS code
GeneralFailure, //General failure (really now!)
AuthenticatorPresent
}

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; }
}

public static string GenerateDeviceID()
{
return "android:" + Guid.NewGuid().ToString();
}
}
}
69 changes: 69 additions & 0 deletions StaemAuthFramework/Confirmation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SteamAuth
{
public class Confirmation
{
/// <summary>
/// The ID of this confirmation
/// </summary>
public ulong ID;

/// <summary>
/// The unique key used to act upon this confirmation.
/// </summary>
public ulong Key;

/// <summary>
/// The value of the data-type HTML attribute returned for this contribution.
/// </summary>
public int IntType;

/// <summary>
/// Represents either the Trade Offer ID or market transaction ID that caused this confirmation to be created.
/// </summary>
public ulong Creator;

/// <summary>
/// The type of this confirmation.
/// </summary>
public ConfirmationType ConfType;

public Confirmation(ulong id, ulong key, int type, ulong creator)
{
this.ID = id;
this.Key = key;
this.IntType = type;
this.Creator = creator;

//Do a switch simply because we're not 100% certain of all the possible types.
switch (type)
{
case 1:
this.ConfType = ConfirmationType.GenericConfirmation;
break;
case 2:
this.ConfType = ConfirmationType.Trade;
break;
case 3:
this.ConfType = ConfirmationType.MarketSellTransaction;
break;
default:
this.ConfType = ConfirmationType.Unknown;
break;
}
}

public enum ConfirmationType
{
GenericConfirmation,
Trade,
MarketSellTransaction,
Unknown
}
}
}
Loading

0 comments on commit 9bcb4aa

Please sign in to comment.