using System; using System.IO; using System.Net; using System.Security.Cryptography; using System.Text; using UnityEngine; public static class AuthManager { private const string CookieName = "fishka_auth"; private const string TokenSeparator = "."; private const string AppSalt = "FISHKA_WEB_ADMIN_V1"; // поменяй на своё public static bool VerifyPassword(string entered, string actual) { if (entered == null) entered = ""; if (actual == null) actual = ""; return ConstantTimeEquals(entered, actual); } public static bool IsAuthorized(HttpListenerRequest request, string password) { try { var cookie = request.Cookies?[CookieName]; if (cookie == null || string.IsNullOrEmpty(cookie.Value)) return false; return ValidateToken(cookie.Value, password); } catch { return false; } } public static void SetAuthCookie(HttpListenerResponse response, string token, int daysValid) { // Cookie через header руками, чтобы не упираться в SameSite API разных .NET в Unity int maxAge = Mathf.Clamp(daysValid, 1, 3650) * 24 * 60 * 60; string header = $"{CookieName}={token}; Path=/; Max-Age={maxAge}; HttpOnly; SameSite=Lax"; response.Headers.Add("Set-Cookie", header); } public static string GenerateToken(string password, int daysValid) { long exp = DateTimeOffset.UtcNow.AddDays(daysValid).ToUnixTimeSeconds(); string nonce = Guid.NewGuid().ToString("N"); string payload = $"{exp}:{nonce}"; byte[] sig = HmacSha256(GetSecretKey(password), payload); return Base64UrlEncode(Encoding.UTF8.GetBytes(payload)) + TokenSeparator + Base64UrlEncode(sig); } private static bool ValidateToken(string token, string password) { if (string.IsNullOrEmpty(token)) return false; var parts = token.Split(new[] { TokenSeparator }, StringSplitOptions.None); if (parts.Length != 2) return false; byte[] payloadBytes, sigBytes; try { payloadBytes = Base64UrlDecode(parts[0]); sigBytes = Base64UrlDecode(parts[1]); } catch { return false; } string payload = Encoding.UTF8.GetString(payloadBytes); byte[] expected = HmacSha256(GetSecretKey(password), payload); if (!FixedTimeEquals(sigBytes, expected)) return false; // exp var idx = payload.IndexOf(':'); if (idx <= 0) return false; if (!long.TryParse(payload.Substring(0, idx), out long exp)) return false; long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); if (now > exp) return false; return true; } private static byte[] GetSecretKey(string password) { // Если поменяешь пароль — старые куки станут невалидными (это плюс). string s = $"{AppSalt}:{password}"; return Sha256(Encoding.UTF8.GetBytes(s)); } private static byte[] Sha256(byte[] data) { using var sha = SHA256.Create(); return sha.ComputeHash(data); } private static byte[] HmacSha256(byte[] key, string data) { using var h = new HMACSHA256(key); return h.ComputeHash(Encoding.UTF8.GetBytes(data)); } // Достаём пароль из body: либо form-url-encoded (password=...), либо json {"password":"..."} public static string ExtractPassword(string body, string contentType) { if (string.IsNullOrEmpty(body)) return ""; contentType = (contentType ?? "").ToLower(); if (contentType.Contains("application/json")) { // очень простой парсер под {"password":"..."} // если хочешь — можно заменить на DTO+JsonUtility const string key = "\"password\""; int i = body.IndexOf(key, StringComparison.OrdinalIgnoreCase); if (i < 0) return ""; int colon = body.IndexOf(':', i); if (colon < 0) return ""; int q1 = body.IndexOf('"', colon + 1); if (q1 < 0) return ""; int q2 = body.IndexOf('"', q1 + 1); if (q2 < 0) return ""; return body.Substring(q1 + 1, q2 - q1 - 1); } // form try { var nvc = System.Web.HttpUtility.ParseQueryString(body, Encoding.UTF8); return nvc["password"] ?? ""; } catch { return ""; } } private static string Base64UrlEncode(byte[] data) { return Convert.ToBase64String(data) .TrimEnd('=') .Replace('+', '-') .Replace('/', '_'); } private static byte[] Base64UrlDecode(string s) { s = s.Replace('-', '+').Replace('_', '/'); switch (s.Length % 4) { case 2: s += "=="; break; case 3: s += "="; break; } return Convert.FromBase64String(s); } private static bool ConstantTimeEquals(string a, string b) { var ba = Encoding.UTF8.GetBytes(a); var bb = Encoding.UTF8.GetBytes(b); return FixedTimeEquals(ba, bb); } private static bool FixedTimeEquals(byte[] a, byte[] b) { if (a == null || b == null) return false; if (a.Length != b.Length) return false; int diff = 0; for (int i = 0; i < a.Length; i++) diff |= a[i] ^ b[i]; return diff == 0; } }