From 4c2cb63cf9f71fb392c3723a99e88ca3d02b127d Mon Sep 17 00:00:00 2001 From: Fringg Date: Sun, 22 Mar 2026 07:24:27 +0300 Subject: [PATCH] fix: accept stale Telegram initData to prevent MiniApp auth failures Telegram Desktop/iOS cache initData with stale auth_date (tdesktop#28303). Increase max_age_seconds from 24h to 30 days for all cabinet login and account linking endpoints. HMAC signature still validates authenticity, JWT tokens handle session expiration. Add structured logging for stale initData acceptance monitoring. --- app/cabinet/auth/telegram_auth.py | 20 ++++++++++++++++++++ app/cabinet/routes/account_linking.py | 6 ++++-- app/cabinet/routes/auth.py | 9 +++++++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/app/cabinet/auth/telegram_auth.py b/app/cabinet/auth/telegram_auth.py index f4de0e68..716c7ddb 100644 --- a/app/cabinet/auth/telegram_auth.py +++ b/app/cabinet/auth/telegram_auth.py @@ -49,7 +49,17 @@ def validate_telegram_login_widget(data: dict[str, Any], max_age_seconds: int = auth_time = datetime.fromtimestamp(int(auth_date), tz=UTC) age = (datetime.now(UTC) - auth_time).total_seconds() if age > max_age_seconds or age < -_MAX_CLOCK_SKEW_SECONDS: + logger.warning( + 'Telegram widget auth rejected: too old', + age_hours=round(age / 3600, 1), + max_age_hours=round(max_age_seconds / 3600, 1), + ) return False + if age > 86400: + logger.info( + 'Telegram widget auth accepted with stale auth_date', + age_hours=round(age / 3600, 1), + ) except (ValueError, TypeError, OSError): return False @@ -96,7 +106,17 @@ def validate_telegram_init_data(init_data: str, max_age_seconds: int = 86400) -> auth_time = datetime.fromtimestamp(int(auth_date), tz=UTC) age = (datetime.now(UTC) - auth_time).total_seconds() if age > max_age_seconds or age < -_MAX_CLOCK_SKEW_SECONDS: + logger.warning( + 'Telegram initData rejected: too old', + age_hours=round(age / 3600, 1), + max_age_hours=round(max_age_seconds / 3600, 1), + ) return None + if age > 86400: + logger.info( + 'Telegram initData accepted with stale auth_date (Telegram caching bug)', + age_hours=round(age / 3600, 1), + ) except (ValueError, TypeError, OSError): return None diff --git a/app/cabinet/routes/account_linking.py b/app/cabinet/routes/account_linking.py index 4ed3c8fe..3424161f 100644 --- a/app/cabinet/routes/account_linking.py +++ b/app/cabinet/routes/account_linking.py @@ -487,7 +487,8 @@ async def link_telegram( if request.init_data: # Mini App flow: validate initData - user_data = validate_telegram_init_data(request.init_data) + # Generous max_age: Telegram Desktop/iOS cache initData with stale auth_date + user_data = validate_telegram_init_data(request.init_data, max_age_seconds=86400 * 30) if not user_data or not user_data.get('id'): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, @@ -560,7 +561,8 @@ async def link_telegram( if request.photo_url is not None: widget_data['photo_url'] = request.photo_url - if not validate_telegram_login_widget(widget_data): + # Generous max_age: Telegram caches auth data with stale auth_date + if not validate_telegram_login_widget(widget_data, max_age_seconds=86400 * 30): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid or expired Telegram Login Widget data', diff --git a/app/cabinet/routes/auth.py b/app/cabinet/routes/auth.py index a69ba16c..85e5ab22 100644 --- a/app/cabinet/routes/auth.py +++ b/app/cabinet/routes/auth.py @@ -431,7 +431,11 @@ async def auth_telegram( detail='Too many requests', headers={'Retry-After': '60'}, ) - user_data = validate_telegram_init_data(request.init_data) + # Telegram Desktop/iOS cache initData with stale auth_date (known Telegram bug: + # https://github.com/telegramdesktop/tdesktop/issues/28303). + # Use generous max_age: HMAC signature proves authenticity, + # JWT tokens handle actual session expiration after login. + user_data = validate_telegram_init_data(request.init_data, max_age_seconds=86400 * 30) if not user_data: raise HTTPException( @@ -550,7 +554,8 @@ async def auth_telegram_widget( widget_data = request.model_dump(exclude={'campaign_slug', 'referral_code'}) - if not validate_telegram_login_widget(widget_data): + # Generous max_age: Telegram caches auth data with stale auth_date + if not validate_telegram_login_widget(widget_data, max_age_seconds=86400 * 30): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid or expired Telegram authentication data',