diff --git a/mnemo_cards_backend/lib/api/v2/auth_api_v2.dart b/mnemo_cards_backend/lib/api/v2/auth_api_v2.dart index b393e66..1568a76 100644 --- a/mnemo_cards_backend/lib/api/v2/auth_api_v2.dart +++ b/mnemo_cards_backend/lib/api/v2/auth_api_v2.dart @@ -238,21 +238,29 @@ class AuthApiV2 { Future authenticateTelegramWebApp(Request request) async { try { final body = await request.readAsString(); + log('Telegram Web App auth request body: ${body.length} chars', name: 'AuthApiV2'); + final json = jsonDecode(body) as Map; final initData = json['initData'] as String?; if (initData == null || initData.isEmpty) { + log('Telegram Web App auth: initData is null or empty', name: 'AuthApiV2'); return _badRequest('Telegram Web App init data is required'); } + log('Telegram Web App auth: initData received, length: ${initData.length}', name: 'AuthApiV2'); + // Use the existing telegram utils to get user ID final telegramUtils = TelegramUtils(); final userData = await telegramUtils.getUserId(initData); if (userData == null) { - return _badRequest('Invalid Telegram Web App data'); + log('Telegram Web App auth: getUserId returned null - validation failed', name: 'AuthApiV2'); + return _badRequest('Invalid Telegram Web App data: validation failed'); } + log('Telegram Web App auth: userData extracted, userId: ${userData.id}', name: 'AuthApiV2'); + // Find or create user based on telegram user ID var (user, _) = await _userManager.createOrGetUser( externalId: userData.id, diff --git a/mnemo_cards_backend/lib/user/telegram.dart b/mnemo_cards_backend/lib/user/telegram.dart index 1bdbc8a..9dc38ab 100644 --- a/mnemo_cards_backend/lib/user/telegram.dart +++ b/mnemo_cards_backend/lib/user/telegram.dart @@ -8,6 +8,8 @@ class TelegramUtils { /// Validates the data received from the Telegram web app /// Following the Python implementation exactly + /// + /// [initData] should be URL-encoded (as received from Telegram Web App) bool checkValidateInitData(String hashStr, String initData, String token, {String cStr = "WebAppData"}) { try { @@ -35,30 +37,49 @@ class TelegramUtils { final dataCheck = crypto.Hmac(crypto.sha256, secretKey.bytes) .convert(utf8.encode(dataString)); + final calculatedHash = dataCheck.toString(); + final isValid = calculatedHash == hashStr; + + if (!isValid) { + log('checkValidateInitData: hash mismatch. Expected: ${hashStr.substring(0, hashStr.length > 20 ? 20 : hashStr.length)}..., Got: ${calculatedHash.substring(0, calculatedHash.length > 20 ? 20 : calculatedHash.length)}...'); + } + // Compare hex digests - return dataCheck.toString() == hashStr; - } catch (e) { + return isValid; + } catch (e, s) { + log('checkValidateInitData error: $e', error: e, stackTrace: s); return false; } } 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}'); + // Extract hash from the data final hashMatch = RegExp(r'hash=([^&]+)').firstMatch(tgWebAppData); if (hashMatch == null) { + log('getUserId: hash not found in data'); return null; } final hash = hashMatch.group(1)!; + log('getUserId: hash extracted: ${hash.substring(0, hash.length > 20 ? 20 : hash.length)}...'); + // Validate using the new method - if (!checkValidateInitData(hash, tgWebAppData, _botToken)) { + // 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: validation passed'); // Extract user ID from user parameter final userMatch = RegExp(r'user=([^&]+)').firstMatch(tgWebAppData); if (userMatch == null) { + log('getUserId: user parameter not found'); return null; } @@ -67,10 +88,13 @@ class TelegramUtils { final userId = userData['id']?.toString(); final username = userData['username']?.toString(); if (userId == null) { + log('getUserId: user ID is null in user data'); return null; } + log('getUserId: successfully extracted userId: $userId'); return (id: userId, username: username); - } catch (e) { + } catch (e, s) { + log('getUserId error: $e', error: e, stackTrace: s); return null; } }