diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Tools/ResourceServer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Tools/ResourceServer.cs
index bfea1b8..9f2e319 100644
--- a/WelsonJS.Toolkit/WelsonJS.Launcher/Tools/ResourceServer.cs
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Tools/ResourceServer.cs
@@ -125,6 +125,14 @@ namespace WelsonJS.Launcher.Tools
return;
}
+ // Serve TFA request
+ const string tfaPrefix = "tfa/";
+ if (path.StartsWith(tfaPrefix, StringComparison.OrdinalIgnoreCase))
+ {
+ ServeTfaRequest(context, path.Substring(tfaPrefix.Length - 1));
+ return;
+ }
+
// Serve a resource
ServeResource(context, GetResource(_resourceName), "text/html");
}
@@ -251,6 +259,24 @@ namespace WelsonJS.Launcher.Tools
}
}
+ private void ServeTfaRequest(HttpListenerContext context, string endpoint)
+ {
+ Tfa _tfa = new Tfa();
+
+ if (endpoint.Equals("/pubkey"))
+ {
+ ServeResource(context, _tfa.GetPubKey(), "text/plain", 200);
+ return;
+ }
+
+ ServeResource(context);
+ }
+
+ private void ServeResource(HttpListenerContext context)
+ {
+ ServeResource(context, "Not Found", "application/xml", 404);
+ }
+
private void ServeResource(HttpListenerContext context, byte[] data, string mimeType = "text/html", int statusCode = 200)
{
string xmlHeader = "";
diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Tools/Tfa.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Tools/Tfa.cs
new file mode 100644
index 0000000..7621176
--- /dev/null
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Tools/Tfa.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Collections.Generic;
+
+namespace WelsonJS.Launcher.Tools
+{
+ public class Tfa
+ {
+ private const string Base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+
+ public int GetOtp(string key)
+ {
+ byte[] binaryKey = DecodeBase32(key.Replace(" ", ""));
+ long timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds() / 30;
+ byte[] timestampBytes = BitConverter.GetBytes(timestamp);
+ Array.Reverse(timestampBytes); // Ensure big-endian order
+
+ using (var hmac = new HMACSHA1(binaryKey))
+ {
+ byte[] hash = hmac.ComputeHash(timestampBytes);
+ int offset = hash[hash.Length - 1] & 0xF;
+
+ int otp = ((hash[offset] & 0x7F) << 24) |
+ ((hash[offset + 1] & 0xFF) << 16) |
+ ((hash[offset + 2] & 0xFF) << 8) |
+ (hash[offset + 3] & 0xFF);
+
+ return otp % 1000000; // Ensure 6-digit OTP
+ }
+ }
+
+ public string GetPubKey()
+ {
+ var rand = new Random();
+ var key = new char[16];
+ for (int i = 0; i < 16; i++)
+ {
+ key[i] = Base32Chars[rand.Next(Base32Chars.Length)];
+ }
+ return string.Join(" ", Enumerable.Range(0, 4).Select(i => new string(key, i * 4, 4)));
+ }
+
+ private static byte[] DecodeBase32(string key)
+ {
+ int buffer = 0, bitsLeft = 0;
+ var binaryKey = new List();
+
+ foreach (char c in key)
+ {
+ int value = Base32Chars.IndexOf(c);
+ if (value < 0) continue; // Ignore invalid characters
+
+ buffer = (buffer << 5) + value;
+ bitsLeft += 5;
+ if (bitsLeft >= 8)
+ {
+ bitsLeft -= 8;
+ binaryKey.Add((byte)((buffer >> bitsLeft) & 0xFF));
+ }
+ }
+ return binaryKey.ToArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj
index d037bbf..3525d39 100644
--- a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj
@@ -101,6 +101,7 @@
GlobalSettingsForm.cs
+
EnvForm.cs