Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions src/XIVLauncher.Common/Shell/OtpShellReceiver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static XIVLauncher.Common.Http.OtpListener;

namespace XIVLauncher.Common.Shell
{
public class OtpShellReceiver
{
private readonly string fileName;
private readonly string args;


/// <summary>
/// Create a shell command receiver that takes the given configured shell command
/// and executes it to derive the OTP
/// </summary>
/// <param name="shellCommand">The configured command. The first space position is treated as the separator between filename and args</param>
public OtpShellReceiver(string shellCommand)
{
// Parse shell command. Just get the first part and then the arguments afterward
var firstSpacePosition = shellCommand.IndexOf(" ");

if (firstSpacePosition == -1)
{
this.fileName = shellCommand;
this.args = string.Empty;
} else
{
this.fileName = shellCommand.Substring(0, firstSpacePosition);
this.args = shellCommand.Substring(firstSpacePosition + 1);
}
}

public async Task<(bool wasGotten, string? oneTimePassword)> TryGetOneTimePasswordAsync(CancellationToken cts = default)
{
Process proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = this.fileName, // "op",
Arguments = this.args, // "item get jxtm37qvd5f6tjezazg6grwq7u --otp",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};

using var _ = cts.Register(() =>
{
try
{
proc.Kill();
}
catch
{

}
});

try
{
var pass = await Task.Run(() =>
{
if (this.TryGetOneTimePassword(proc, out string otp))
{
return otp;
} else
{
return null;
}
});

if (pass == null)
{
return (false, null);
}
return (true, pass);
} catch
{
return (false, null);
}
}

public bool TryGetOneTimePassword(Process proc, out String? otp)
{
proc.Start();
var builder = new StringBuilder();
while (!proc.StandardOutput.EndOfStream)
{
builder.AppendLine(proc.StandardOutput.ReadLine());
}


if (proc.ExitCode != 0)
{
otp = null;
return false;
}

var result = builder.ToString().Trim();
if (string.IsNullOrWhiteSpace(result))
{
otp = null;
return false;
}

otp = result;
return true;

}
}
}
2 changes: 1 addition & 1 deletion src/XIVLauncher/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Globalization;
using System.IO;
using System.Linq;
Expand Down
2 changes: 2 additions & 0 deletions src/XIVLauncher/Settings/ILauncherSettingsV3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public interface ILauncherSettingsV3
string AdditionalLaunchArgs { get; set; }
bool InGameAddonEnabled { get; set; }
DalamudLoadMethod? InGameAddonLoadMethod { get; set; }
bool OtpShellEnabled { get; set; }
string OtpShellCommand { get; set; }
bool OtpServerEnabled { get; set; }
bool OtpAlwaysOnTopEnabled { get; set; }
ClientLanguage? Language { get; set; }
Expand Down
39 changes: 38 additions & 1 deletion src/XIVLauncher/Windows/OtpInputDialog.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
Expand All @@ -9,6 +10,7 @@
using System.Windows.Threading;
using Serilog;
using XIVLauncher.Common.Http;
using XIVLauncher.Common.Shell;
using XIVLauncher.Common.Util;
using XIVLauncher.Windows.ViewModel;

Expand All @@ -25,6 +27,7 @@ public partial class OtpInputDialog : Window

private OtpInputDialogViewModel ViewModel => DataContext as OtpInputDialogViewModel;

private CancellationTokenSource cts;
private OtpListener _otpListener;
private bool _ignoreCurrentOtp;

Expand All @@ -44,9 +47,14 @@ public OtpInputDialog()

