using System.Security.Cryptography; using System.Text; namespace GmRelay.Web.Services; public sealed class TelegramAuthService(IConfiguration configuration) { public bool Verify(IQueryCollection query, out long telegramId, out string name) { telegramId = 0; name = string.Empty; if (!query.TryGetValue("hash", out var hash)) return false; var token = configuration["Telegram__BotToken"] ?? configuration["Telegram:BotToken"]; if (string.IsNullOrEmpty(token)) return false; // 1. Sort and join var dataCheckList = query .Where(x => x.Key != "hash") .OrderBy(x => x.Key) .Select(x => $"{x.Key}={x.Value}") .ToList(); var dataCheckString = string.Join("\n", dataCheckList); // 2. Compute Secret Key (static method — no IDisposable needed) var secretKey = SHA256.HashData(Encoding.UTF8.GetBytes(token)); // 3. Compute Hash (static method — no IDisposable needed) var computedHashBytes = HMACSHA256.HashData(secretKey, Encoding.UTF8.GetBytes(dataCheckString)); // 4. Timing-safe comparison to prevent timing attacks var hashBytes = Convert.FromHexString(hash.ToString()); if (!CryptographicOperations.FixedTimeEquals(computedHashBytes, hashBytes)) return false; // 5. Check expiration (auth_date) if (query.TryGetValue("auth_date", out var authDateStr) && long.TryParse(authDateStr, out var authDate)) { var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); if (now - authDate > 86400) // 24 hours return false; } if (query.TryGetValue("id", out var idStr) && long.TryParse(idStr, out telegramId)) { var firstName = query["first_name"].ToString(); var lastName = query["last_name"].ToString(); name = string.IsNullOrWhiteSpace(lastName) ? firstName : $"{firstName} {lastName}"; return true; } return false; } }