5319592964
- Add TelegramAuthPayloadBuilder in GmRelay.Shared for C# tests. - Refactor TelegramAuthServiceTests to use the shared builder. - Add Python equivalent (telegram_init_data.py) for E2E runner. - Add self-contained Python tests and E2E README. Closes #144 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
246 lines
8.0 KiB
C#
246 lines
8.0 KiB
C#
using System.Text.Json;
|
|
using GmRelay.Shared.Telegram;
|
|
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 result = TelegramAuthPayloadBuilder.BuildLoginWidget(
|
|
botToken,
|
|
424242L,
|
|
"Ada",
|
|
"Lovelace",
|
|
"ada");
|
|
var query = ParseQueryString(result.QueryString);
|
|
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 result = TelegramAuthPayloadBuilder.BuildLoginWidget(botToken, 424242L, "Ada");
|
|
var tamperedQuery = ParseQueryString(result.QueryString.Replace("hash=", "hash=00", StringComparison.Ordinal));
|
|
var service = new TelegramAuthService(CreateConfiguration(botToken));
|
|
|
|
var verified = service.Verify(tamperedQuery, out _, out _);
|
|
|
|
Assert.False(verified);
|
|
}
|
|
|
|
[Fact]
|
|
public void Verify_ShouldRejectExpiredPayload()
|
|
{
|
|
const string botToken = "test-bot-token";
|
|
var expiredAuthDate = DateTimeOffset.UtcNow.AddDays(-2).ToUnixTimeSeconds();
|
|
var result = TelegramAuthPayloadBuilder.BuildLoginWidget(
|
|
botToken,
|
|
424242L,
|
|
"Ada",
|
|
authDate: expiredAuthDate);
|
|
var query = ParseQueryString(result.QueryString);
|
|
var service = new TelegramAuthService(CreateConfiguration(botToken));
|
|
|
|
var verified = service.Verify(query, out _, out _);
|
|
|
|
Assert.False(verified);
|
|
}
|
|
|
|
[Fact]
|
|
public void VerifyWebAppInitData_ShouldAcceptValidTelegramWebAppPayload()
|
|
{
|
|
const string botToken = "test-bot-token";
|
|
var result = TelegramAuthPayloadBuilder.BuildMiniAppInitData(
|
|
botToken,
|
|
424242L,
|
|
"Ada",
|
|
"Lovelace",
|
|
"ada",
|
|
queryId: "AAHdF6IQAAAAAN0XohDhrOrc");
|
|
var service = new TelegramAuthService(CreateConfiguration(botToken));
|
|
|
|
var verified = service.VerifyWebAppInitData(result.InitDataRaw, out var telegramId, out var name);
|
|
|
|
Assert.True(verified);
|
|
Assert.Equal(424242L, telegramId);
|
|
Assert.Equal("Ada Lovelace", name);
|
|
}
|
|
|
|
[Fact]
|
|
public void VerifyWebAppInitData_ShouldRejectTamperedHash()
|
|
{
|
|
const string botToken = "test-bot-token";
|
|
var result = TelegramAuthPayloadBuilder.BuildMiniAppInitData(botToken, 424242L, "Ada");
|
|
var tamperedInitData = result.InitDataRaw.Replace("hash=", "hash=00", StringComparison.Ordinal);
|
|
var service = new TelegramAuthService(CreateConfiguration(botToken));
|
|
|
|
var verified = service.VerifyWebAppInitData(tamperedInitData, out _, out _);
|
|
|
|
Assert.False(verified);
|
|
}
|
|
|
|
[Fact]
|
|
public void VerifyWebAppInitData_ShouldRejectExpiredPayload()
|
|
{
|
|
const string botToken = "test-bot-token";
|
|
var expiredAuthDate = DateTimeOffset.UtcNow.AddDays(-2).ToUnixTimeSeconds();
|
|
var result = TelegramAuthPayloadBuilder.BuildMiniAppInitData(
|
|
botToken,
|
|
424242L,
|
|
"Ada",
|
|
authDate: expiredAuthDate);
|
|
var service = new TelegramAuthService(CreateConfiguration(botToken));
|
|
|
|
var verified = service.VerifyWebAppInitData(result.InitDataRaw, out _, out _);
|
|
|
|
Assert.False(verified);
|
|
}
|
|
|
|
[Fact]
|
|
public void VerifyLoginPayload_ShouldAcceptValidTelegramWidgetCallbackPayload()
|
|
{
|
|
const string botToken = "test-bot-token";
|
|
var authDate = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
|
var result = TelegramAuthPayloadBuilder.BuildLoginWidget(
|
|
botToken,
|
|
424242L,
|
|
"Ada",
|
|
"Lovelace",
|
|
"ada",
|
|
"https://t.me/i/userpic/320/ada.jpg",
|
|
authDate);
|
|
var payload = new TelegramLoginPayload(
|
|
result.TelegramId,
|
|
result.FirstName,
|
|
result.LastName,
|
|
result.Username,
|
|
result.PhotoUrl,
|
|
result.AuthDate,
|
|
result.Hash);
|
|
var service = new TelegramAuthService(CreateConfiguration(botToken));
|
|
|
|
var verified = service.VerifyLoginPayload(payload, out var telegramId, out var name);
|
|
|
|
Assert.True(verified);
|
|
Assert.Equal(424242L, telegramId);
|
|
Assert.Equal("Ada Lovelace", name);
|
|
}
|
|
|
|
[Fact]
|
|
public void VerifyLoginPayload_ShouldRejectTamperedCallbackHash()
|
|
{
|
|
var payload = new TelegramLoginPayload(
|
|
424242,
|
|
"Ada",
|
|
null,
|
|
null,
|
|
null,
|
|
DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
|
"00");
|
|
var service = new TelegramAuthService(CreateConfiguration("test-bot-token"));
|
|
|
|
var verified = service.VerifyLoginPayload(payload, out _, out _);
|
|
|
|
Assert.False(verified);
|
|
}
|
|
|
|
[Fact]
|
|
public void VerifyLoginPayload_ShouldRejectExpiredCallbackPayload()
|
|
{
|
|
const string botToken = "test-bot-token";
|
|
var authDate = DateTimeOffset.UtcNow.AddDays(-2).ToUnixTimeSeconds();
|
|
var result = TelegramAuthPayloadBuilder.BuildLoginWidget(
|
|
botToken,
|
|
424242L,
|
|
"Ada",
|
|
authDate: authDate);
|
|
var payload = new TelegramLoginPayload(
|
|
result.TelegramId,
|
|
result.FirstName,
|
|
result.LastName,
|
|
result.Username,
|
|
result.PhotoUrl,
|
|
result.AuthDate,
|
|
result.Hash);
|
|
var service = new TelegramAuthService(CreateConfiguration(botToken));
|
|
|
|
var verified = service.VerifyLoginPayload(payload, out _, out _);
|
|
|
|
Assert.False(verified);
|
|
}
|
|
|
|
[Fact]
|
|
public void VerifyLoginPayload_ShouldRejectMissingRequiredCallbackFields()
|
|
{
|
|
var payload = new TelegramLoginPayload(
|
|
0,
|
|
"",
|
|
null,
|
|
null,
|
|
null,
|
|
DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
|
"");
|
|
var service = new TelegramAuthService(CreateConfiguration("test-bot-token"));
|
|
|
|
var verified = service.VerifyLoginPayload(payload, out _, out _);
|
|
|
|
Assert.False(verified);
|
|
}
|
|
|
|
[Fact]
|
|
public void TelegramLoginPayload_ShouldDeserializeTelegramWidgetSnakeCaseJson()
|
|
{
|
|
var payload = JsonSerializer.Deserialize<TelegramLoginPayload>(
|
|
"""
|
|
{
|
|
"id": 424242,
|
|
"first_name": "Ada",
|
|
"last_name": "Lovelace",
|
|
"username": "ada",
|
|
"photo_url": "https://t.me/i/userpic/320/ada.jpg",
|
|
"auth_date": 1714300000,
|
|
"hash": "abcdef"
|
|
}
|
|
""");
|
|
|
|
Assert.NotNull(payload);
|
|
Assert.Equal(424242L, payload.Id);
|
|
Assert.Equal("Ada", payload.FirstName);
|
|
Assert.Equal("Lovelace", payload.LastName);
|
|
Assert.Equal("ada", payload.Username);
|
|
Assert.Equal("https://t.me/i/userpic/320/ada.jpg", payload.PhotoUrl);
|
|
Assert.Equal(1714300000L, payload.AuthDate);
|
|
Assert.Equal("abcdef", payload.Hash);
|
|
}
|
|
|
|
private static IConfiguration CreateConfiguration(string botToken) =>
|
|
new ConfigurationBuilder()
|
|
.AddInMemoryCollection(new Dictionary<string, string?>
|
|
{
|
|
["Telegram:BotToken"] = botToken
|
|
})
|
|
.Build();
|
|
|
|
private static QueryCollection ParseQueryString(string queryString)
|
|
{
|
|
var parsed = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(queryString);
|
|
return new QueryCollection(parsed.ToDictionary(
|
|
pair => pair.Key,
|
|
pair => pair.Value));
|
|
}
|
|
}
|