Skip to content

Commit 101889f

Browse files
committed
github: add explicit device code auth mode to UIs
Add the device code option to the Avalonia and WPF helper UI applications for GitHub. This includes adding a new explicit "device code" button to the primary authentication prompt view, and a new device code display prompt.
1 parent 28f487a commit 101889f

File tree

23 files changed

+466
-10
lines changed

23 files changed

+466
-10
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Threading;
2+
using System.Threading.Tasks;
3+
using GitHub.UI.ViewModels;
4+
using GitHub.UI.Views;
5+
using Microsoft.Git.CredentialManager;
6+
using Microsoft.Git.CredentialManager.UI;
7+
8+
namespace GitHub.UI.Commands
9+
{
10+
public class DeviceCodeCommandImpl : DeviceCodeCommand
11+
{
12+
public DeviceCodeCommandImpl(ICommandContext context) : base(context) { }
13+
14+
protected override Task ShowAsync(DeviceCodeViewModel viewModel, CancellationToken ct)
15+
{
16+
return AvaloniaUi.ShowViewAsync<DeviceCodeView>(viewModel, GetParentHandle(), ct);
17+
}
18+
}
19+
}

src/shared/GitHub.UI.Avalonia/Controls/TesterWindow.axaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,17 @@
6060
<Button Classes="accent" Content="Show" Click="ShowTwoFactorCode" />
6161
</StackPanel>
6262
</TabItem>
63+
64+
<TabItem Header="Device Code">
65+
<StackPanel>
66+
<Grid RowDefinitions="Auto,Auto" ColumnDefinitions="Auto,*">
67+
<Label Grid.Row="0" Grid.Column="0" Content="User Code" />
68+
<TextBox Grid.Row="0" Grid.Column="1" x:Name="userCode" />
69+
<Label Grid.Row="1" Grid.Column="0" Content="Verification Url" />
70+
<TextBox Grid.Row="1" Grid.Column="1" x:Name="verificationUrl" />
71+
</Grid>
72+
<Button Classes="accent" Content="Show" Click="ShowDeviceCode" />
73+
</StackPanel>
74+
</TabItem>
6375
</TabControl>
6476
</Window>

src/shared/GitHub.UI.Avalonia/Controls/TesterWindow.axaml.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ private void ShowCredentials(object sender, RoutedEventArgs e)
5555
var vm = new CredentialsViewModel(_environment)
5656
{
5757
ShowBrowserLogin = this.FindControl<CheckBox>("useBrowser").IsChecked ?? false,
58+
ShowDeviceLogin = this.FindControl<CheckBox>("useDevice").IsChecked ?? false,
5859
ShowTokenLogin = this.FindControl<CheckBox>("usePat").IsChecked ?? false,
5960
ShowBasicLogin = this.FindControl<CheckBox>("useBasic").IsChecked ?? false,
6061
EnterpriseUrl = this.FindControl<TextBox>("enterpriseUrl").Text,
@@ -75,5 +76,17 @@ private void ShowTwoFactorCode(object sender, RoutedEventArgs e)
7576
var window = new DialogWindow(view) {DataContext = vm};
7677
window.ShowDialog(this);
7778
}
79+
80+
private void ShowDeviceCode(object sender, RoutedEventArgs e)
81+
{
82+
var vm = new DeviceCodeViewModel(_environment)
83+
{
84+
UserCode = this.FindControl<TextBox>("userCode").Text,
85+
VerificationUrl = this.FindControl<TextBox>("verificationUrl").Text,
86+
};
87+
var view = new DeviceCodeView();
88+
var window = new DialogWindow(view) {DataContext = vm};
89+
window.ShowDialog(this);
90+
}
7891
}
7992
}

src/shared/GitHub.UI.Avalonia/GitHub.UI.Avalonia.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@
3838
<DependentUpon>DeviceCodeView.axaml</DependentUpon>
3939
<SubType>Code</SubType>
4040
</Compile>
41+
<Compile Update="Views\DeviceCodeView.axaml.cs">
42+
<DependentUpon>DeviceCodeView.axaml</DependentUpon>
43+
<SubType>Code</SubType>
44+
</Compile>
4145
</ItemGroup>
4246

