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 //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 (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")); Console.Write(ConsoleIO.BasicIO ? Translations.Get("mcc.login_basic_io") + "\n" : Translations.Get("mcc.login"));
Settings.Login = Console.ReadLine(); 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(); RequestPassword();
} }
startupargs = args; startupargs = args;
InitializeClient(); InitializeClient();
@ -209,7 +214,6 @@ namespace MinecraftClient
SessionCache.Store(Settings.Login.ToLower(), session); SessionCache.Store(Settings.Login.ToLower(), session);
} }
} }
} }
if (result == ProtocolHandler.LoginResult.Success) if (result == ProtocolHandler.LoginResult.Success)
@ -315,6 +319,7 @@ namespace MinecraftClient
case ProtocolHandler.LoginResult.NotPremium: failureReason = "error.login.premium"; break; case ProtocolHandler.LoginResult.NotPremium: failureReason = "error.login.premium"; break;
case ProtocolHandler.LoginResult.OtherError: failureReason = "error.login.network"; break; case ProtocolHandler.LoginResult.OtherError: failureReason = "error.login.network"; break;
case ProtocolHandler.LoginResult.SSLError: failureReason = "error.login.ssl"; 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; default: failureReason = "error.login.unknown"; break;
} }
failureMessage += Translations.Get(failureReason); 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 invalidAccount = new Regex("Sign in to", RegexOptions.IgnoreCase);
private Regex twoFA = new Regex("Help us protect your account", RegexOptions.IgnoreCase); private Regex twoFA = new Regex("Help us protect your account", RegexOptions.IgnoreCase);
public string SignInUrl { get { return authorize; } }
/// <summary> /// <summary>
/// Pre-authentication /// Pre-authentication
/// </summary> /// </summary>
@ -114,7 +116,7 @@ namespace MinecraftClient.Protocol
if (twoFA.IsMatch(response.Body)) if (twoFA.IsMatch(response.Body))
{ {
// TODO: Handle 2FA // 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)) else if (invalidAccount.IsMatch(response.Body))
{ {

View file

@ -330,7 +330,7 @@ namespace MinecraftClient.Protocol
return Protocol18Forge.ServerForceForge(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 }; public enum AccountType { Mojang, Microsoft };
/// <summary> /// <summary>
@ -344,7 +344,10 @@ namespace MinecraftClient.Protocol
{ {
if (type == AccountType.Microsoft) 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) else if (type == AccountType.Mojang)
{ {
@ -353,6 +356,13 @@ namespace MinecraftClient.Protocol
else throw new InvalidOperationException("Account type must be Mojang or Microsoft"); 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) private static LoginResult MojangLogin(string user, string pass, out SessionToken session)
{ {
session = new SessionToken() { ClientID = Guid.NewGuid().ToString().Replace("-", "") }; 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("-", "") }; session = new SessionToken() { ClientID = Guid.NewGuid().ToString().Replace("-", "") };
var ms = new XboxLive(); var ms = new XboxLive();
@ -440,7 +544,6 @@ namespace MinecraftClient.Protocol
try try
{ {
var msaResponse = ms.UserLogin(email, password, ms.PreAuth());
var xblResponse = ms.XblAuthenticate(msaResponse); var xblResponse = ms.XblAuthenticate(msaResponse);
var xsts = ms.XSTSAuthenticate(xblResponse); // Might throw even password correct var xsts = ms.XSTSAuthenticate(xblResponse); // Might throw even password correct

View file

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

View file

@ -66,6 +66,7 @@ error.login.network=Network error.
error.login.ssl=SSL Error. error.login.ssl=SSL Error.
error.login.unknown=Unknown 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.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.login_failed=Failed to login to this server.
error.join=Failed to join this server. error.join=Failed to join this server.
error.connect=Failed to connect to this IP. error.connect=Failed to connect to this IP.

View file

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