using System.Security.Cryptography; using System.Text; using GmRelay.Web.Services; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Primitives; namespace GmRelay.Bot.Tests.Web; public sealed class TelegramAuthServiceTests { [Fact] public void Verify_ShouldAcceptValidTelegramPayload() { const string botToken = "test-bot-token"; var authDate = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); var query = CreateQueryCollection( botToken, new Dictionary { ["auth_date"] = authDate, ["first_name"] = "Ada", ["id"] = "424242", ["last_name"] = "Lovelace", ["username"] = "ada" }); var service = new TelegramAuthService(CreateConfiguration(botToken)); var verified = service.Verify(query, out var telegramId, out var name); Assert.True(verified); Assert.Equal(424242L, telegramId); Assert.Equal("Ada Lovelace", name); } [Fact] public void Verify_ShouldRejectTamperedHash() { const string botToken = "test-bot-token"; var values = new Dictionary { ["auth_date"] = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ["first_name"] = "Ada", ["id"] = "424242" }; var query = CreateQueryCollection(botToken, values); var invalidQuery = new QueryCollection(new Dictionary(query.ToDictionary( pair => pair.Key, pair => pair.Value)) { ["hash"] = "00" }); var service = new TelegramAuthService(CreateConfiguration(botToken)); var verified = service.Verify(invalidQuery, out _, out _); Assert.False(verified); } [Fact] public void Verify_ShouldRejectExpiredPayload() { const string botToken = "test-bot-token"; var expiredAuthDate = DateTimeOffset.UtcNow.AddDays(-2).ToUnixTimeSeconds().ToString(); var query = CreateQueryCollection( botToken, new Dictionary { ["auth_date"] = expiredAuthDate, ["first_name"] = "Ada", ["id"] = "424242" }); var service = new TelegramAuthService(CreateConfiguration(botToken)); var verified = service.Verify(query, out _, out _); Assert.False(verified); } private static IConfiguration CreateConfiguration(string botToken) => new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { ["Telegram:BotToken"] = botToken }) .Build(); private static QueryCollection CreateQueryCollection(string botToken, Dictionary values) { var hash = ComputeTelegramHash(botToken, values); var queryValues = values.ToDictionary( pair => pair.Key, pair => new StringValues(pair.Value)); queryValues["hash"] = new StringValues(hash); return new QueryCollection(queryValues); } private static string ComputeTelegramHash(string botToken, IReadOnlyDictionary values) { var dataCheckString = string.Join( "\n", values .OrderBy(pair => pair.Key, StringComparer.Ordinal) .Select(pair => $"{pair.Key}={pair.Value}")); var secretKey = SHA256.HashData(Encoding.UTF8.GetBytes(botToken)); var hashBytes = HMACSHA256.HashData(secretKey, Encoding.UTF8.GetBytes(dataCheckString)); return Convert.ToHexString(hashBytes).ToLowerInvariant(); } }