Skip to content

Commit 7873dc3

Browse files
asutermoandrew
andauthored
Multiple authentication flow tweaks (#480)
Co-authored-by: andrew <andrew@forming.ai>
1 parent f6d619a commit 7873dc3

File tree

1 file changed

+141
-32
lines changed

1 file changed

+141
-32
lines changed

XAU/ViewModels/Pages/HomeViewModel.cs

Lines changed: 141 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System.ComponentModel;
22
using System.Diagnostics;
33
using System.IO;
4+
using System.Security.Cryptography;
5+
using System.Text;
46
using System.Windows.Media;
57
using Wpf.Ui.Controls;
68
using Memory;
@@ -358,6 +360,8 @@ private async Task InitializeViewModel()
358360
CheckForEventUpdates();
359361
CheckForXboxGamesDatabaseUpdate();
360362
LoadSettings();
363+
if (Settings.OAuthLogin)
364+
OAuthLogin();
361365
_isInitialized = true;
362366
if (Settings.AutoLaunchXboxAppEnabled && Process.GetProcessesByName(ProcessNames.XboxPcApp).Length == 0)
363367
{
@@ -530,44 +534,83 @@ private async void OAuthLogin()
530534
if (LoginText == "Logout")
531535
{
532536
oauth.Signout();
533-
File.Delete(AuthFilePath);
537+
try { File.Delete(AuthFilePath); } catch { }
538+
ClearProfileState();
534539
LoginText = "Login";
535540
return;
536541
}
537542
Settings.OAuthLogin = true;
538543

539-
//check for previous session auth otherwise do interactive login (should almost certainly make this more secure
540-
if (File.Exists(AuthFilePath))
544+
// Use saved session if valid; otherwise interactive login
545+
MicrosoftOAuthResponse? response = await TryRestoreSessionAsync();
546+
if (response == null)
547+
response = await TryInteractiveLoginAsync();
548+
}
549+
550+
private void DeleteAuthFile()
551+
{
552+
try { File.Delete(AuthFilePath); } catch { }
553+
}
554+
555+
private void CompleteLogin(MicrosoftOAuthResponse response, string? successMessage = null)
556+
{
557+
writeSession(response);
558+
if (!string.IsNullOrEmpty(successMessage))
559+
_snackbarService.Show("Success", successMessage, ControlAppearance.Success, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration);
560+
GenerateTokens(response);
561+
}
562+
563+
private async Task<MicrosoftOAuthResponse?> TryRestoreSessionAsync()
564+
{
565+
if (!File.Exists(AuthFilePath))
566+
return null;
567+
568+
MicrosoftOAuthResponse? response;
569+
try
541570
{
542-
MicrosoftOAuthResponse response = readSession();
543-
try
544-
{
545-
response = await oauth.AuthenticateSilently(response?.RefreshToken);
546-
writeSession(response);
547-
_snackbarService.Show("Success", "Logged in with previous session", ControlAppearance.Success, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration);
548-
GenerateTokens(response);
549-
}
550-
catch
551-
{
552-
_snackbarService.Show("Session invalid", "You are required to log in again as the session has expired", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);
553-
response = await oauth.AuthenticateInteractively();
554-
}
571+
response = readSession();
572+
}
573+
catch
574+
{
575+
DeleteAuthFile();
576+
_snackbarService.Show("Session invalid", "Saved session could not be read. Please log in again.", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);
577+
return null;
578+
}
555579

580+
if (response == null || !response.Validate() || string.IsNullOrEmpty(response.RefreshToken))
581+
{
582+
DeleteAuthFile();
583+
if (response != null && !response.Validate())
584+
_snackbarService.Show("Session expired", "Your saved session has expired. Please log in again.", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);
585+
return null;
556586
}
557-
else
587+
588+
try
558589
{
559-
try
560-
{
590+
response = await oauth.AuthenticateSilently(response.RefreshToken!);
591+
CompleteLogin(response, "Logged in with previous session");
592+
return response;
593+
}
594+
catch
595+
{
596+
_snackbarService.Show("Session invalid", "You are required to log in again as the session has expired", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);
597+
ClearProfileState();
598+
return await TryInteractiveLoginAsync();
599+
}
600+
}
561601

562-
MicrosoftOAuthResponse response = await oauth.AuthenticateInteractively();
563-
writeSession(response);
564-
_snackbarService.Show("Success", "Logged in", ControlAppearance.Success, new SymbolIcon(SymbolRegular.Checkmark24), _snackbarDuration);
565-
GenerateTokens(response);
566-
}
567-
catch
568-
{
569-
_snackbarService.Show("Error", "Failed to authenticate", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);
570-
}
602+
private async Task<MicrosoftOAuthResponse?> TryInteractiveLoginAsync()
603+
{
604+
try
605+
{
606+
var response = await oauth.AuthenticateInteractively();
607+
CompleteLogin(response, "Logged in");
608+
return response;
609+
}
610+
catch
611+
{
612+
_snackbarService.Show("Error", "Failed to authenticate", ControlAppearance.Danger, new SymbolIcon(SymbolRegular.ErrorCircle24), _snackbarDuration);
613+
return null;
571614
}
572615
}
573616

@@ -584,6 +627,24 @@ private async void GenerateTokens(MicrosoftOAuthResponse response)
584627
try
585628
{
586629
XAUTH = $"XBL3.0 x={sisuResult.AuthorizationToken.XuiClaims.UserHash};{sisuResult.AuthorizationToken.Token}";
630+
var xui = sisuResult.AuthorizationToken.XuiClaims;
631+
XUIDOnly = xui?.XboxUserId ?? "";
632+
if (!string.IsNullOrEmpty(XUIDOnly))
633+
{
634+
IsLoggedIn = true;
635+
XAUTHTested = true;
636+
InitComplete = true;
637+
if (Settings.PrivacyMode)
638+
{
639+
GamerTag = "Gamertag: Hidden";
640+
Xuid = "XUID: Hidden";
641+
}
642+
else
643+
{
644+
GamerTag = $"Gamertag: {xui?.Gamertag ?? "Unknown"}";
645+
Xuid = $"XUID: {XUIDOnly}";
646+
}
647+
}
587648
}
588649
catch
589650
{
@@ -607,18 +668,66 @@ private async void GenerateTokens(MicrosoftOAuthResponse response)
607668
}
608669
LoginText = "Logout";
609670
XauthWorker_ProgressChanged(null, null);
671+
if (IsLoggedIn && !GrabbedProfile)
672+
GrabProfile();
610673
}
611-
private MicrosoftOAuthResponse readSession()
674+
private void ClearProfileState()
612675
{
613-
var file = File.ReadAllText(AuthFilePath);
614-
var response = JsonConvert.DeserializeObject<MicrosoftOAuthResponse>(file);
676+
IsLoggedIn = false;
677+
XAUTHTested = false;
678+
GrabbedProfile = false;
679+
XAUTH = "";
680+
XUIDOnly = "";
681+
GamerTag = "Gamertag: Unknown ";
682+
Xuid = "XUID: Unknown";
683+
GamerPic = "pack://application:,,,/Assets/cirno.png";
684+
GamerScore = "Gamerscore: Unknown";
685+
ProfileRep = "Reputation: Unknown";
686+
AccountTier = "Tier: Unknown";
687+
CurrentlyPlaying = "Currently Playing: Unknown";
688+
ActiveDevice = "Active Device: Unknown";
689+
IsVerified = "Verified: Unknown";
690+
Location = "Location: Unknown";
691+
Tenure = "Tenure: Unknown";
692+
Following = "Following: Unknown";
693+
Followers = "Followers: Unknown";
694+
Gamepass = "Gamepass: Unknown";
695+
Bio = "Bio: Unknown";
696+
Watermarks.Clear();
697+
XauthWorker_ProgressChanged(null, null);
698+
}
615699

