stuff
Some checks are pending
Backend CI / test (push) Waiting to run
Backend CI / build (push) Blocked by required conditions
Web App CI / test (push) Waiting to run
Web App CI / build (push) Blocked by required conditions
Deploy Mnemo Cards / Deploy Backend (push) Waiting to run
Deploy Mnemo Cards / Deploy Web App (push) Blocked by required conditions
Deploy Mnemo Cards / Final Verification (push) Blocked by required conditions

This commit is contained in:
Dmitry 2025-12-17 05:36:21 +03:00
parent 3afd853f15
commit b4ad3598ee
2 changed files with 86 additions and 23 deletions

View file

@ -10,11 +10,12 @@ class TelegramUtils {
/// Following the Python implementation exactly
///
/// [initData] should be URL-encoded (as received from Telegram Web App)
/// [isAlreadyDecoded] if true, treats initData as already decoded
bool checkValidateInitData(String hashStr, String initData, String token,
{String cStr = "WebAppData"}) {
{String cStr = "WebAppData", bool isAlreadyDecoded = false}) {
try {
// Decode URL-encoded string
final decodedData = Uri.decodeComponent(initData);
// Decode URL-encoded string if needed
final decodedData = isAlreadyDecoded ? initData : Uri.decodeComponent(initData);
// Split into chunks and filter out hash parameter
final chunks = decodedData
@ -55,35 +56,80 @@ class TelegramUtils {
Future<({String id, String? username})?> getUserId(String initialData) async {
try {
log('getUserId: initialData length: ${initialData.length}');
final tgWebAppData = Uri.decodeComponent(initialData);
log('getUserId: decoded data length: ${tgWebAppData.length}');
log('getUserId: initialData preview: ${initialData.length > 150 ? initialData.substring(0, 150) : initialData}');
// Extract hash from the data
final hashMatch = RegExp(r'hash=([^&]+)').firstMatch(tgWebAppData);
// Check if data is a toString() representation of TelegramInitData object
// If it contains "raw: " pattern, extract the raw value
// Pattern: raw: user=...&hash=...} (may have closing brace at the end)
String? rawData;
final rawMatch = RegExp(r'raw:\s*(.+)').firstMatch(initialData);
if (rawMatch != null) {
rawData = rawMatch.group(1)?.trim();
// Remove trailing } if present (from the closing brace of the object)
if (rawData != null && rawData.endsWith('}')) {
rawData = rawData.substring(0, rawData.length - 1);
}
log('getUserId: extracted raw data from TelegramInitData toString(), length: ${rawData?.length ?? 0}');
}
// Use raw data if extracted, otherwise use initialData as-is
final dataToProcess = rawData ?? initialData;
// Try to determine if data is URL-encoded or already decoded
// If it contains % characters, it's likely URL-encoded
final isUrlEncoded = dataToProcess.contains('%');
log('getUserId: data appears to be ${isUrlEncoded ? "URL-encoded" : "decoded"}');
String dataToValidate;
String hash;
String decodedData;
if (isUrlEncoded) {
// Data is URL-encoded, extract hash from encoded string
final hashMatchEncoded = RegExp(r'hash=([^&]+)').firstMatch(dataToProcess);
if (hashMatchEncoded == null) {
log('getUserId: hash not found in URL-encoded data');
return null;
}
final hashEncoded = hashMatchEncoded.group(1)!;
hash = Uri.decodeComponent(hashEncoded);
dataToValidate = dataToProcess;
decodedData = Uri.decodeComponent(dataToProcess);
// Validate with URL-encoded data (default behavior)
if (!checkValidateInitData(hash, dataToValidate, _botToken)) {
log('getUserId: validation failed with URL-encoded data');
return null;
}
} else {
// Data appears to be already decoded, extract hash directly
final hashMatch = RegExp(r'hash=([^&]+)').firstMatch(dataToProcess);
if (hashMatch == null) {
log('getUserId: hash not found in data');
log('getUserId: hash not found in decoded data');
return null;
}
final hash = hashMatch.group(1)!;
log('getUserId: hash extracted: ${hash.substring(0, hash.length > 20 ? 20 : hash.length)}...');
hash = hashMatch.group(1)!;
dataToValidate = dataToProcess;
decodedData = dataToProcess;
// Validate with already decoded data
if (!checkValidateInitData(hash, dataToValidate, _botToken, isAlreadyDecoded: true)) {
log('getUserId: validation failed with decoded data');
return null;
}
}
// Validate using the new method
// Note: checkValidateInitData expects the original URL-encoded data
// (as received from Telegram Web App), so we pass initialData, not tgWebAppData
if (!checkValidateInitData(hash, initialData, _botToken)) {
log('getUserId: validation failed');
return null;
}
log('getUserId: hash extracted: ${hash.substring(0, hash.length > 20 ? 20 : hash.length)}...');
log('getUserId: validation passed');
// Extract user ID from user parameter
final userMatch = RegExp(r'user=([^&]+)').firstMatch(tgWebAppData);
// Extract user ID from user parameter (use decoded data)
final userMatch = RegExp(r'user=([^&]+)').firstMatch(decodedData);
if (userMatch == null) {
log('getUserId: user parameter not found');
return null;
}
final userJson = Uri.decodeComponent(userMatch.group(1)!);
// If data was already decoded, user value is also already decoded
final userValue = userMatch.group(1)!;
final userJson = isUrlEncoded ? Uri.decodeComponent(userValue) : userValue;
final userData = jsonDecode(userJson) as Map<String, dynamic>;
final userId = userData['id']?.toString();
final username = userData['username']?.toString();

View file

@ -44,7 +44,24 @@ class AuthService {
try {
// Try different ways to get init data
if (webAppData.initData != null) {
initData = webAppData.initData.toString();
// Try to get raw property directly if available (for URL-encoded string)
// Otherwise fall back to toString()
final initDataObject = webAppData.initData!;
try {
// Use dynamic access to try to get raw property
final dynamicObj = initDataObject as dynamic;
if (dynamicObj.raw != null) {
initData = dynamicObj.raw.toString();
log('Using raw property from TelegramInitData', name: 'AuthService');
} else {
initData = initDataObject.toString();
log('Using toString() from TelegramInitData', name: 'AuthService');
}
} catch (e) {
// Fallback to toString() if raw property access fails
initData = initDataObject.toString();
log('Fell back to toString() for initData', name: 'AuthService');
}
} else if (webAppData.initDataUnsafe != null) {
initData = webAppData.initDataUnsafe.toString();
}