Implement browser sign-in method (#1447)

* Implement browser sign-in method

* Handle empty link

* Improve

* Handle user cancel login
This commit is contained in:
ReinforceZwei 2021-02-06 09:29:14 +08:00 committed by GitHub
parent 424f514be2
commit 71eb1dca17
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 124 additions and 8 deletions

View file

@ -136,16 +136,21 @@ namespace MinecraftClient
}
//Asking the user to type in missing data such as Username and Password
bool useBrowser = Settings.AccountType == ProtocolHandler.AccountType.Microsoft && Settings.LoginMethod == "browser";
if (Settings.Login == "")
{
if (useBrowser)
ConsoleIO.WriteLine("Press Enter to skip session cache checking and continue sign-in with browser");
Console.Write(ConsoleIO.BasicIO ? Translations.Get("mcc.login_basic_io") + "\n" : Translations.Get("mcc.login"));
Settings.Login = Console.ReadLine();
}
if (Settings.Password == "" && (Settings.SessionCaching == CacheType.None || !SessionCache.Contains(Settings.Login.ToLower())))
if (Settings.Password == ""
&& (Settings.SessionCaching == CacheType.None || !SessionCache.Contains(Settings.Login.ToLower()))
&& !useBrowser)
{
RequestPassword();
}
startupargs = args;
InitializeClient();
@ -209,7 +214,6 @@ namespace MinecraftClient
SessionCache.Store(Settings.Login.ToLower(), session);
}
}
}
if (result == ProtocolHandler.LoginResult.Success)
@ -315,6 +319,7 @@ namespace MinecraftClient
case ProtocolHandler.LoginResult.NotPremium: failureReason = "error.login.premium"; break;
case ProtocolHandler.LoginResult.OtherError: failureReason = "error.login.network"; break;
case ProtocolHandler.LoginResult.SSLError: failureReason = "error.login.ssl"; break;
case ProtocolHandler.LoginResult.UserCancel: failureReason = "error.login.cancel"; break;
default: failureReason = "error.login.unknown"; break;
}
failureMessage += Translations.Get(failureReason);

View file

