Files
GmRelayBot/src/GmRelay.Web/Services/TelegramAuthService.cs
T
Toutsu 41f2ea6e90
Deploy Telegram Bot / build-and-push (push) Successful in 23s
Deploy Telegram Bot / deploy (push) Successful in 10s
feat: add telegram mini app dashboard
2026-04-28 14:56:55 +03:00

155 lines
5.3 KiB
C#

using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.WebUtilities;
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;
}
public bool VerifyWebAppInitData(string initData, out long telegramId, out string name)
{
telegramId = 0;
name = string.Empty;
if (string.IsNullOrWhiteSpace(initData))
return false;
var token = configuration["Telegram__BotToken"] ?? configuration["Telegram:BotToken"];
if (string.IsNullOrEmpty(token))
return false;
var values = QueryHelpers.ParseQuery(initData);
if (!values.TryGetValue("hash", out var hash) || string.IsNullOrWhiteSpace(hash))
return false;
var dataCheckString = string.Join(
"\n",
values
.Where(pair => pair.Key != "hash")
.OrderBy(pair => pair.Key, StringComparer.Ordinal)
.Select(pair => $"{pair.Key}={pair.Value}"));
var secretKey = HMACSHA256.HashData(Encoding.UTF8.GetBytes("WebAppData"), Encoding.UTF8.GetBytes(token));
var computedHashBytes = HMACSHA256.HashData(secretKey, Encoding.UTF8.GetBytes(dataCheckString));
byte[] hashBytes;
try
{
hashBytes = Convert.FromHexString(hash.ToString());
}
catch (FormatException)
{
return false;
}
if (!CryptographicOperations.FixedTimeEquals(computedHashBytes, hashBytes))
return false;
if (!values.TryGetValue("auth_date", out var authDateStr) ||
!long.TryParse(authDateStr, out var authDate))
{
return false;
}
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (now - authDate > 86400)
return false;
if (!values.TryGetValue("user", out var userJson) || string.IsNullOrWhiteSpace(userJson))
return false;
return TryReadWebAppUser(userJson.ToString(), out telegramId, out name);
}
private static bool TryReadWebAppUser(string userJson, out long telegramId, out string name)
{
telegramId = 0;
name = string.Empty;
try
{
using var document = JsonDocument.Parse(userJson);
var root = document.RootElement;
if (!root.TryGetProperty("id", out var idElement) || !idElement.TryGetInt64(out telegramId))
return false;
var firstName = root.TryGetProperty("first_name", out var firstNameElement)
? firstNameElement.GetString() ?? string.Empty
: string.Empty;
var lastName = root.TryGetProperty("last_name", out var lastNameElement)
? lastNameElement.GetString() ?? string.Empty
: string.Empty;
var username = root.TryGetProperty("username", out var usernameElement)
? usernameElement.GetString()
: null;
name = (firstName, lastName) switch
{
({ Length: > 0 }, { Length: > 0 }) => $"{firstName} {lastName}",
({ Length: > 0 }, _) => firstName,
_ when !string.IsNullOrWhiteSpace(username) => "@" + username,
_ => $"Telegram {telegramId.ToString(System.Globalization.CultureInfo.InvariantCulture)}"
};
return true;
}
catch (JsonException)
{
return false;
}
}
}