700+
private static readonly byte[] AuthFileMagic = Encoding.ASCII.GetBytes("XAU1");
701+
private static readonly byte[] AuthDpapiEntropy = Encoding.UTF8.GetBytes("XAU-Auth-v1");
702+
703+
private MicrosoftOAuthResponse readSession()
704+
{
705+
var raw = File.ReadAllBytes(AuthFilePath);
706+
string json;
707+
if (raw.Length >= AuthFileMagic.Length && raw.AsSpan(0, AuthFileMagic.Length).SequenceEqual(AuthFileMagic))
708+
{
709+
var encrypted = raw.AsSpan(AuthFileMagic.Length).ToArray();
710+
var plain = ProtectedData.Unprotect(encrypted, AuthDpapiEntropy, DataProtectionScope.CurrentUser);
711+
json = Encoding.UTF8.GetString(plain);
712+
}
713+
else
714+
{
715+
json = Encoding.UTF8.GetString(raw);
716+
}
717+
var response = JsonConvert.DeserializeObject<MicrosoftOAuthResponse>(json);
616718
return response;
617719
}
720+
618721
private void writeSession(MicrosoftOAuthResponse response)
619722
{
620723
var json = JsonConvert.SerializeObject(response);
621-
File.WriteAllText(AuthFilePath, json);
724+
var plain = Encoding.UTF8.GetBytes(json);
725+
var encrypted = ProtectedData.Protect(plain, AuthDpapiEntropy, DataProtectionScope.CurrentUser);
726+
using (var fs = new FileStream(AuthFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
727+
{
728+
fs.Write(AuthFileMagic, 0, AuthFileMagic.Length);
729+
fs.Write(encrypted, 0, encrypted.Length);
730+
}
622731
}
623732

624733
#endregion

0 commit comments

Comments
 (0)