using Google.Apis.Auth.OAuth2; using Google.Apis.Oauth2.v2; using System; using System.Threading.Tasks; using Windows.Security.Authentication.Web; using YouTube.Authorization; using Windows.Security.Credentials; using FoxTube.Models; using YouTube; using System.Threading; using Google.Apis.YouTube.v3; using Windows.UI.Xaml.Controls; using FoxTube.Utils; using YoutubeExplode; using System.Linq; using Newtonsoft.Json; using Google.Apis.Oauth2.v2.Data; namespace FoxTube.Services { public static class UserService { public const string UsersStorageKey = "UserService.Users"; public const string LastUserInfoKey = "UserService.LastUser"; public const int MaxUsersCount = 1; #region Private members private static readonly ExtendedYouTubeService _defaultService = new ExtendedYouTubeService(new Google.Apis.Services.BaseClientService.Initializer { ApplicationName = "FoxTube", ApiKey = SecretConstants.YoutubeApiKey, // TODO: Replace with an actual API key }); private static readonly YoutubeClient _defaultYtClient = new YoutubeClient(); private static string[] Scopes { get; } = new string[] { Oauth2Service.Scope.UserinfoProfile, Oauth2Service.Scope.UserinfoEmail, YouTubeService.Scope.YoutubeForceSsl }; private static ClientSecrets[] ClientSecrets { get; } = SecretConstants.ClientSecrets; #endregion public static Userinfoplus[] Users { get; private set; } = new Userinfoplus[MaxUsersCount]; public static bool IncognitoMode { get; set; } = false; public static bool CanAddAccounts => Users.Any(i => i == null); public static User CurrentUser { get; set; } public static bool Authorized => CurrentUser != null; public static ExtendedYouTubeService Service { get { if (IncognitoMode || CurrentUser == null) return _defaultService; else return CurrentUser.Service; } } public static YoutubeClient YouTubeClient { get { if (IncognitoMode || CurrentUser == null) return _defaultYtClient; else return CurrentUser.Client; } } public static event EventHandler UserStateUpdated; public static event EventHandler SubscriptionsChanged; static UserService() => Initialize(); public static async Task AddUser() { int queueIndex = Users.ToList().FindIndex(i => i == null); if (queueIndex < 0) throw new StackOverflowException("The maximum accounts limit is reached"); ClientSecrets secrets = ClientSecrets[queueIndex]; Uri requestString = AuthorizationHelpers.FormQueryString(secrets, Scopes); WebAuthenticationResult result = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.UseTitle, requestString, AuthorizationHelpers.Endpoint); switch (result.ResponseStatus) { case WebAuthenticationStatus.Success: string successCode = AuthorizationHelpers.ParseSuccessCode(result.ResponseData); YouTube.Authorization.UserCredential credential = await AuthorizationHelpers.ExchangeToken(secrets, successCode); await LoadUser(credential, queueIndex); return true; case WebAuthenticationStatus.UserCancel: break; case WebAuthenticationStatus.ErrorHttp: await new ContentDialog { Title = "Something went wrong...", Content = "It may be a bug or temporary server issues. Please, try again later" }.ShowAsync(); Metrics.SendReport(new Exception("Authorization failed (HTTP Error)"), null, ("Response status", result.ResponseStatus.ToString()), ("Response data", result.ResponseData), ("Error details", result.ResponseErrorDetail.ToString())); break; } return false; } public static async void Initialize() { Users = JsonConvert.DeserializeObject(Storage.Registry.Values[UsersStorageKey] as string ?? "") ?? new Userinfoplus[MaxUsersCount]; int? lastUserIndex = Storage.Registry.Values[LastUserInfoKey] as int?; if (lastUserIndex.HasValue && Users[lastUserIndex.Value] != null || (lastUserIndex = Users.ToList().FindIndex(i => i != null)) > -1) await SwitchUser(lastUserIndex.Value); } public static async Task Logout() { if (CurrentUser?.UserInfo?.Id == null) return false; try { string userId = CurrentUser.UserInfo.Id; PasswordVault passwordVault = new PasswordVault(); try { PasswordCredential credential = passwordVault.Retrieve("foxtube", userId); passwordVault.Remove(credential); } catch { } await CurrentUser.Credential.RevokeTokenAsync(CancellationToken.None); Storage.Registry.Values.Remove($"Subscriptions.{CurrentUser.UserInfo.Id}"); CurrentUser = null; Users[Users.ToList().FindIndex(i => i.Id == userId)] = null; Storage.Registry.Values[UsersStorageKey] = JsonConvert.SerializeObject(Users); Storage.Registry.Values[LastUserInfoKey] = null; if (Users.Any(i => i != null)) await SwitchUser(Users.ToList().FindIndex(i => i != null)); else UserStateUpdated?.Invoke(Users, false); return true; } catch (Exception e) { Metrics.SendReport(new Exception("Failed to logout", e)); await new ContentDialog { Title = "Something went wrong...", Content = "It may be a bug or temporary server issues. Please, try again later" }.ShowAsync(); return false; } } public static async Task SwitchUser(int userIndex) { Userinfoplus userInfo = Users[userIndex]; PasswordVault valut = new PasswordVault(); try { PasswordCredential vaultCredential = valut.Retrieve("foxtube", userInfo.Id); vaultCredential.RetrievePassword(); string token = vaultCredential.Password; YouTube.Authorization.UserCredential credential = await AuthorizationHelpers.RestoreUser(ClientSecrets[userIndex], token); await LoadUser(credential, userIndex); return true; } catch { return false; } } private static async Task LoadUser(YouTube.Authorization.UserCredential credential, int userIndex) { CurrentUser = await User.GetUser(credential); Users[userIndex] = CurrentUser.UserInfo; Storage.Registry.Values[UsersStorageKey] = JsonConvert.SerializeObject(Users); Storage.Registry.Values[LastUserInfoKey] = userIndex; credential.RefreshTokenUpdated += (s, e) => UpdateToken(CurrentUser.UserInfo.Id, credential.Token.RefreshToken); UpdateToken(CurrentUser.UserInfo.Id, credential.Token.RefreshToken); UserStateUpdated?.Invoke(Users, true); } private static void UpdateToken(string id, string refreshToken) { PasswordVault passwordVault = new PasswordVault(); try { PasswordCredential vaultCredential = passwordVault.Retrieve("foxtube", id); vaultCredential.Password = refreshToken; } catch { PasswordCredential vaultCredential = new PasswordCredential("foxtube", CurrentUser.UserInfo.Id, refreshToken); passwordVault.Add(vaultCredential); } } internal static void SubscriptionsChangedInvoker(User sender, Subscription subscription) => SubscriptionsChanged?.Invoke(sender, subscription); } }