diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index a3ceb14a40..f9dd97a8f2 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -48,6 +48,8 @@ namespace osu.Game.Online.API public string ProvidedUsername { get; private set; } + public string SecondFactorCode { get; private set; } + private string password; public IBindable LocalUser => localUser; @@ -183,6 +185,7 @@ namespace osu.Game.Online.API /// /// /// This method takes control of and transitions from to either + /// - (pending 2fa) /// - (successful connection) /// - (failed connection but retrying) /// - (failed and can't retry, clear credentials and require user interaction) @@ -190,8 +193,6 @@ namespace osu.Game.Online.API /// Whether the connection attempt was successful. private void attemptConnect() { - state.Value = APIState.Connecting; - if (localUser.IsDefault) { // Show a placeholder user if saved credentials are available. @@ -208,11 +209,14 @@ namespace osu.Game.Online.API if (!authentication.HasValidAccessToken) { + state.Value = APIState.Connecting; LastLoginError = null; try { authentication.AuthenticateWithLogin(ProvidedUsername, password); + state.Value = APIState.RequiresSecondFactorAuth; + return; } catch (Exception e) { @@ -225,6 +229,28 @@ namespace osu.Game.Online.API } } + if (state.Value == APIState.RequiresSecondFactorAuth) + { + if (string.IsNullOrEmpty(SecondFactorCode)) + return; + + state.Value = APIState.Connecting; + LastLoginError = null; + + // TODO: use code to ensure second factor authentication completed. + Thread.Sleep(1000); + bool success = SecondFactorCode == "00000000"; + SecondFactorCode = null; + + if (!success) + { + state.Value = APIState.RequiresSecondFactorAuth; + LastLoginError = new InvalidOperationException("Second factor auth failed"); + SecondFactorCode = null; + return; + } + } + var userReq = new GetUserRequest(); userReq.Failure += ex => { @@ -307,6 +333,13 @@ namespace osu.Game.Online.API this.password = password; } + public void AuthenticateSecondFactor(string code) + { + Debug.Assert(State.Value == APIState.RequiresSecondFactorAuth); + + SecondFactorCode = code; + } + public IHubClientConnector GetHubConnector(string clientName, string endpoint, bool preferMessagePack) => new HubClientConnector(clientName, endpoint, this, versionHash, preferMessagePack); @@ -493,6 +526,7 @@ namespace osu.Game.Online.API public void Logout() { password = null; + SecondFactorCode = null; authentication.Clear(); // Scheduled prior to state change such that the state changed event is invoked with the correct user and their friends present @@ -543,7 +577,7 @@ namespace osu.Game.Online.API /// /// Waiting on second factor authentication. /// - RequiresAuthentication, + RequiresSecondFactorAuth, /// /// We are in the process of (re-)connecting. diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 4cf2152193..bd833d39dd 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -118,13 +118,18 @@ namespace osu.Game.Online.API if (requiresTwoFactor) { - state.Value = APIState.RequiresAuthentication; + state.Value = APIState.RequiresSecondFactorAuth; requiresTwoFactor = false; } else state.Value = APIState.Online; } + public void AuthenticateSecondFactor(string code) + { + state.Value = APIState.Online; + } + public void Logout() { state.Value = APIState.Offline; diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index a1d7006c8c..5f99ad2f4d 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -106,6 +106,12 @@ namespace osu.Game.Online.API /// The user's password. void Login(string username, string password); + /// + /// Provide a second-factor authentication code for authentication. + /// + /// The 2FA code. + void AuthenticateSecondFactor(string code); + /// /// Log out the current user. /// diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index 59b9c6ab9f..f896d03231 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -81,7 +81,7 @@ namespace osu.Game.Overlays.Login }; break; - case APIState.RequiresAuthentication: + case APIState.RequiresSecondFactorAuth: Child = form = new AccountVerificationForm(); break;