Skip to content

Commit 1fceb29

Browse files
committed
Migrate Login background api from HttpListener to AspNetCore http server (fix #3888, fix #4068)
1 parent 65cb379 commit 1fceb29

File tree

2 files changed

+119
-77
lines changed

2 files changed

+119
-77
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using Microsoft.AspNetCore.Builder;
2+
using Microsoft.AspNetCore.Hosting;
3+
using Microsoft.AspNetCore.Http;
4+
using Microsoft.Extensions.Hosting;
5+
using UniGetUI.Core.Logging;
6+
7+
namespace UniGetUI.Services;
8+
9+
public class GHAuthApiRunner: IDisposable
10+
{
11+
public event EventHandler<string>? OnLogin;
12+
private IHost? _host;
13+
public GHAuthApiRunner() { }
14+
15+
public async Task Start()
16+
{
17+
var builder = Host.CreateDefaultBuilder();
18+
builder.ConfigureWebHostDefaults(webBuilder =>
19+
{
20+
webBuilder.UseKestrel();
21+
webBuilder.SuppressStatusMessages(true);
22+
webBuilder.Configure(app =>
23+
{
24+
app.UseRouting();
25+
app.UseEndpoints(endpoints =>
26+
{
27+
endpoints.MapGet("/", LOGIN_CollectGitHubToken);
28+
});
29+
});
30+
webBuilder.UseUrls("http://localhost:58642");
31+
});
32+
_host = builder.Build();
33+
await _host.StartAsync();
34+
Logger.Info("Api running on http://localhost:58642");
35+
}
36+
37+
private async Task LOGIN_CollectGitHubToken(HttpContext context)
38+
{
39+
var code = context.Request.Query["code"];
40+
if (string.IsNullOrEmpty(code))
41+
{
42+
context.Response.StatusCode = 400;
43+
return;
44+
}
45+
46+
await context.Response.WriteAsync(
47+
"""
48+
<html><style>
49+
div {
50+
display: flex;
51+
flex-direction: column;
52+
align-items: center;
53+
justify-content: center;
54+
height: 100vh;
55+
font-family: sans-serif;
56+
text-align: center;
57+
}
58+
</style><script>
59+
window.close();
60+
</script><div>
61+
<title>UniGetUI authentication</title>
62+
<h1>Authentication successful</h1>
63+
<p>You can now close this window and return to UniGetUI</p>
64+
</div></html>
65+
""");
66+
67+
Logger.ImportantInfo($"[AUTH API] Received authentication token {code} from GitHub");
68+
OnLogin?.Invoke(this, code.ToString());
69+
}
70+
71+
public async Task Stop()
72+
{
73+
try
74+
{
75+
ArgumentNullException.ThrowIfNull(_host);
76+
await _host.StopAsync();
77+
}
78+
catch (Exception ex)
79+
{
80+
Logger.Error(ex);
81+
}
82+
}
83+
84+
public void Dispose()
85+
{
86+
_host?.Dispose();
87+
}
88+
}

src/UniGetUI/Services/GitHubAuthService.cs

Lines changed: 31 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -6,40 +6,15 @@
66
using UniGetUI.Core.SecureSettings;
77
using UniGetUI.Core.SettingsEngine;
88
using Windows.System;
9+
using UniGetUI.Interface;
910

1011
namespace UniGetUI.Services
1112
{
1213
public class GitHubAuthService
1314
{
1415
private readonly string GitHubClientId = Secrets.GetGitHubClientId();
1516
private readonly string GitHubClientSecret = Secrets.GetGitHubClientSecret();
16-
17-
private const string DataReceivedWebsite = """
18-
<html>
19-
<style>
20-
div {
21-
display: flex;
22-
flex-direction: column;
23-
align-items: center;
24-
justify-content: center;
25-
height: 100vh;
26-
font-family: sans-serif;
27-
text-align: center;
28-
}
29-
</style>
30-
<script>
31-
window.close();
32-
</script>
33-
<div>
34-
<title>UniGetUI authentication</title>
35-
<h1>Authentication successful</h1>
36-
<p>You can now close this window and return to UniGetUI</p>
37-
</div>
38-
</html>
39-
""";
40-
4117
private const string RedirectUri = "http://127.0.0.1:58642/";
42-
4318
private readonly GitHubClient _client;
4419

4520
public static event EventHandler<EventArgs>? AuthStatusChanged;
@@ -63,25 +38,13 @@ public GitHubAuthService()
6338
};
6439
}
6540

66-
private static HttpListener? httpListener;
41+
private GHAuthApiRunner? loginBackend;
6742
public async Task<bool> SignInAsync()
6843
{
6944
try
7045
{
7146
Logger.Info("Initiating GitHub sign-in process using loopback redirect...");
7247

73-
if (httpListener is null)
74-
{
75-
httpListener = new HttpListener();
76-
httpListener.Prefixes.Add(RedirectUri);
77-
httpListener.Start();
78-
Logger.Info($"Listening for GitHub callback on {RedirectUri}");
79-
}
80-
else
81-
{
82-
Logger.Warn("Http listener already existed");
83-
}
84-
8548
var request = new OauthLoginRequest(GitHubClientId)
8649
{
8750
Scopes = { "read:user", "gist" },
@@ -90,41 +53,33 @@ public async Task<bool> SignInAsync()
9053

9154
var oauthLoginUrl = _client.Oauth.GetGitHubLoginUrl(request);
9255

56+
codeFromAPI = null;
57+
if (loginBackend is not null)
58+
{
59+
try
60+
{
61+
await loginBackend.Stop();
62+
loginBackend.Dispose();
63+
loginBackend = null;
64+
}
65+
catch (Exception ex)
66+
{
67+
Logger.Warn(ex);
68+
}
69+
}
70+
loginBackend = new GHAuthApiRunner();
71+
loginBackend.OnLogin += BackgroundApiOnOnLogin;
72+
await loginBackend.Start();
9373
await Launcher.LaunchUriAsync(oauthLoginUrl);
9474

95-
var context = await httpListener.GetContextAsync();
96-
97-
var response = context.Response;
98-
var buffer = Encoding.UTF8.GetBytes(DataReceivedWebsite);
99-
response.ContentLength64 = buffer.Length;
100-
var output = response.OutputStream;
101-
await output.WriteAsync(buffer, 0, buffer.Length);
102-
output.Close();
103-
104-
httpListener.Stop();
105-
httpListener = null;
106-
Logger.Info("GitHub callback received and processed.");
75+
while (codeFromAPI is null) await Task.Delay(100);
10776

108-
var code = context.Request.QueryString["code"];
109-
if (string.IsNullOrEmpty(code))
110-
{
111-
var error = context.Request.QueryString["error"];
112-
var errorDescription = context.Request.QueryString["error_description"];
113-
Logger.Error($"GitHub OAuth callback returned an error: {error} - {errorDescription}");
114-
AuthStatusChanged?.Invoke(this, EventArgs.Empty);
115-
return false;
116-
}
77+
loginBackend.OnLogin -= BackgroundApiOnOnLogin;
78+
await loginBackend.Stop();
79+
loginBackend.Dispose();
80+
loginBackend = null;
11781

118-
return await _completeSignInAsync(code);
119-
}
120-
catch (HttpListenerException ex) when (ex.ErrorCode == 5) // Access Denied
121-
{
122-
Logger.Error("Access denied to the http listener. Please run the following command in an administrator terminal:");
123-
Logger.Error($"netsh http add urlacl url={RedirectUri} user=Everyone");
124-
// Optionally, you could try to run this command for the user.
125-
// For now, just logging the instruction.
126-
AuthStatusChanged?.Invoke(this, EventArgs.Empty);
127-
return false;
82+
return await _completeSignInAsync(codeFromAPI);
12883
}
12984
catch (Exception ex)
13085
{
@@ -134,13 +89,12 @@ public async Task<bool> SignInAsync()
13489
AuthStatusChanged?.Invoke(this, EventArgs.Empty);
13590
return false;
13691
}
137-
finally
138-
{
139-
try {
140-
httpListener?.Stop();
141-
} catch { /* ignore */ }
142-
httpListener = null;
143-
}
92+
}
93+
94+
private string? codeFromAPI;
95+
private void BackgroundApiOnOnLogin(object? sender, string c)
96+
{
97+
codeFromAPI = c;
14498
}
14599

146100
private async Task<bool> _completeSignInAsync(string code)

0 commit comments

Comments
 (0)