public new bool? ShowDialog()
{
cts = new CancellationTokenSource();
OtpTextBox.Focus();

if (App.Settings.OtpServerEnabled)
if (App.Settings.OtpShellEnabled)
{
var receiver = new OtpShellReceiver(App.Settings.OtpShellCommand);
this.GetOneTimePassword(receiver, cts.Token).ConfigureAwait(false);
} else if (App.Settings.OtpServerEnabled)
{
_otpListener = new OtpListener("legacy-" + AppUtil.GetAssemblyVersion());
_otpListener.OnOtpReceived += TryAcceptOtp;
Expand All @@ -66,12 +74,41 @@ public OtpInputDialog()
return base.ShowDialog();
}

private async Task GetOneTimePassword(OtpShellReceiver receiver, CancellationToken cancellationToken)
{
try
{
var res = await receiver.TryGetOneTimePasswordAsync(cts.Token);
if (res.wasGotten)
{
TryAcceptOtp(res.oneTimePassword);
}
}
catch (Exception ex)
{
Log.Error(ex, "Failed to get OTP.");
}
}

public void Reset()
{
OtpInputPrompt.Text = ViewModel.OtpInputPromptLoc;
OtpInputPrompt.Foreground = _otpInputPromptDefaultBrush;
OtpTextBox.Text = "";
OtpTextBox.Focus();
if (cts != null)
{
cts.Cancel();
try
{
cts.Dispose();
}
catch (ObjectDisposedException)
{

}
}
cts = new CancellationTokenSource();
}

public void IgnoreCurrentResult(string reason)
Expand Down
6 changes: 6 additions & 0 deletions src/XIVLauncher/Windows/SettingsControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@
</Button>
</StackPanel>

<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
<TextBox Width="500" VerticalAlignment="Center"
HorizontalAlignment="Left" Foreground="{DynamicResource MaterialDesignBody}"
Margin="0,4,0,0" x:Name="OtpShellArgsTextBox" materialDesign:HintAssist.Hint="{Binding OtpShellArgsLoc}" materialDesign:HintAssist.IsFloating="True"/>
</StackPanel>

<CheckBox Foreground="{DynamicResource MaterialDesignBody}"
x:Name="OtpAlwaysOnTopCheckBox" Margin="0,3,0,0" Content="{Binding OtpAlwaysOnTopCheckBoxLoc}" ToolTip="{Binding OtpAlwaysOnTopTooltipLoc}" />

Expand Down
5 changes: 4 additions & 1 deletion src/XIVLauncher/Windows/SettingsControl.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public void ReloadSettings()
OtpServerCheckBox.IsChecked = App.Settings.OtpServerEnabled;

OtpAlwaysOnTopCheckBox.IsChecked = App.Settings.OtpAlwaysOnTopEnabled;

OtpShellArgsTextBox.Text = App.Settings.OtpShellCommand;
LaunchArgsTextBox.Text = App.Settings.AdditionalLaunchArgs;

DpiAwarenessComboBox.SelectedIndex = (int) App.Settings.DpiAwareness.GetValueOrDefault(DpiAwareness.Unaware);
Expand Down Expand Up @@ -122,6 +122,9 @@ private void AcceptButton_Click(object sender, RoutedEventArgs e)

App.Settings.OtpServerEnabled = OtpServerCheckBox.IsChecked == true;

App.Settings.OtpShellEnabled = !string.IsNullOrWhiteSpace(OtpShellArgsTextBox.Text);
App.Settings.OtpShellCommand = OtpShellArgsTextBox.Text;

App.Settings.OtpAlwaysOnTopEnabled = OtpAlwaysOnTopCheckBox.IsChecked == true;

App.Settings.AdditionalLaunchArgs = LaunchArgsTextBox.Text;
Expand Down
3 changes: 3 additions & 0 deletions src/XIVLauncher/Windows/ViewModel/SettingsControlViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using CheapLoc;

namespace XIVLauncher.Windows.ViewModel
Expand Down Expand Up @@ -70,6 +71,7 @@ private void SetupLoc()
SteamCheckBoxLoc = Loc.Localize("FirstTimeSteamCheckBox", "Enable Steam integration");
OtpServerCheckBoxLoc = Loc.Localize("OtpServerCheckBox", "Enable XL Authenticator app/OTP macro support");
OtpServerTooltipLoc = Loc.Localize("OtpServerTooltip", "This will allow you to send your OTP code to XIVLauncher directly from your phone.\nClick \"Learn more\" to see how to set this up.");
OtpShellArgsLoc = Loc.Localize("OtpShellArgsLoc", "The arguments to use to fetch your OTP code");
LearnMoreLoc = Loc.Localize("LearnMore", "Learn More");
OtpLearnMoreTooltipLoc = Loc.Localize("OtpLearnMoreTooltipLoc", "Open a guide in your web browser.");
OtpAlwaysOnTopCheckBoxLoc = Loc.Localize("OtpAlwaysOnTopCheckBox", "Keep the OTP Window Always on Top");
Expand Down Expand Up @@ -161,6 +163,7 @@ private void SetupLoc()
public string SteamCheckBoxLoc { get; private set; }
public string OtpServerCheckBoxLoc { get; private set; }
public string OtpServerTooltipLoc { get; private set; }
public string OtpShellArgsLoc { get; private set; }
public string LearnMoreLoc { get; private set; }
public string OtpLearnMoreTooltipLoc { get; private set; }
public string OtpAlwaysOnTopCheckBoxLoc { get; private set; }
Expand Down