avatar/Assets/VRCSDK/Dependencies/VRChat/Editor/ControlPanel/VRCSdkControlPanelAccount.cs
2022-09-27 20:47:45 -07:00

552 lines
19 KiB
C#

using UnityEngine;
using UnityEditor;
using VRC.Core;
using System.Text.RegularExpressions;
using VRC.SDKBase.Editor;
public partial class VRCSdkControlPanel : EditorWindow
{
static bool isInitialized = false;
static string clientInstallPath;
static bool signingIn = false;
static string error = null;
public static bool FutureProofPublishEnabled { get { return UnityEditor.EditorPrefs.GetBool("futureProofPublish", DefaultFutureProofPublishEnabled); } }
//public static bool DefaultFutureProofPublishEnabled { get { return !SDKClientUtilities.IsInternalSDK(); } }
public static bool DefaultFutureProofPublishEnabled { get { return false; } }
static string storedUsername
{
get
{
return null;
}
set
{
EditorPrefs.DeleteKey("sdk#username");
}
}
static string storedPassword
{
get
{
return null;
}
set
{
EditorPrefs.DeleteKey("sdk#password");
}
}
static string username { get; set; } = null;
static string password { get; set; } = null;
static ApiServerEnvironment serverEnvironment
{
get
{
ApiServerEnvironment env = ApiServerEnvironment.Release;
try
{
env = (ApiServerEnvironment)System.Enum.Parse(typeof(ApiServerEnvironment), UnityEditor.EditorPrefs.GetString("VRC_ApiServerEnvironment", env.ToString()));
}
catch (System.Exception e)
{
Debug.LogError("Invalid server environment name - " + e.ToString());
}
return env;
}
set
{
UnityEditor.EditorPrefs.SetString("VRC_ApiServerEnvironment", value.ToString());
API.SetApiUrlFromEnvironment(value);
}
}
private void OnEnableAccount()
{
entered2faCodeIsInvalid = false;
warningIconGraphic = Resources.Load("2FAIcons/SDK_Warning_Triangle_icon") as Texture2D;
}
public static void RefreshApiUrlSetting()
{
// this forces the static api url variable to be reset from the server environment set in editor prefs.
// needed because the static variable states get cleared when entering / exiting play mode
ApiServerEnvironment env = serverEnvironment;
serverEnvironment = env;
}
public static void InitAccount()
{
if (isInitialized)
return;
if (!APIUser.IsLoggedIn && ApiCredentials.Load())
APIUser.InitialFetchCurrentUser((c) => AnalyticsSDK.LoggedInUserChanged(c.Model as APIUser), null);
clientInstallPath = SDKClientUtilities.GetSavedVRCInstallPath();
if (string.IsNullOrEmpty(clientInstallPath))
clientInstallPath = SDKClientUtilities.LoadRegistryVRCInstallPath();
signingIn = false;
isInitialized = true;
ClearContent();
}
public static bool OnShowStatus()
{
API.SetOnlineMode(true);
SignIn(false);
EditorGUILayout.BeginVertical();
if (APIUser.IsLoggedIn)
{
OnCreatorStatusGUI();
}
EditorGUILayout.EndVertical();
return APIUser.IsLoggedIn;
}
static bool OnAccountGUI()
{
const int ACCOUNT_LOGIN_BORDER_SPACING = 20;
EditorGUILayout.Separator();
EditorGUILayout.Separator();
EditorGUILayout.Separator();
EditorGUILayout.Separator();
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUILayout.Space(ACCOUNT_LOGIN_BORDER_SPACING);
GUILayout.BeginVertical("Account", "window", GUILayout.Height(150), GUILayout.Width(340));
if (signingIn)
{
EditorGUILayout.LabelField("Signing in as " + username + ".");
}
else if (APIUser.IsLoggedIn)
{
if (Status != "Connected")
EditorGUILayout.LabelField(Status);
OnCreatorStatusGUI();
GUILayout.BeginHorizontal();
GUILayout.Label("");
if (GUILayout.Button("Logout"))
{
storedUsername = username = null;
storedPassword = password = null;
VRC.Tools.ClearCookies();
APIUser.Logout();
ClearContent();
}
GUILayout.EndHorizontal();
}
else
{
InitAccount();
ApiServerEnvironment newEnv = ApiServerEnvironment.Release;
if (VRCSettings.DisplayAdvancedSettings)
newEnv = (ApiServerEnvironment)EditorGUILayout.EnumPopup("Use API", serverEnvironment);
if (serverEnvironment != newEnv)
serverEnvironment = newEnv;
username = EditorGUILayout.TextField("Username/Email", username);
password = EditorGUILayout.PasswordField("Password", password);
if (GUILayout.Button("Sign In"))
SignIn(true);
if (GUILayout.Button("Sign up"))
Application.OpenURL("https://vrchat.com/register");
}
if (showTwoFactorAuthenticationEntry)
{
OnTwoFactorAuthenticationGUI();
}
GUILayout.EndVertical();
GUILayout.Space(ACCOUNT_LOGIN_BORDER_SPACING);
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
return !signingIn;
}
static void OnCreatorStatusGUI()
{
EditorGUILayout.LabelField("Logged in as:", APIUser.CurrentUser.displayName);
//if (SDKClientUtilities.IsInternalSDK())
// EditorGUILayout.LabelField("Developer Status: ", APIUser.CurrentUser.developerType.ToString());
EditorGUILayout.BeginHorizontal();
EditorGUILayout.BeginVertical();
EditorGUILayout.LabelField("World Creator Status: ", APIUser.CurrentUser.canPublishWorlds ? "Allowed to publish worlds" : "Not yet allowed to publish worlds");
EditorGUILayout.LabelField("Avatar Creator Status: ", APIUser.CurrentUser.canPublishAvatars ? "Allowed to publish avatars" : "Not yet allowed to publish avatars");
EditorGUILayout.EndVertical();
if (!APIUser.CurrentUser.canPublishAllContent)
{
if (GUILayout.Button("More Info..."))
{
VRCSdkControlPanel.ShowContentPublishPermissionsDialog();
}
}
EditorGUILayout.EndHorizontal();
}
void ShowAccount()
{
if (VRC.Core.ConfigManager.RemoteConfig.IsInitialized())
{
if (VRC.Core.ConfigManager.RemoteConfig.HasKey("sdkUnityVersion"))
{
string sdkUnityVersion = VRC.Core.ConfigManager.RemoteConfig.GetString("sdkUnityVersion");
if (string.IsNullOrEmpty(sdkUnityVersion))
EditorGUILayout.LabelField("Could not fetch remote config.");
else if (Application.unityVersion != sdkUnityVersion)
{
EditorGUILayout.LabelField("Unity Version", EditorStyles.boldLabel);
EditorGUILayout.LabelField("Wrong Unity version. Please use " + sdkUnityVersion);
}
}
}
else
{
VRC.Core.API.SetOnlineMode(true, "vrchat");
VRC.Core.ConfigManager.RemoteConfig.Init();
}
OnAccountGUI();
}
private const string TWO_FACTOR_AUTHENTICATION_HELP_URL = "https://docs.vrchat.com/docs/setup-2fa";
private const string ENTER_2FA_CODE_TITLE_STRING = "Enter a numeric code from your authenticator app (or one of your saved recovery codes).";
private const string ENTER_2FA_CODE_LABEL_STRING = "Code:";
private const string CHECKING_2FA_CODE_STRING = "Checking code...";
private const string ENTER_2FA_CODE_INVALID_CODE_STRING = "Invalid Code";
private const string ENTER_2FA_CODE_VERIFY_STRING = "Verify";
private const string ENTER_2FA_CODE_CANCEL_STRING = "Cancel";
private const string ENTER_2FA_CODE_HELP_STRING = "Help";
private const int WARNING_ICON_SIZE = 60;
private const int WARNING_FONT_HEIGHT = 18;
static private Texture2D warningIconGraphic;
static bool entered2faCodeIsInvalid;
static bool authorizationCodeWasVerified;
static private int previousAuthenticationCodeLength = 0;
static bool checkingCode;
static string authenticationCode = "";
static System.Action onAuthenticationVerifiedAction;
static bool _showTwoFactorAuthenticationEntry = false;
static bool showTwoFactorAuthenticationEntry
{
get
{
return _showTwoFactorAuthenticationEntry;
}
set
{
_showTwoFactorAuthenticationEntry = value;
authenticationCode = "";
if (!_showTwoFactorAuthenticationEntry && !authorizationCodeWasVerified)
Logout();
}
}
static bool IsValidAuthenticationCodeFormat()
{
bool isValid2faAuthenticationCode = false;
if (!string.IsNullOrEmpty(authenticationCode))
{
// check if the input is a valid 6-digit numberic code (ignoring spaces)
Regex rx = new Regex(@"^(\s*\d\s*){6}$", RegexOptions.Compiled);
MatchCollection matches6DigitCode = rx.Matches(authenticationCode);
isValid2faAuthenticationCode = (matches6DigitCode.Count == 1);
}
return isValid2faAuthenticationCode;
}
static bool IsValidRecoveryCodeFormat()
{
bool isValid2faRecoveryCode = false;
if (!string.IsNullOrEmpty(authenticationCode))
{
// check if the input is a valid 8-digit alpha-numberic code (format xxxx-xxxx) "-" is optional & ignore any spaces
// OTP codes also exclude the letters i,l,o and the digit 1 to prevent any confusion
Regex rx = new Regex(@"^(\s*[a-hj-km-np-zA-HJ-KM-NP-Z02-9]\s*){4}-?(\s*[a-hj-km-np-zA-HJ-KM-NP-Z02-9]\s*){4}$", RegexOptions.Compiled);
MatchCollection matchesRecoveryCode = rx.Matches(authenticationCode);
isValid2faRecoveryCode = (matchesRecoveryCode.Count == 1);
}
return isValid2faRecoveryCode;
}
static void OnTwoFactorAuthenticationGUI()
{
const int ENTER_2FA_CODE_BORDER_SIZE = 20;
const int ENTER_2FA_CODE_BUTTON_WIDTH = 260;
const int ENTER_2FA_CODE_VERIFY_BUTTON_WIDTH = ENTER_2FA_CODE_BUTTON_WIDTH / 2;
const int ENTER_2FA_CODE_ENTRY_REGION_WIDTH = 130;
const int ENTER_2FA_CODE_MIN_WINDOW_WIDTH = ENTER_2FA_CODE_VERIFY_BUTTON_WIDTH + ENTER_2FA_CODE_ENTRY_REGION_WIDTH + (ENTER_2FA_CODE_BORDER_SIZE * 3);
bool isValidAuthenticationCode = IsValidAuthenticationCodeFormat();
// Invalid code text
if (entered2faCodeIsInvalid)
{
GUIStyle s = new GUIStyle(EditorStyles.label);
s.alignment = TextAnchor.UpperLeft;
s.normal.textColor = Color.red;
s.fontSize = WARNING_FONT_HEIGHT;
s.padding = new RectOffset(0, 0, (WARNING_ICON_SIZE - s.fontSize) / 2, 0);
s.fixedHeight = WARNING_ICON_SIZE;
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
EditorGUILayout.BeginVertical();
GUILayout.FlexibleSpace();
EditorGUILayout.BeginHorizontal();
var textDimensions = s.CalcSize(new GUIContent(ENTER_2FA_CODE_INVALID_CODE_STRING));
GUILayout.Label(new GUIContent(warningIconGraphic), GUILayout.Width(WARNING_ICON_SIZE), GUILayout.Height(WARNING_ICON_SIZE));
EditorGUILayout.LabelField(ENTER_2FA_CODE_INVALID_CODE_STRING, s, GUILayout.Width(textDimensions.x));
EditorGUILayout.EndHorizontal();
GUILayout.FlexibleSpace();
EditorGUILayout.EndVertical();
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
}
else if (checkingCode)
{
// Display checking code message
EditorGUILayout.BeginVertical();
GUILayout.FlexibleSpace();
EditorGUILayout.BeginHorizontal();
GUIStyle s = new GUIStyle(EditorStyles.label);
s.alignment = TextAnchor.MiddleCenter;
s.fixedHeight = WARNING_ICON_SIZE;
EditorGUILayout.LabelField(CHECKING_2FA_CODE_STRING, s, GUILayout.Height(WARNING_ICON_SIZE));
EditorGUILayout.EndHorizontal();
GUILayout.FlexibleSpace();
EditorGUILayout.EndVertical();
}
else
{
EditorGUILayout.BeginHorizontal();
GUILayout.Space(ENTER_2FA_CODE_BORDER_SIZE);
GUILayout.FlexibleSpace();
GUIStyle titleStyle = new GUIStyle(EditorStyles.label);
titleStyle.alignment = TextAnchor.MiddleCenter;
titleStyle.wordWrap = true;
EditorGUILayout.LabelField(ENTER_2FA_CODE_TITLE_STRING, titleStyle, GUILayout.Width(ENTER_2FA_CODE_MIN_WINDOW_WIDTH - (2 * ENTER_2FA_CODE_BORDER_SIZE)), GUILayout.Height(WARNING_ICON_SIZE), GUILayout.ExpandHeight(true));
GUILayout.FlexibleSpace();
GUILayout.Space(ENTER_2FA_CODE_BORDER_SIZE);
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.BeginHorizontal();
GUILayout.Space(ENTER_2FA_CODE_BORDER_SIZE);
GUILayout.FlexibleSpace();
Vector2 size = EditorStyles.boldLabel.CalcSize(new GUIContent(ENTER_2FA_CODE_LABEL_STRING));
EditorGUILayout.LabelField(ENTER_2FA_CODE_LABEL_STRING, EditorStyles.boldLabel, GUILayout.MaxWidth(size.x));
authenticationCode = EditorGUILayout.TextField(authenticationCode);
// Verify 2FA code button
if (GUILayout.Button(ENTER_2FA_CODE_VERIFY_STRING, GUILayout.Width(ENTER_2FA_CODE_VERIFY_BUTTON_WIDTH)))
{
checkingCode = true;
APIUser.VerifyTwoFactorAuthCode(authenticationCode, isValidAuthenticationCode ? API2FA.TIME_BASED_ONE_TIME_PASSWORD_AUTHENTICATION : API2FA.ONE_TIME_PASSWORD_AUTHENTICATION, username, password,
delegate
{
// valid 2FA code submitted
entered2faCodeIsInvalid = false;
authorizationCodeWasVerified = true;
checkingCode = false;
showTwoFactorAuthenticationEntry = false;
if (null != onAuthenticationVerifiedAction)
onAuthenticationVerifiedAction();
},
delegate
{
entered2faCodeIsInvalid = true;
checkingCode = false;
}
);
}
GUILayout.FlexibleSpace();
GUILayout.Space(ENTER_2FA_CODE_BORDER_SIZE);
EditorGUILayout.EndHorizontal();
GUILayout.FlexibleSpace();
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
// after user has entered an invalid code causing the invalid code message to be displayed,
// edit the code will change it's length meaning it is invalid format, so we can clear the invalid code setting until they resubmit
if (previousAuthenticationCodeLength != authenticationCode.Length)
{
previousAuthenticationCodeLength = authenticationCode.Length;
entered2faCodeIsInvalid = false;
}
GUI.enabled = true;
GUILayout.FlexibleSpace();
GUILayout.Space(ENTER_2FA_CODE_BORDER_SIZE);
EditorGUILayout.EndHorizontal();
GUILayout.FlexibleSpace();
// Two-Factor Authentication Help button
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button(ENTER_2FA_CODE_HELP_STRING))
{
Application.OpenURL(TWO_FACTOR_AUTHENTICATION_HELP_URL);
}
EditorGUILayout.EndHorizontal();
// Cancel button
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button(ENTER_2FA_CODE_CANCEL_STRING))
{
showTwoFactorAuthenticationEntry = false;
Logout();
}
EditorGUILayout.EndHorizontal();
}
private static string Status
{
get
{
if (!APIUser.IsLoggedIn)
return error == null ? "Please log in." : "Error in authenticating: " + error;
if (signingIn)
return "Logging in.";
else
{
if( serverEnvironment == ApiServerEnvironment.Dev )
return "Connected to " + serverEnvironment.ToString();
return "Connected";
}
}
}
private static void OnAuthenticationCompleted()
{
AttemptLogin();
}
private static void AttemptLogin()
{
APIUser.Login(username, password,
delegate (ApiModelContainer<APIUser> c)
{
APIUser user = c.Model as APIUser;
if (c.Cookies.ContainsKey("twoFactorAuth"))
ApiCredentials.Set(user.username, username, "vrchat", c.Cookies["auth"], c.Cookies["twoFactorAuth"]);
else if (c.Cookies.ContainsKey("auth"))
ApiCredentials.Set(user.username, username, "vrchat", c.Cookies["auth"]);
else
ApiCredentials.SetHumanName(user.username);
signingIn = false;
error = null;
storedUsername = null;
storedPassword = null;
AnalyticsSDK.LoggedInUserChanged(user);
if (!APIUser.CurrentUser.canPublishAllContent)
{
if (UnityEditor.SessionState.GetString("HasShownContentPublishPermissionsDialogForUser", "") != user.id)
{
UnityEditor.SessionState.SetString("HasShownContentPublishPermissionsDialogForUser", user.id);
VRCSdkControlPanel.ShowContentPublishPermissionsDialog();
}
}
},
delegate (ApiModelContainer<APIUser> c)
{
Logout();
error = c.Error;
VRC.Core.Logger.Log("Error logging in: " + error);
},
delegate (ApiModelContainer<API2FA> c)
{
if (c.Cookies.ContainsKey("auth"))
ApiCredentials.Set(username, username, "vrchat", c.Cookies["auth"]);
showTwoFactorAuthenticationEntry = true;
onAuthenticationVerifiedAction = OnAuthenticationCompleted;
}
);
}
private static object syncObject = new object();
private static void SignIn(bool explicitAttempt)
{
lock (syncObject)
{
if (signingIn
|| APIUser.IsLoggedIn
|| (!explicitAttempt && string.IsNullOrEmpty(storedUsername)))
return;
signingIn = true;
}
InitAccount();
AttemptLogin();
}
public static void Logout()
{
signingIn = false;
storedUsername = null;
storedPassword = null;
VRC.Tools.ClearCookies();
APIUser.Logout();
}
private void AccountDestroy()
{
signingIn = false;
isInitialized = false;
}
}