@ -21,6 +21,8 @@ namespace MinecraftClient.Protocol
private Regex invalidAccount = new Regex("Sign in to", RegexOptions.IgnoreCase);
private Regex twoFA = new Regex("Help us protect your account", RegexOptions.IgnoreCase);
public string SignInUrl { get { return authorize; } }
/// <summary>
/// Pre-authentication
/// </summary>
@ -114,7 +116,7 @@ namespace MinecraftClient.Protocol
if (twoFA.IsMatch(response.Body))
{
// TODO: Handle 2FA
throw new Exception("2FA enabled but not supported yet. Try to disable it in Microsoft account settings");
throw new Exception("2FA enabled but not supported yet. Use browser sign-in method or try to disable 2FA in Microsoft account settings");
}
else if (invalidAccount.IsMatch(response.Body))
{

View file

@ -330,7 +330,7 @@ namespace MinecraftClient.Protocol
return Protocol18Forge.ServerForceForge(protocol);
}
public enum LoginResult { OtherError, ServiceUnavailable, SSLError, Success, WrongPassword, AccountMigrated, NotPremium, LoginRequired, InvalidToken, InvalidResponse, NullError };
public enum LoginResult { OtherError, ServiceUnavailable, SSLError, Success, WrongPassword, AccountMigrated, NotPremium, LoginRequired, InvalidToken, InvalidResponse, NullError, UserCancel };
public enum AccountType { Mojang, Microsoft };
/// <summary>
@ -344,7 +344,10 @@ namespace MinecraftClient.Protocol
{
if (type == AccountType.Microsoft)
{
return MicrosoftLogin(user, pass, out session);
if (Settings.LoginMethod == "mcc")
return MicrosoftMCCLogin(user, pass, out session);
else
return MicrosoftBrowserLogin(out session);
}
else if (type == AccountType.Mojang)
{
@ -353,6 +356,13 @@ namespace MinecraftClient.Protocol
else throw new InvalidOperationException("Account type must be Mojang or Microsoft");
}
/// <summary>
/// Login using Mojang account. Will be outdated after account migration
/// </summary>
/// <param name="user"></param>
/// <param name="pass"></param>
/// <param name="session"></param>
/// <returns></returns>
private static LoginResult MojangLogin(string user, string pass, out SessionToken session)
{
session = new SessionToken() { ClientID = Guid.NewGuid().ToString().Replace("-", "") };
@ -432,7 +442,101 @@ namespace MinecraftClient.Protocol
}
}
private static LoginResult MicrosoftLogin(string email, string password, out SessionToken session)
/// <summary>
/// Sign-in to Microsoft Account without using browser. Only works if 2FA is disabled.
/// Might not work well in some rare cases.
/// </summary>
/// <param name="email"></param>
/// <param name="password"></param>
/// <param name="session"></param>
/// <returns></returns>
private static LoginResult MicrosoftMCCLogin(string email, string password, out SessionToken session)
{
var ms = new XboxLive();
try
{
var msaResponse = ms.UserLogin(email, password, ms.PreAuth());
return MicrosoftLogin(msaResponse, out session);
}
catch (Exception e)
{
session = new SessionToken() { ClientID = Guid.NewGuid().ToString().Replace("-", "") };
ConsoleIO.WriteLineFormatted("§cMicrosoft authenticate failed: " + e.Message);
if (Settings.DebugMessages)
{
ConsoleIO.WriteLineFormatted("§c" + e.StackTrace);
}
return LoginResult.WrongPassword; // Might not always be wrong password
}
}
/// <summary>
/// Sign-in to Microsoft Account by asking user to open sign-in page using browser.
/// </summary>
/// <remarks>
/// The downside is this require user to copy and paste lengthy content from and to console.
/// Sign-in page: 218 chars
/// Response URL: around 1500 chars
/// </remarks>
/// <param name="session"></param>
/// <returns></returns>
public static LoginResult MicrosoftBrowserLogin(out SessionToken session)
{
var ms = new XboxLive();
string[] askOpenLink =
{
"Copy the following link to your browser and login to your Microsoft Account",
">>>>>>>>>>>>>>>>>>>>>>",
"",
ms.SignInUrl,
"",
"<<<<<<<<<<<<<<<<<<<<<<",
"NOTICE: Once successfully logged in, you will see a blank page in your web browser.",
"Copy the contents of your browser's address bar and paste it below to complete the login process.",
};
ConsoleIO.WriteLine(string.Join("\n", askOpenLink));
string[] parts = { };
while (true)
{
string link = ConsoleIO.ReadLine();
if (string.IsNullOrEmpty(link))
{
session = new SessionToken();
return LoginResult.UserCancel;
}
parts = link.Split('#');
if (parts.Length < 2)
{
ConsoleIO.WriteLine("Invalid link. Please try again.");
continue;
}
else break;
}
string hash = parts[1];
var dict = Request.ParseQueryString(hash);
var msaResponse = new XboxLive.UserLoginResponse()
{
AccessToken = dict["access_token"],
RefreshToken = dict["refresh_token"],
ExpiresIn = int.Parse(dict["expires_in"])
};
try
{
return MicrosoftLogin(msaResponse, out session);
}
catch (Exception e)
{
session = new SessionToken() { ClientID = Guid.NewGuid().ToString().Replace("-", "") };
ConsoleIO.WriteLineFormatted("§cMicrosoft authenticate failed: " + e.Message);
if (Settings.DebugMessages)
{
ConsoleIO.WriteLineFormatted("§c" + e.StackTrace);
}
return LoginResult.WrongPassword; // Might not always be wrong password
}
}
private static LoginResult MicrosoftLogin(XboxLive.UserLoginResponse msaResponse, out SessionToken session)
{
session = new SessionToken() { ClientID = Guid.NewGuid().ToString().Replace("-", "") };
var ms = new XboxLive();
@ -440,7 +544,6 @@ namespace MinecraftClient.Protocol
try
{
var msaResponse = ms.UserLogin(email, password, ms.PreAuth());
var xblResponse = ms.XblAuthenticate(msaResponse);
var xsts = ms.XSTSAuthenticate(xblResponse); // Might throw even password correct

View file

@ -10,6 +10,7 @@ login=
password=
serverip=
type=mojang # Account type. mojang or microsoft
method=mcc # Microsoft Account sign-in method. mcc OR browser
# Advanced settings

View file

@ -66,6 +66,7 @@ error.login.network=Network error.
error.login.ssl=SSL Error.
error.login.unknown=Unknown Error.
error.login.ssl_help=§8It appears that you are using Mono to run this program.\nThe first time, you have to import HTTPS certificates using:\nmozroots --import --ask-remove
error.login.cancel=User cancelled.
error.login_failed=Failed to login to this server.
error.join=Failed to join this server.
error.connect=Failed to connect to this IP.

View file

@ -26,6 +26,7 @@ namespace MinecraftClient
public static string Username = "";
public static string Password = "";
public static ProtocolHandler.AccountType AccountType = ProtocolHandler.AccountType.Mojang;
public static string LoginMethod = "mcc";
public static string ServerIP = "";
public static ushort ServerPort = 25565;
public static string ServerVersion = "";
@ -277,6 +278,9 @@ namespace MinecraftClient
case "type": AccountType = argValue == "mojang"
? ProtocolHandler.AccountType.Mojang
: ProtocolHandler.AccountType.Microsoft; break;
case "method": LoginMethod = argValue.ToLower() == "browser"
? "browser"
: "mcc"; break;
case "serverip": if (!SetServerIP(argValue)) serverAlias = argValue; ; break;
case "singlecommand": SingleCommand = argValue; break;
case "language": Language = argValue; break;