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
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:
parent
3afd853f15
commit
b4ad3598ee
2 changed files with 86 additions and 23 deletions
|
|
@ -10,11 +10,12 @@ class TelegramUtils {
|
||||||
/// Following the Python implementation exactly
|
/// Following the Python implementation exactly
|
||||||
///
|
///
|
||||||
/// [initData] should be URL-encoded (as received from Telegram Web App)
|
/// [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,
|
bool checkValidateInitData(String hashStr, String initData, String token,
|
||||||
{String cStr = "WebAppData"}) {
|
{String cStr = "WebAppData", bool isAlreadyDecoded = false}) {
|
||||||
try {
|
try {
|
||||||
// Decode URL-encoded string
|
// Decode URL-encoded string if needed
|
||||||
final decodedData = Uri.decodeComponent(initData);
|
final decodedData = isAlreadyDecoded ? initData : Uri.decodeComponent(initData);
|
||||||
|
|
||||||
// Split into chunks and filter out hash parameter
|
// Split into chunks and filter out hash parameter
|
||||||
final chunks = decodedData
|
final chunks = decodedData
|
||||||
|
|
@ -55,35 +56,80 @@ class TelegramUtils {
|
||||||
Future<({String id, String? username})?> getUserId(String initialData) async {
|
Future<({String id, String? username})?> getUserId(String initialData) async {
|
||||||
try {
|
try {
|
||||||
log('getUserId: initialData length: ${initialData.length}');
|
log('getUserId: initialData length: ${initialData.length}');
|
||||||
final tgWebAppData = Uri.decodeComponent(initialData);
|
log('getUserId: initialData preview: ${initialData.length > 150 ? initialData.substring(0, 150) : initialData}');
|
||||||
log('getUserId: decoded data length: ${tgWebAppData.length}');
|
|
||||||
|
|
||||||
// Extract hash from the data
|
// Check if data is a toString() representation of TelegramInitData object
|
||||||
final hashMatch = RegExp(r'hash=([^&]+)').firstMatch(tgWebAppData);
|
// If it contains "raw: " pattern, extract the raw value
|
||||||
if (hashMatch == null) {
|
// Pattern: raw: user=...&hash=...} (may have closing brace at the end)
|
||||||
log('getUserId: hash not found in data');
|
String? rawData;
|
||||||
return null;
|
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}');
|
||||||
}
|
}
|
||||||
final hash = hashMatch.group(1)!;
|
|
||||||
|
// 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 decoded data');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log('getUserId: hash extracted: ${hash.substring(0, hash.length > 20 ? 20 : hash.length)}...');
|
log('getUserId: hash extracted: ${hash.substring(0, hash.length > 20 ? 20 : hash.length)}...');
|
||||||
|
|
||||||
// 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: validation passed');
|
log('getUserId: validation passed');
|
||||||
|
|
||||||
// Extract user ID from user parameter
|
// Extract user ID from user parameter (use decoded data)
|
||||||
final userMatch = RegExp(r'user=([^&]+)').firstMatch(tgWebAppData);
|
final userMatch = RegExp(r'user=([^&]+)').firstMatch(decodedData);
|
||||||
if (userMatch == null) {
|
if (userMatch == null) {
|
||||||
log('getUserId: user parameter not found');
|
log('getUserId: user parameter not found');
|
||||||
return null;
|
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 userData = jsonDecode(userJson) as Map<String, dynamic>;
|
||||||
final userId = userData['id']?.toString();
|
final userId = userData['id']?.toString();
|
||||||
final username = userData['username']?.toString();
|
final username = userData['username']?.toString();
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,24 @@ class AuthService {
|
||||||
try {
|
try {
|
||||||
// Try different ways to get init data
|
// Try different ways to get init data
|
||||||
if (webAppData.initData != null) {
|
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) {
|
} else if (webAppData.initDataUnsafe != null) {
|
||||||
initData = webAppData.initDataUnsafe.toString();
|
initData = webAppData.initDataUnsafe.toString();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue