Initial commit: Client Doc docs Server Tools

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ud18010
2026-07-10 10:24:29 +08:00
co-authored by Cursor
commit 7e35d8da31
3374 changed files with 680813 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
using System;
using System.Security.Cryptography;
using System.Text;
namespace XWorld.Server.Auth
{
// 无状态 HMAC-SHA256 token。格式: "v1.{pid}.{expUnix}.{base64url(hmac(payload))}"
// 其中 payload = "v1.{pid}.{expUnix}"。
public sealed class TokenService
{
readonly byte[] _key;
public TokenService(byte[] key)
{
if (key == null || key.Length < 16) throw new ArgumentException("key too short");
_key = key;
}
public string Issue(int pid, TimeSpan ttl)
{
long exp = DateTimeOffset.UtcNow.Add(ttl).ToUnixTimeSeconds();
string payload = "v1." + pid + "." + exp;
return payload + "." + B64Url(Sign(payload));
}
public bool Verify(string token, out int pid)
{
pid = 0;
if (string.IsNullOrEmpty(token)) return false;
int lastDot = token.LastIndexOf('.');
if (lastDot <= 0) return false;
string payload = token.Substring(0, lastDot);
string sig = token.Substring(lastDot + 1);
byte[] expected = Sign(payload);
byte[] got;
try { got = FromB64Url(sig); } catch { return false; }
if (!CryptographicOperations.FixedTimeEquals(expected, got)) return false;
string[] parts = payload.Split('.'); // ["v1", pid, exp]
if (parts.Length != 3 || parts[0] != "v1") return false;
if (!int.TryParse(parts[1], out pid)) return false;
if (!long.TryParse(parts[2], out long exp)) return false;
if (DateTimeOffset.UtcNow.ToUnixTimeSeconds() > exp) return false;
return true;
}
byte[] Sign(string payload)
{
using var h = new HMACSHA256(_key);
return h.ComputeHash(Encoding.UTF8.GetBytes(payload));
}
static string B64Url(byte[] b) =>
Convert.ToBase64String(b).TrimEnd('=').Replace('+', '-').Replace('/', '_');
static byte[] FromB64Url(string s)
{
string b = s.Replace('-', '+').Replace('_', '/');
switch (b.Length % 4) { case 2: b += "=="; break; case 3: b += "="; break; }
return Convert.FromBase64String(b);
}
}
}