4347
</Project>

src/shared/GitHub.UI.Avalonia/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ private static void AppMain(object o)
5050
{
5151
app.RegisterCommand(new CredentialsCommandImpl(context));
5252
app.RegisterCommand(new TwoFactorCommandImpl(context));
53+
app.RegisterCommand(new DeviceCodeCommandImpl(context));
5354

5455
int exitCode = app.RunAsync(args)
5556
.ConfigureAwait(false)

src/shared/GitHub.UI.Avalonia/Views/CredentialsView.axaml

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
55
xmlns:controls="clr-namespace:GitHub.UI.Controls"
66
xmlns:vm="clr-namespace:GitHub.UI.ViewModels;assembly=GitHub.UI.Shared"
7+
xmlns:converters="clr-namespace:Microsoft.Git.CredentialManager.UI.Converters;assembly=Microsoft.Git.CredentialManager.UI.Avalonia"
78
mc:Ignorable="d" d:DesignWidth="420"
89
x:Class="GitHub.UI.Views.CredentialsView">
910
<Design.DataContext>
@@ -50,10 +51,15 @@
5051
</Style>
5152
</TabControl.Styles>
5253

53-
<TabItem IsVisible="{Binding $self.IsEnabled}"
54-
IsEnabled="{Binding ShowBrowserLogin}">
54+
<TabItem IsVisible="{Binding $self.IsEnabled}">
55+
<TabItem.IsEnabled>
56+
<MultiBinding Converter="{x:Static converters:BoolConvertersEx.Or}">
57+
<Binding Path="ShowBrowserLogin" />
58+
<Binding Path="ShowDeviceLogin" />
59+
</MultiBinding>
60+
</TabItem.IsEnabled>
5561
<TabItem.Header>
56-
<TextBlock Text="Browser" FontSize="12" />
62+
<TextBlock Text="{Binding OAuthModeTitle}" FontSize="12" />
5763
</TabItem.Header>
5864
<StackPanel Margin="0,10">
5965
<Button x:Name="signInBrowserButton"
@@ -64,6 +70,12 @@
6470
HorizontalAlignment="Center"
6571
Margin="0,0,0,10"
6672
Classes="accent"/>
73+
<Button x:Name="signInDeviceButton"
74+
Content="Sign in with a code"
75+
Command="{Binding SignInDeviceCommand}"
76+
IsVisible="{Binding ShowDeviceLogin}"
77+
HorizontalAlignment="Center"
78+
Margin="0,10,0,10"/>
6779
</StackPanel>
6880
</TabItem>
6981

src/shared/GitHub.UI.Avalonia/Views/CredentialsView.axaml.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public class CredentialsView : UserControl, IFocusable
99
{
1010
private TabControl _tabControl;
1111
private Button _browserButton;
12+
private Button _deviceButton;
1213
private TextBox _tokenTextBox;
1314
private TextBox _userNameTextBox;
1415
private TextBox _passwordTextBox;
@@ -24,6 +25,7 @@ private void InitializeComponent()
2425

2526
_tabControl = this.FindControl<TabControl>("authModesTabControl");
2627
_browserButton = this.FindControl<Button>("signInBrowserButton");
28+
_deviceButton = this.FindControl<Button>("signInDeviceButton");
2729
_tokenTextBox = this.FindControl<TextBox>("tokenTextBox");
2830
_userNameTextBox = this.FindControl<TextBox>("userNameTextBox");
2931
_passwordTextBox = this.FindControl<TextBox>("passwordTextBox");
@@ -43,6 +45,11 @@ public void SetFocus()
4345
_tabControl.SelectedIndex = 0;
4446
_browserButton.Focus();
4547
}
48+
else if (vm.ShowDeviceLogin)
49+
{
50+
_tabControl.SelectedIndex = 0;
51+
_deviceButton.Focus();
52+
}
4653
else if (vm.ShowTokenLogin)
4754
{
4855
_tabControl.SelectedIndex = 1;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<UserControl xmlns="https://github.com/avaloniaui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
4+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
xmlns:controls="clr-namespace:GitHub.UI.Controls"
6+
xmlns:vm="clr-namespace:GitHub.UI.ViewModels;assembly=GitHub.UI.Shared"
7+
mc:Ignorable="d" d:DesignWidth="420"
8+
x:Class="GitHub.UI.Views.DeviceCodeView">
9+
<Design.DataContext>
10+
<vm:DeviceCodeViewModel/>
11+
</Design.DataContext>
12+
<DockPanel>
13+
<StackPanel DockPanel.Dock="Top" Margin="0,0,0,15">
14+
<!-- TODO: replace with GitHub logo -->
15+
<TextBlock Text="GitHub"
16+
HorizontalAlignment="Center"
17+
FontSize="20"
18+
FontWeight="Bold"/>
19+
<TextBlock Text="Device authentication"
20+
HorizontalAlignment="Center"
21+
FontSize="24"
22+
FontWeight="Light"
23+
Margin="0,0,0,10" />
24+
<controls:HorizontalShadowDivider/>
25+
</StackPanel>
26+
27+
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
28+
<TextBlock Text="Visit the URL below, sign in, and enter the following device code to continue."
29+
Margin="0,0,0,20"
30+
TextWrapping="Wrap" TextAlignment="Center"/>
31+
<TextBox Text="{Binding UserCode}"
32+
Margin="0,0,0,20"
33+
HorizontalAlignment="Center"
34+
FontSize="24"
35+
TextAlignment="Center"
36+
Classes="label monospace"/>
37+
<Button Content="{Binding VerificationUrl}"
38+
Command="{Binding VerificationUrlCommand}"
39+
HorizontalAlignment="Center"
40+
Classes="hyperlink" />
41+
</StackPanel>
42+
</DockPanel>
43+
</UserControl>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using Avalonia.Controls;
2+
using Avalonia.Markup.Xaml;
3+
4+
namespace GitHub.UI.Views
5+
{
6+
public class DeviceCodeView : UserControl
7+
{
8+
public DeviceCodeView()
9+
{
10+
InitializeComponent();
11+
}
12+
13+
private void InitializeComponent()
14+
{
15+
AvaloniaXamlLoader.Load(this);
16+
}
17+
}
18+
}

src/shared/GitHub.UI/Commands/CredentialsCommand.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ protected CredentialsCommand(ICommandContext context)
3131
new Option("--browser", "Enable browser-based OAuth authentication.")
3232
);
3333

34+
AddOption(
35+
new Option("--device", "Enable device code OAuth authentication.")
36+
);
37+
3438
AddOption(
3539
new Option("--pat", "Enable personal access token authentication.")
3640
);
@@ -48,6 +52,7 @@ private class CommandOptions
4852
public string EnterpriseUrl { get; set; }
4953
public bool Basic { get; set; }
5054
public bool Browser { get; set; }
55+
public bool Device { get; set; }
5156
public bool Pat { get; set; }
5257
public bool All { get; set; }
5358
}
@@ -57,6 +62,7 @@ private async Task<int> ExecuteAsync(CommandOptions options)
5762
var viewModel = new CredentialsViewModel(Context.Environment)
5863
{
5964
ShowBrowserLogin = options.All || options.Browser,
65+
ShowDeviceLogin = options.All || options.Device,
6066
ShowTokenLogin = options.All || options.Pat,
6167
ShowBasicLogin = options.All || options.Basic,
6268
};
@@ -92,6 +98,10 @@ private async Task<int> ExecuteAsync(CommandOptions options)
9298
result["mode"] = "browser";
9399
break;
94100

101+
case AuthenticationModes.Device:
102+
result["mode"] = "device";
103+
break;
104+
95105
case AuthenticationModes.Pat:
96106
result["mode"] = "pat";
97107
result["pat"] = viewModel.Token;

0 commit comments

Comments
 (0)