mirror of
https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies.git
synced 2026-07-25 06:14:31 +00:00
305 lines
11 KiB
C#
305 lines
11 KiB
C#
using JetBrains.Annotations;
|
|
using Newtonsoft.Json.Linq;
|
|
using SteamLib.Authentication;
|
|
using SteamLib.Core;
|
|
using SteamLib.Core.Enums;
|
|
using SteamLib.Core.Interfaces;
|
|
using SteamLib.Core.StatusCodes;
|
|
using SteamLib.Exceptions;
|
|
using SteamLib.Exceptions.Mobile;
|
|
using SteamLib.ProtoCore;
|
|
using SteamLib.ProtoCore.Enums;
|
|
using SteamLib.ProtoCore.Exceptions;
|
|
using SteamLib.ProtoCore.Services;
|
|
using SteamLib.SteamMobile;
|
|
using SteamLib.SteamMobile.AuthenticatorLinker;
|
|
|
|
namespace SteamLib.Api.Mobile;
|
|
|
|
[PublicAPI]
|
|
public static class SteamAuthenticatorLinkerApi
|
|
{
|
|
private const string PHONE_REQ = SteamConstants.STEAM_COMMUNITY + "steamguard/phoneajax";
|
|
|
|
#region Global
|
|
|
|
public static Task<AccountPhoneStatus_Response> HasPhone(HttpClient client, string accessToken)
|
|
{
|
|
const string uri = SteamConstants.STEAM_API + "IPhoneService/AccountPhoneStatus/v1";
|
|
|
|
var reqUri = AddAccessToken(uri, accessToken);
|
|
return client.PostProto<AccountPhoneStatus_Response>(reqUri, request: null);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="client"></param>
|
|
/// <param name="data"></param>
|
|
/// <param name="deviceId"></param>
|
|
/// <returns></returns>
|
|
/// <exception cref="AuthenticatorLinkerException"></exception>
|
|
/// <exception cref="EResultException"></exception>
|
|
public static async Task<AddAuthenticator_Response> LinkRequest(HttpClient client, ISessionData data, string deviceId)
|
|
{
|
|
const string uri = SteamConstants.STEAM_API + "ITwoFactorService/AddAuthenticator/v1";
|
|
data.EnsureValidated();
|
|
var reqUri = AddAccessToken(uri, data.GetAccessToken());
|
|
var req = new AddAuthenticator_Request
|
|
{
|
|
SteamId = (ulong)data.SteamId.Steam64.Id,
|
|
AuthenticatorType = 1,
|
|
DeviceIdentifier = deviceId,
|
|
Version = 2
|
|
};
|
|
|
|
var resp = await client.PostProtoMsg<AddAuthenticator_Response>(reqUri, req);
|
|
if (resp is {Result: EResult.InvalidState, ResponseMsg.Status: 2})
|
|
{
|
|
throw new AuthenticatorLinkerException(AuthenticatorLinkerError.InvalidStateWithStatus2);
|
|
}
|
|
|
|
return resp.GetResponseEnsureSuccess();
|
|
}
|
|
|
|
public static async Task<LinkResult> FinalizeLink(HttpClient client, string confirmationCode, ISessionData data,
|
|
byte[] sharedSecret, bool validateSmsCode)
|
|
{
|
|
const string uri = SteamConstants.STEAM_API + "ITwoFactorService/FinalizeAddAuthenticator/v1";
|
|
data.EnsureValidated();
|
|
var reqUri = AddAccessToken(uri, data.GetAccessToken());
|
|
var time = await TimeAligner.GetSteamTimeAsync();
|
|
if (validateSmsCode)
|
|
{
|
|
var validateSmsReq = new FinalizeAddAuthenticator_Request
|
|
{
|
|
SteamId = data.SteamId.Steam64.ToUlong(),
|
|
AuthenticatorTime = (ulong) time,
|
|
ConfirmationCode = confirmationCode,
|
|
ValidateConfirmationCode = true
|
|
};
|
|
|
|
var validateResp = await client.PostProto<FinalizeAddAuthenticator_Response>(reqUri, validateSmsReq);
|
|
if (validateResp.Success == false || validateResp.Status != 2)
|
|
{
|
|
if (validateResp.Status == 89)
|
|
throw new AuthenticatorLinkerException(AuthenticatorLinkerError.BadConfirmationCode);
|
|
throw new AuthenticatorLinkerException("Can't accept sms code. Status: " + validateResp.Status);
|
|
}
|
|
}
|
|
|
|
var tries = 0;
|
|
|
|
|
|
while (tries < 30)
|
|
{
|
|
tries++;
|
|
time = await TimeAligner.GetSteamTimeAsync();
|
|
var code = SteamGuardCodeGenerator.GenerateCode(sharedSecret, time);
|
|
|
|
var req = new FinalizeAddAuthenticator_Request
|
|
{
|
|
SteamId = data.SteamId.Steam64.ToUlong(),
|
|
AuthenticatorCode = code,
|
|
AuthenticatorTime = (ulong)time,
|
|
ConfirmationCode = confirmationCode,
|
|
};
|
|
|
|
|
|
var resp = await client.PostProto<FinalizeAddAuthenticator_Response>(reqUri, req);
|
|
|
|
if (resp.Success && resp.WantMore == false)
|
|
{
|
|
return new LinkResult();
|
|
}
|
|
|
|
if (resp.Status == 89)
|
|
{
|
|
return new LinkResult(LinkError.BadConfirmationCode);
|
|
}
|
|
|
|
|
|
if (resp.Status == 88 && tries >= 30)
|
|
{
|
|
return new LinkResult(LinkError.UnableToGenerateCorrectCodes);
|
|
}
|
|
}
|
|
return new LinkResult(LinkError.GeneralFailure);
|
|
}
|
|
|
|
public static async Task<bool> IsValidPhoneNumber(HttpClient client, long phoneNumber, string sessionId)
|
|
{
|
|
return await CheckPhoneNumber(client, phoneNumber, sessionId) == CheckPhoneResult.Valid;
|
|
}
|
|
public static async Task<CheckPhoneResult> CheckPhoneNumber(HttpClient client, long phoneNumber, string sessionId)
|
|
{
|
|
var phone = '+' + phoneNumber.ToString();
|
|
var data = new Dictionary<string, string>
|
|
{
|
|
{"phoneNumber", phone},
|
|
{"sessionID", sessionId}
|
|
};
|
|
|
|
var reqContent = new FormUrlEncodedContent(data);
|
|
var req = new HttpRequestMessage(HttpMethod.Post, SteamConstants.STEAM_STORE + "phone/validate");
|
|
req.Content = reqContent;
|
|
req.Headers.Referrer = new Uri("https://store.steampowered.com/phone/add");
|
|
var resp = await client.SendAsync(req);
|
|
var content = await resp.EnsureSuccessStatusCode().Content.ReadAsStringAsync();
|
|
var j = JObject.Parse(content);
|
|
|
|
if (j["success"]!.Value<bool>() == false)
|
|
return CheckPhoneResult.GeneralFailure;
|
|
|
|
if (j["is_voip"]!.Value<bool>())
|
|
return CheckPhoneResult.VoIp;
|
|
|
|
return j["is_valid"]!.Value<bool>() ? CheckPhoneResult.Valid : CheckPhoneResult.NotValid;
|
|
}
|
|
|
|
public static async Task<bool> AttachPhone(HttpClient client, long phoneNumber, string accessToken)
|
|
{
|
|
const string uri = SteamConstants.STEAM_API + "IPhoneService/SetAccountPhoneNumber/v1";
|
|
var phone = '+' + phoneNumber.ToString();
|
|
var req = new SetAccountPhoneNumber_Request
|
|
{
|
|
PhoneNumber = phone
|
|
};
|
|
|
|
var reqUri = AddAccessToken(uri, accessToken);
|
|
var resp =
|
|
await client.PostProtoMsg<SetAccountPhoneNumber_Response>(reqUri, req);
|
|
|
|
return resp.Result == EResult.Pending;
|
|
}
|
|
public static async Task<bool> CheckEmailConfirmation(HttpClient client, string accessToken)
|
|
{
|
|
const string uri = SteamConstants.STEAM_API + "IPhoneService/IsAccountWaitingForEmailConfirmation/v1";
|
|
|
|
var reqUri = AddAccessToken(uri, accessToken);
|
|
|
|
var i = 0;
|
|
while (i < 5)
|
|
{
|
|
i++;
|
|
var resp = await client.PostProto<IsAccountWaitingForEmailConfirmation_Response>(reqUri, new EmptyMessage()); ;
|
|
|
|
if (resp.IsWaiting == false) return true;
|
|
|
|
await Task.Delay(resp.SecondsToWait * 1000);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
public static Task<EResult> SendSmsCode(HttpClient client, string accessToken)
|
|
{
|
|
const string uri = SteamConstants.STEAM_API + "IPhoneService/SendPhoneVerificationCode/v1";
|
|
|
|
var reqUri = AddAccessToken(uri, accessToken);
|
|
return client.PostProto(reqUri, new SendPhoneVerificationCode_Request());
|
|
}
|
|
public static async Task<bool> CheckSmsCode(HttpClient client, int smsCode, string sessionId)
|
|
{
|
|
var data = new Dictionary<string, string>
|
|
{
|
|
{"op", "check_sms_code"},
|
|
{"arg", smsCode.ToString()},
|
|
{"checkfortos", "0"},
|
|
{"skipvoip", "1"},
|
|
{"sessionid", sessionId}
|
|
};
|
|
|
|
var content = new FormUrlEncodedContent(data);
|
|
var resp = await client.PostAsync(PHONE_REQ, content);
|
|
var respContent = await resp.EnsureSuccessStatusCode().Content.ReadAsStringAsync();
|
|
var success = SteamStatusCode.Translate<SteamStatusCode>(respContent, out _).Equals(SteamStatusCode.Ok);
|
|
|
|
if (success) return true;
|
|
|
|
await Task.Delay(5000);
|
|
var hasPhone = await HasPhone(client, sessionId);
|
|
return hasPhone.HasPhone;
|
|
}
|
|
private static FormUrlEncodedContent CreateAjaxContent(string op, string arg, string sessionId)
|
|
{
|
|
var data = new Dictionary<string, string>
|
|
{
|
|
{"op",op},
|
|
{"arg", arg},
|
|
{"sessionid", sessionId}
|
|
};
|
|
return new FormUrlEncodedContent(data);
|
|
}
|
|
|
|
public static string GenerateDeviceId() => "android:" + Guid.NewGuid();
|
|
private static string AddAccessToken(string uri, string accessToken) => uri + "?access_token=" + accessToken;
|
|
|
|
#endregion
|
|
|
|
#region ForLinker
|
|
|
|
internal static Task<AddAuthenticator_Response> LinkRequest(this SteamAuthenticatorLinker linker)
|
|
{
|
|
if (linker.SessionData == null)
|
|
throw new InvalidOperationException("SessionData is null");
|
|
|
|
return LinkRequest(linker.Client, linker.SessionData, linker.DeviceId ??= GenerateDeviceId());
|
|
}
|
|
|
|
internal static Task<bool> IsValidPhoneNumber(this SteamAuthenticatorLinker linker, long phoneNumber)
|
|
{
|
|
if (linker.SessionData == null)
|
|
throw new InvalidOperationException("SessionData is null");
|
|
|
|
return IsValidPhoneNumber(linker.Client, phoneNumber, linker.SessionData.SessionId);
|
|
}
|
|
|
|
internal static Task<AccountPhoneStatus_Response> HasPhone(this SteamAuthenticatorLinker linker)
|
|
{
|
|
if (linker.SessionData == null)
|
|
throw new InvalidOperationException("SessionData is null");
|
|
|
|
return HasPhone(linker.Client, linker.SessionData.GetAccessToken());
|
|
}
|
|
internal static Task<bool> AttachPhone(this SteamAuthenticatorLinker linker, long phoneNumber)
|
|
{
|
|
if (linker.SessionData == null)
|
|
throw new InvalidOperationException("SessionData is null");
|
|
|
|
return AttachPhone(linker.Client, phoneNumber, linker.SessionData.GetAccessToken());
|
|
}
|
|
internal static Task<bool> CheckEmailConfirmation(this SteamAuthenticatorLinker linker)
|
|
{
|
|
if (linker.SessionData == null)
|
|
throw new InvalidOperationException("SessionData is null");
|
|
|
|
return CheckEmailConfirmation(linker.Client, linker.SessionData.GetAccessToken());
|
|
}
|
|
|
|
internal static Task<EResult> SendSmsCode(this SteamAuthenticatorLinker linker)
|
|
{
|
|
if (linker.SessionData == null)
|
|
throw new InvalidOperationException("SessionData is null");
|
|
|
|
return SendSmsCode(linker.Client, linker.SessionData.GetAccessToken());
|
|
}
|
|
internal static Task<bool> CheckSmsCode(this SteamAuthenticatorLinker linker, int smsCode)
|
|
{
|
|
if (linker.SessionData == null)
|
|
throw new InvalidOperationException("SessionData is null");
|
|
|
|
return CheckSmsCode(linker.Client, smsCode, linker.SessionData.SessionId);
|
|
}
|
|
|
|
internal static Task<LinkResult> FinalizeLink(this SteamAuthenticatorLinker linker, string confirmationCode, byte[] sharedSecret, bool validateSmsCode)
|
|
{
|
|
if (linker.SessionData == null)
|
|
throw new InvalidOperationException("SessionData is null");
|
|
|
|
return FinalizeLink(linker.Client, confirmationCode, linker.SessionData, sharedSecret, validateSmsCode);
|
|
}
|
|
|
|
|
|
#endregion
|
|
private static string GetAccessToken(this ISessionData s) => s.GetToken(SteamDomain.Community)?.Token ?? throw new SessionInvalidException("Access token was null. MobileEndpoints requires valid AccessToken");
|
|
} |