1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-21 09:45:06 +08:00

Add APIAccess flow for 2fa

This commit is contained in:
Dean Herbert 2023-11-16 16:38:07 +09:00
parent c4e461ba44
commit 0e4244a692
No known key found for this signature in database
4 changed files with 50 additions and 5 deletions

View File

@ -48,6 +48,8 @@ namespace osu.Game.Online.API
public string ProvidedUsername { get; private set; } public string ProvidedUsername { get; private set; }
public string SecondFactorCode { get; private set; }
private string password; private string password;
public IBindable<APIUser> LocalUser => localUser; public IBindable<APIUser> LocalUser => localUser;
@ -183,6 +185,7 @@ namespace osu.Game.Online.API
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This method takes control of <see cref="state"/> and transitions from <see cref="APIState.Connecting"/> to either /// This method takes control of <see cref="state"/> and transitions from <see cref="APIState.Connecting"/> to either
/// - <see cref="APIState.RequiresSecondFactorAuth"/> (pending 2fa)
/// - <see cref="APIState.Online"/> (successful connection) /// - <see cref="APIState.Online"/> (successful connection)
/// - <see cref="APIState.Failing"/> (failed connection but retrying) /// - <see cref="APIState.Failing"/> (failed connection but retrying)
/// - <see cref="APIState.Offline"/> (failed and can't retry, clear credentials and require user interaction) /// - <see cref="APIState.Offline"/> (failed and can't retry, clear credentials and require user interaction)
@ -190,8 +193,6 @@ namespace osu.Game.Online.API
/// <returns>Whether the connection attempt was successful.</returns> /// <returns>Whether the connection attempt was successful.</returns>
private void attemptConnect() private void attemptConnect()
{ {
state.Value = APIState.Connecting;
if (localUser.IsDefault) if (localUser.IsDefault)
{ {
// Show a placeholder user if saved credentials are available. // Show a placeholder user if saved credentials are available.
@ -208,11 +209,14 @@ namespace osu.Game.Online.API
if (!authentication.HasValidAccessToken) if (!authentication.HasValidAccessToken)
{ {
state.Value = APIState.Connecting;
LastLoginError = null; LastLoginError = null;
try try
{ {
authentication.AuthenticateWithLogin(ProvidedUsername, password); authentication.AuthenticateWithLogin(ProvidedUsername, password);
state.Value = APIState.RequiresSecondFactorAuth;
return;
} }
catch (Exception e) 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(); var userReq = new GetUserRequest();
userReq.Failure += ex => userReq.Failure += ex =>
{ {
@ -307,6 +333,13 @@ namespace osu.Game.Online.API
this.password = password; 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) => public IHubClientConnector GetHubConnector(string clientName, string endpoint, bool preferMessagePack) =>
new HubClientConnector(clientName, endpoint, this, versionHash, preferMessagePack); new HubClientConnector(clientName, endpoint, this, versionHash, preferMessagePack);
@ -493,6 +526,7 @@ namespace osu.Game.Online.API
public void Logout() public void Logout()
{ {
password = null; password = null;
SecondFactorCode = null;
authentication.Clear(); authentication.Clear();
// Scheduled prior to state change such that the state changed event is invoked with the correct user and their friends present // 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
/// <summary> /// <summary>
/// Waiting on second factor authentication. /// Waiting on second factor authentication.
/// </summary> /// </summary>
RequiresAuthentication, RequiresSecondFactorAuth,
/// <summary> /// <summary>
/// We are in the process of (re-)connecting. /// We are in the process of (re-)connecting.

View File

@ -118,13 +118,18 @@ namespace osu.Game.Online.API
if (requiresTwoFactor) if (requiresTwoFactor)
{ {
state.Value = APIState.RequiresAuthentication; state.Value = APIState.RequiresSecondFactorAuth;
requiresTwoFactor = false; requiresTwoFactor = false;
} }
else else
state.Value = APIState.Online; state.Value = APIState.Online;
} }
public void AuthenticateSecondFactor(string code)
{
state.Value = APIState.Online;
}
public void Logout() public void Logout()
{ {
state.Value = APIState.Offline; state.Value = APIState.Offline;

View File

@ -106,6 +106,12 @@ namespace osu.Game.Online.API
/// <param name="password">The user's password.</param> /// <param name="password">The user's password.</param>
void Login(string username, string password); void Login(string username, string password);
/// <summary>
/// Provide a second-factor authentication code for authentication.
/// </summary>
/// <param name="code">The 2FA code.</param>
void AuthenticateSecondFactor(string code);
/// <summary> /// <summary>
/// Log out the current user. /// Log out the current user.
/// </summary> /// </summary>

View File

@ -81,7 +81,7 @@ namespace osu.Game.Overlays.Login
}; };
break; break;
case APIState.RequiresAuthentication: case APIState.RequiresSecondFactorAuth:
Child = form = new AccountVerificationForm(); Child = form = new AccountVerificationForm();
break; break;