SHD Desktop public download instructions

This commit is contained in:
bulava92
2026-10-28 16:59:02 +03:00
commit 4d6e26db88
24 changed files with 2523 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
using System.Security.Cryptography;
using System.Text;
namespace SHD.Windows;
internal sealed record DesktopAuthCallback(string Code, string State);
internal static class DesktopAuthFlow
{
public const string CallbackUri = "shd-auth://callback";
private const string ClientId = "shd-desktop";
public static PendingDesktopAuth Create(ShdEnvironment environment, BrowserTab targetTab)
{
var verifier = Base64Url(RandomNumberGenerator.GetBytes(32));
var state = Base64Url(RandomNumberGenerator.GetBytes(32));
var challenge = Base64Url(SHA256.HashData(Encoding.ASCII.GetBytes(verifier)));
var url = environment.BaseUrl() + "/desktop-auth/authorize" +
"?client_id=" + Uri.EscapeDataString(ClientId) +
"&redirect_uri=" + Uri.EscapeDataString(CallbackUri) +
"&response_type=code" +
"&state=" + Uri.EscapeDataString(state) +
"&code_challenge=" + Uri.EscapeDataString(challenge) +
"&code_challenge_method=S256";
return new PendingDesktopAuth(state, verifier, environment, targetTab, new Uri(url));
}
public static Uri CompleteUri(ShdEnvironment environment) =>
new(environment.BaseUrl() + "/desktop-auth/complete");
public static bool TryParseCallback(string value, out DesktopAuthCallback? callback, out bool serverReturnedError)
{
callback = null;
serverReturnedError = false;
if (!Uri.TryCreate(value, UriKind.Absolute, out var uri) ||
!uri.Scheme.Equals("shd-auth", StringComparison.OrdinalIgnoreCase) ||
!uri.Host.Equals("callback", StringComparison.OrdinalIgnoreCase) ||
uri.AbsolutePath is not ("" or "/") || uri.UserInfo.Length != 0 || uri.Port != -1 ||
uri.Fragment.Length != 0)
return false;
var values = new Dictionary<string, List<string>>(StringComparer.Ordinal);
foreach (var pair in uri.Query.TrimStart('?').Split('&', StringSplitOptions.RemoveEmptyEntries))
{
var separator = pair.IndexOf('=');
var name = Decode(separator < 0 ? pair : pair[..separator]);
var item = Decode(separator < 0 ? "" : pair[(separator + 1)..]);
if (!values.TryGetValue(name, out var list)) values[name] = list = [];
list.Add(item);
}
if (values.ContainsKey("error"))
{
if (values["error"].Count != 1 ||
!values.TryGetValue("state", out var errorStates) || errorStates.Count != 1 || string.IsNullOrWhiteSpace(errorStates[0]))
return false;
serverReturnedError = true;
callback = new DesktopAuthCallback("", errorStates[0]);
return true;
}
if (!values.TryGetValue("code", out var codes) || codes.Count != 1 || string.IsNullOrWhiteSpace(codes[0]) ||
!values.TryGetValue("state", out var states) || states.Count != 1 || string.IsNullOrWhiteSpace(states[0]))
return false;
callback = new DesktopAuthCallback(codes[0], states[0]);
return true;
}
public static string FormEncode(string value) => Uri.EscapeDataString(value).Replace("%20", "+", StringComparison.Ordinal);
private static string Decode(string value) => Uri.UnescapeDataString(value.Replace("+", " ", StringComparison.Ordinal));
private static string Base64Url(byte[] value) => Convert.ToBase64String(value)
.TrimEnd('=')
.Replace('+', '-')
.Replace('/', '_');
}
internal sealed record PendingDesktopAuth(string State, string Verifier, ShdEnvironment Environment, BrowserTab TargetTab, Uri AuthorizationUri);