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.
This commit is contained in:
Fringg
2026-03-22 07:24:27 +03:00
parent d3c994083e
commit 4c2cb63cf9
3 changed files with 31 additions and 4 deletions
+20
View File
@@ -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
+4 -2
View File
@@ -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',
+7 -2
View File
@@ -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',