multiple xr toolkit package
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

VRCSdkControlPanelAccount.cs 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. using UnityEngine;
  2. using UnityEditor;
  3. using VRC.Core;
  4. using System.Text.RegularExpressions;
  5. public partial class VRCSdkControlPanel : EditorWindow
  6. {
  7. static bool isInitialized = false;
  8. static string clientInstallPath;
  9. static bool signingIn = false;
  10. static string error = null;
  11. public static bool FutureProofPublishEnabled { get { return UnityEditor.EditorPrefs.GetBool("futureProofPublish", DefaultFutureProofPublishEnabled); } }
  12. public static bool DefaultFutureProofPublishEnabled { get { return !SDKClientUtilities.IsInternalSDK(); } }
  13. static string storedUsername
  14. {
  15. get
  16. {
  17. if (EditorPrefs.HasKey("sdk#username"))
  18. return EditorPrefs.GetString("sdk#username");
  19. return null;
  20. }
  21. set
  22. {
  23. EditorPrefs.SetString("sdk#username", value);
  24. if (string.IsNullOrEmpty(value))
  25. EditorPrefs.DeleteKey("sdk#username");
  26. }
  27. }
  28. static string storedPassword
  29. {
  30. get
  31. {
  32. if (EditorPrefs.HasKey("sdk#password"))
  33. return EditorPrefs.GetString("sdk#password");
  34. return null;
  35. }
  36. set
  37. {
  38. EditorPrefs.SetString("sdk#password", value);
  39. if (string.IsNullOrEmpty(value))
  40. EditorPrefs.DeleteKey("sdk#password");
  41. }
  42. }
  43. static string _username = null;
  44. static string _password = null;
  45. static string username
  46. {
  47. get
  48. {
  49. if (!string.IsNullOrEmpty(_username))
  50. return _username;
  51. else
  52. _username = storedUsername;
  53. return _username;
  54. }
  55. set
  56. {
  57. _username = value;
  58. }
  59. }
  60. static string password
  61. {
  62. get
  63. {
  64. if (!string.IsNullOrEmpty(_password))
  65. return _password;
  66. else
  67. _password = storedPassword;
  68. return _password;
  69. }
  70. set
  71. {
  72. _password = value;
  73. }
  74. }
  75. static ApiServerEnvironment serverEnvironment
  76. {
  77. get
  78. {
  79. ApiServerEnvironment env = ApiServerEnvironment.Release;
  80. try
  81. {
  82. env = (ApiServerEnvironment)System.Enum.Parse(typeof(ApiServerEnvironment), UnityEditor.EditorPrefs.GetString("VRC_ApiServerEnvironment", env.ToString()));
  83. }
  84. catch (System.Exception e)
  85. {
  86. Debug.LogError("Invalid server environment name - " + e.ToString());
  87. }
  88. return env;
  89. }
  90. set
  91. {
  92. UnityEditor.EditorPrefs.SetString("VRC_ApiServerEnvironment", value.ToString());
  93. API.SetApiUrlFromEnvironment(value);
  94. }
  95. }
  96. private void OnEnableAccount()
  97. {
  98. entered2faCodeIsInvalid = false;
  99. warningIconGraphic = Resources.Load("2FAIcons/SDK_Warning_Triangle_icon") as Texture2D;
  100. }
  101. public static void RefreshApiUrlSetting()
  102. {
  103. // this forces the static api url variable to be reset from the server environment set in editor prefs.
  104. // needed because the static variable states get cleared when entering / exiting play mode
  105. ApiServerEnvironment env = serverEnvironment;
  106. serverEnvironment = env;
  107. }
  108. public static void InitAccount()
  109. {
  110. if (isInitialized)
  111. return;
  112. if (!APIUser.IsLoggedInWithCredentials && ApiCredentials.Load())
  113. APIUser.FetchCurrentUser((c) => AnalyticsSDK.LoggedInUserChanged(c.Model as APIUser), null);
  114. clientInstallPath = SDKClientUtilities.GetSavedVRCInstallPath();
  115. if (string.IsNullOrEmpty(clientInstallPath))
  116. clientInstallPath = SDKClientUtilities.LoadRegistryVRCInstallPath();
  117. signingIn = false;
  118. isInitialized = true;
  119. ClearContent();
  120. }
  121. public static bool OnShowStatus()
  122. {
  123. API.SetOnlineMode(true);
  124. SignIn(false);
  125. EditorGUILayout.BeginVertical();
  126. if (APIUser.IsLoggedInWithCredentials)
  127. {
  128. OnCreatorStatusGUI();
  129. }
  130. EditorGUILayout.EndVertical();
  131. return APIUser.IsLoggedInWithCredentials;
  132. }
  133. static bool OnAccountGUI()
  134. {
  135. const int ACCOUNT_LOGIN_BORDER_SPACING = 20;
  136. EditorGUILayout.Separator();
  137. EditorGUILayout.Separator();
  138. EditorGUILayout.Separator();
  139. EditorGUILayout.Separator();
  140. GUILayout.BeginHorizontal();
  141. GUILayout.FlexibleSpace();
  142. GUILayout.Space(ACCOUNT_LOGIN_BORDER_SPACING);
  143. GUILayout.BeginVertical("Account", "window", GUILayout.Height(150), GUILayout.Width(340));
  144. if (signingIn)
  145. {
  146. EditorGUILayout.LabelField("Signing in as " + username + ".");
  147. }
  148. else if (APIUser.IsLoggedInWithCredentials)
  149. {
  150. if (Status != "Connected")
  151. EditorGUILayout.LabelField(Status);
  152. OnCreatorStatusGUI();
  153. GUILayout.BeginHorizontal();
  154. GUILayout.Label("");
  155. if (GUILayout.Button("Logout"))
  156. {
  157. storedUsername = username = null;
  158. storedPassword = password = null;
  159. VRC.Tools.ClearCookies();
  160. APIUser.Logout();
  161. ClearContent();
  162. }
  163. GUILayout.EndHorizontal();
  164. }
  165. else
  166. {
  167. InitAccount();
  168. ApiServerEnvironment newEnv = ApiServerEnvironment.Release;
  169. if (VRCSettings.Get().DisplayAdvancedSettings)
  170. newEnv = (ApiServerEnvironment)EditorGUILayout.EnumPopup("Use API", serverEnvironment);
  171. if (serverEnvironment != newEnv)
  172. serverEnvironment = newEnv;
  173. username = EditorGUILayout.TextField("Username", username);
  174. password = EditorGUILayout.PasswordField("Password", password);
  175. if (GUILayout.Button("Sign In"))
  176. SignIn(true);
  177. if (GUILayout.Button("Sign up"))
  178. Application.OpenURL("http://vrchat.com/register");
  179. }
  180. if (showTwoFactorAuthenticationEntry)
  181. {
  182. OnTwoFactorAuthenticationGUI();
  183. }
  184. GUILayout.EndVertical();
  185. GUILayout.Space(ACCOUNT_LOGIN_BORDER_SPACING);
  186. GUILayout.FlexibleSpace();
  187. GUILayout.EndHorizontal();
  188. return !signingIn;
  189. }
  190. static void OnCreatorStatusGUI()
  191. {
  192. EditorGUILayout.LabelField("Logged in as:", APIUser.CurrentUser.displayName);
  193. if (SDKClientUtilities.IsInternalSDK())
  194. EditorGUILayout.LabelField("Developer Status: ", APIUser.CurrentUser.developerType.ToString());
  195. EditorGUILayout.BeginHorizontal();
  196. EditorGUILayout.BeginVertical();
  197. EditorGUILayout.LabelField("World Creator Status: ", APIUser.CurrentUser.canPublishWorlds ? "Allowed to publish worlds" : "Not yet allowed to publish worlds");
  198. EditorGUILayout.LabelField("Avatar Creator Status: ", APIUser.CurrentUser.canPublishAvatars ? "Allowed to publish avatars" : "Not yet allowed to publish avatars");
  199. EditorGUILayout.EndVertical();
  200. if (!APIUser.CurrentUser.canPublishAllContent)
  201. {
  202. if (GUILayout.Button("More Info..."))
  203. {
  204. VRCSdkControlPanel.ShowContentPublishPermissionsDialog();
  205. }
  206. }
  207. EditorGUILayout.EndHorizontal();
  208. }
  209. void ShowAccount()
  210. {
  211. if (VRC.Core.RemoteConfig.IsInitialized())
  212. {
  213. if (VRC.Core.RemoteConfig.HasKey("sdkUnityVersion"))
  214. {
  215. string sdkUnityVersion = VRC.Core.RemoteConfig.GetString("sdkUnityVersion");
  216. if (string.IsNullOrEmpty(sdkUnityVersion))
  217. EditorGUILayout.LabelField("Could not fetch remote config.");
  218. else if (Application.unityVersion != sdkUnityVersion)
  219. {
  220. EditorGUILayout.LabelField("Unity Version", EditorStyles.boldLabel);
  221. EditorGUILayout.LabelField("Wrong Unity version. Please use " + sdkUnityVersion);
  222. }
  223. }
  224. }
  225. else
  226. {
  227. VRC.Core.API.SetOnlineMode(true, "vrchat");
  228. VRC.Core.RemoteConfig.Init();
  229. }
  230. OnAccountGUI();
  231. }
  232. private const string TWO_FACTOR_AUTHENTICATION_HELP_URL = "https://docs.vrchat.com/docs/setup-2fa";
  233. private const string ENTER_2FA_CODE_TITLE_STRING = "Enter a numeric code from your authenticator app (or one of your saved recovery codes).";
  234. private const string ENTER_2FA_CODE_LABEL_STRING = "Code:";
  235. private const string CHECKING_2FA_CODE_STRING = "Checking code...";
  236. private const string ENTER_2FA_CODE_INVALID_CODE_STRING = "Invalid Code";
  237. private const string ENTER_2FA_CODE_VERIFY_STRING = "Verify";
  238. private const string ENTER_2FA_CODE_CANCEL_STRING = "Cancel";
  239. private const string ENTER_2FA_CODE_HELP_STRING = "Help";
  240. private const int WARNING_ICON_SIZE = 60;
  241. private const int WARNING_FONT_HEIGHT = 18;
  242. static private Texture2D warningIconGraphic;
  243. static bool entered2faCodeIsInvalid;
  244. static bool authorizationCodeWasVerified;
  245. static private int previousAuthenticationCodeLength = 0;
  246. static bool checkingCode;
  247. static string authenticationCode = "";
  248. static System.Action onAuthenticationVerifiedAction;
  249. static bool _showTwoFactorAuthenticationEntry = false;
  250. static bool showTwoFactorAuthenticationEntry
  251. {
  252. get
  253. {
  254. return _showTwoFactorAuthenticationEntry;
  255. }
  256. set
  257. {
  258. _showTwoFactorAuthenticationEntry = value;
  259. authenticationCode = "";
  260. if (!_showTwoFactorAuthenticationEntry && !authorizationCodeWasVerified)
  261. Logout();
  262. }
  263. }
  264. static bool IsValidAuthenticationCodeFormat()
  265. {
  266. bool isValid2faAuthenticationCode = false;
  267. if (!string.IsNullOrEmpty(authenticationCode))
  268. {
  269. // check if the input is a valid 6-digit numberic code (ignoring spaces)
  270. Regex rx = new Regex(@"^(\s*\d\s*){6}$", RegexOptions.Compiled);
  271. MatchCollection matches6DigitCode = rx.Matches(authenticationCode);
  272. isValid2faAuthenticationCode = (matches6DigitCode.Count == 1);
  273. }
  274. return isValid2faAuthenticationCode;
  275. }
  276. static bool IsValidRecoveryCodeFormat()
  277. {
  278. bool isValid2faRecoveryCode = false;
  279. if (!string.IsNullOrEmpty(authenticationCode))
  280. {
  281. // check if the input is a valid 8-digit alpha-numberic code (format xxxx-xxxx) "-" is optional & ignore any spaces
  282. // OTP codes also exclude the letters i,l,o and the digit 1 to prevent any confusion
  283. 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);
  284. MatchCollection matchesRecoveryCode = rx.Matches(authenticationCode);
  285. isValid2faRecoveryCode = (matchesRecoveryCode.Count == 1);
  286. }
  287. return isValid2faRecoveryCode;
  288. }
  289. static void OnTwoFactorAuthenticationGUI()
  290. {
  291. const int ENTER_2FA_CODE_BORDER_SIZE = 20;
  292. const int ENTER_2FA_CODE_BUTTON_WIDTH = 260;
  293. const int ENTER_2FA_CODE_VERIFY_BUTTON_WIDTH = ENTER_2FA_CODE_BUTTON_WIDTH / 2;
  294. const int ENTER_2FA_CODE_ENTRY_REGION_WIDTH = 130;
  295. 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);
  296. bool isValidAuthenticationCode = IsValidAuthenticationCodeFormat();
  297. bool isValidRecoveryCode = IsValidRecoveryCodeFormat();
  298. // Invalid code text
  299. if (entered2faCodeIsInvalid)
  300. {
  301. GUIStyle s = new GUIStyle(EditorStyles.label);
  302. s.alignment = TextAnchor.UpperLeft;
  303. s.normal.textColor = Color.red;
  304. s.fontSize = WARNING_FONT_HEIGHT;
  305. s.padding = new RectOffset(0, 0, (WARNING_ICON_SIZE - s.fontSize) / 2, 0);
  306. s.fixedHeight = WARNING_ICON_SIZE;
  307. EditorGUILayout.BeginHorizontal();
  308. GUILayout.FlexibleSpace();
  309. EditorGUILayout.BeginVertical();
  310. GUILayout.FlexibleSpace();
  311. EditorGUILayout.BeginHorizontal();
  312. var textDimensions = s.CalcSize(new GUIContent(ENTER_2FA_CODE_INVALID_CODE_STRING));
  313. GUILayout.Label(new GUIContent(warningIconGraphic), GUILayout.Width(WARNING_ICON_SIZE), GUILayout.Height(WARNING_ICON_SIZE));
  314. EditorGUILayout.LabelField(ENTER_2FA_CODE_INVALID_CODE_STRING, s, GUILayout.Width(textDimensions.x));
  315. EditorGUILayout.EndHorizontal();
  316. GUILayout.FlexibleSpace();
  317. EditorGUILayout.EndVertical();
  318. GUILayout.FlexibleSpace();
  319. EditorGUILayout.EndHorizontal();
  320. }
  321. else if (checkingCode)
  322. {
  323. // Display checking code message
  324. EditorGUILayout.BeginVertical();
  325. GUILayout.FlexibleSpace();
  326. EditorGUILayout.BeginHorizontal();
  327. GUIStyle s = new GUIStyle(EditorStyles.label);
  328. s.alignment = TextAnchor.MiddleCenter;
  329. s.fixedHeight = WARNING_ICON_SIZE;
  330. EditorGUILayout.LabelField(CHECKING_2FA_CODE_STRING, s, GUILayout.Height(WARNING_ICON_SIZE));
  331. EditorGUILayout.EndHorizontal();
  332. GUILayout.FlexibleSpace();
  333. EditorGUILayout.EndVertical();
  334. }
  335. else
  336. {
  337. EditorGUILayout.BeginHorizontal();
  338. GUILayout.Space(ENTER_2FA_CODE_BORDER_SIZE);
  339. GUILayout.FlexibleSpace();
  340. GUIStyle titleStyle = new GUIStyle(EditorStyles.label);
  341. titleStyle.alignment = TextAnchor.MiddleCenter;
  342. titleStyle.wordWrap = true;
  343. 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));
  344. GUILayout.FlexibleSpace();
  345. GUILayout.Space(ENTER_2FA_CODE_BORDER_SIZE);
  346. EditorGUILayout.EndHorizontal();
  347. }
  348. EditorGUILayout.BeginHorizontal();
  349. GUILayout.Space(ENTER_2FA_CODE_BORDER_SIZE);
  350. GUILayout.FlexibleSpace();
  351. Vector2 size = EditorStyles.boldLabel.CalcSize(new GUIContent(ENTER_2FA_CODE_LABEL_STRING));
  352. EditorGUILayout.LabelField(ENTER_2FA_CODE_LABEL_STRING, EditorStyles.boldLabel, GUILayout.MaxWidth(size.x));
  353. authenticationCode = EditorGUILayout.TextField(authenticationCode);
  354. // Verify 2FA code button
  355. if (GUILayout.Button(ENTER_2FA_CODE_VERIFY_STRING, GUILayout.Width(ENTER_2FA_CODE_VERIFY_BUTTON_WIDTH)))
  356. {
  357. checkingCode = true;
  358. APIUser.VerifyTwoFactorAuthCode(authenticationCode, isValidAuthenticationCode ? API2FA.TIME_BASED_ONE_TIME_PASSWORD_AUTHENTICATION : API2FA.ONE_TIME_PASSWORD_AUTHENTICATION, username, password,
  359. delegate
  360. {
  361. // valid 2FA code submitted
  362. entered2faCodeIsInvalid = false;
  363. authorizationCodeWasVerified = true;
  364. checkingCode = false;
  365. showTwoFactorAuthenticationEntry = false;
  366. if (null != onAuthenticationVerifiedAction)
  367. onAuthenticationVerifiedAction();
  368. },
  369. delegate
  370. {
  371. entered2faCodeIsInvalid = true;
  372. checkingCode = false;
  373. }
  374. );
  375. }
  376. GUILayout.FlexibleSpace();
  377. GUILayout.Space(ENTER_2FA_CODE_BORDER_SIZE);
  378. EditorGUILayout.EndHorizontal();
  379. GUILayout.FlexibleSpace();
  380. EditorGUILayout.BeginHorizontal();
  381. GUILayout.FlexibleSpace();
  382. // after user has entered an invalid code causing the invalid code message to be displayed,
  383. // edit the code will change it's length meaning it is invalid format, so we can clear the invalid code setting until they resubmit
  384. if (previousAuthenticationCodeLength != authenticationCode.Length)
  385. {
  386. previousAuthenticationCodeLength = authenticationCode.Length;
  387. entered2faCodeIsInvalid = false;
  388. }
  389. GUI.enabled = true;
  390. GUILayout.FlexibleSpace();
  391. GUILayout.Space(ENTER_2FA_CODE_BORDER_SIZE);
  392. EditorGUILayout.EndHorizontal();
  393. GUILayout.FlexibleSpace();
  394. // Two-Factor Authentication Help button
  395. EditorGUILayout.BeginHorizontal();
  396. if (GUILayout.Button(ENTER_2FA_CODE_HELP_STRING))
  397. {
  398. Application.OpenURL(TWO_FACTOR_AUTHENTICATION_HELP_URL);
  399. }
  400. EditorGUILayout.EndHorizontal();
  401. // Cancel button
  402. EditorGUILayout.BeginHorizontal();
  403. if (GUILayout.Button(ENTER_2FA_CODE_CANCEL_STRING))
  404. {
  405. showTwoFactorAuthenticationEntry = false;
  406. Logout();
  407. }
  408. EditorGUILayout.EndHorizontal();
  409. }
  410. private static string Status
  411. {
  412. get
  413. {
  414. if (!APIUser.IsLoggedInWithCredentials)
  415. return error == null ? "Please log in." : "Error in authenticating: " + error;
  416. if (signingIn)
  417. return "Logging in.";
  418. else
  419. {
  420. if( serverEnvironment == ApiServerEnvironment.Dev )
  421. return "Connected to " + serverEnvironment.ToString();
  422. return "Connected";
  423. }
  424. }
  425. }
  426. private static void OnAuthenticationCompleted()
  427. {
  428. AttemptLogin();
  429. }
  430. private static void AttemptLogin()
  431. {
  432. APIUser.Login(username, password,
  433. delegate (ApiModelContainer<APIUser> c)
  434. {
  435. APIUser user = c.Model as APIUser;
  436. if (c.Cookies.ContainsKey("auth"))
  437. ApiCredentials.Set(user.username, username, "vrchat", c.Cookies["auth"]);
  438. else
  439. ApiCredentials.SetHumanName(user.username);
  440. signingIn = false;
  441. error = null;
  442. storedUsername = username;
  443. storedPassword = password;
  444. AnalyticsSDK.LoggedInUserChanged(user);
  445. if (!APIUser.CurrentUser.canPublishAllContent)
  446. {
  447. if (UnityEditor.SessionState.GetString("HasShownContentPublishPermissionsDialogForUser", "") != user.id)
  448. {
  449. UnityEditor.SessionState.SetString("HasShownContentPublishPermissionsDialogForUser", user.id);
  450. VRCSdkControlPanel.ShowContentPublishPermissionsDialog();
  451. }
  452. }
  453. },
  454. delegate (ApiModelContainer<APIUser> c)
  455. {
  456. Logout();
  457. error = c.Error;
  458. VRC.Core.Logger.Log("Error logging in: " + error);
  459. },
  460. delegate (ApiModelContainer<API2FA> c)
  461. {
  462. API2FA model2FA = c.Model as API2FA;
  463. if (c.Cookies.ContainsKey("auth"))
  464. ApiCredentials.Set(username, username, "vrchat", c.Cookies["auth"]);
  465. showTwoFactorAuthenticationEntry = true;
  466. onAuthenticationVerifiedAction = OnAuthenticationCompleted;
  467. }
  468. );
  469. }
  470. private static object syncObject = new object();
  471. private static void SignIn(bool explicitAttempt)
  472. {
  473. lock (syncObject)
  474. {
  475. if (signingIn
  476. || APIUser.IsLoggedInWithCredentials
  477. || (!explicitAttempt && string.IsNullOrEmpty(storedUsername))
  478. || (!explicitAttempt && string.IsNullOrEmpty(storedPassword)))
  479. return;
  480. signingIn = true;
  481. }
  482. InitAccount();
  483. AttemptLogin();
  484. }
  485. public static void Logout()
  486. {
  487. signingIn = false;
  488. storedUsername = null;
  489. storedPassword = null;
  490. VRC.Tools.ClearCookies();
  491. APIUser.Logout();
  492. }
  493. private void AccountDestroy()
  494. {
  495. signingIn = false;
  496. isInitialized = false;
  497. }
  498. }