diff --git a/backend/app/tg/login_flow.py b/backend/app/tg/login_flow.py index 547fb0d..1bd832e 100644 --- a/backend/app/tg/login_flow.py +++ b/backend/app/tg/login_flow.py @@ -15,9 +15,10 @@ """ import uuid +from contextlib import suppress from datetime import datetime, timedelta, timezone -from telethon import errors +from telethon import errors, functions from telethon.sessions import StringSession from app.config import get_settings @@ -250,11 +251,34 @@ async def login_cancel(login_id: str, owner_user_id: str) -> None: + """Отменить попытку логина у Telegram, а не только локально. + + auth.cancelCode инвалидирует код, отправленный ТГ (без него попытка + авторизации остаётся живой на сервере ТГ до истечения кода). Локально — + строка login_sessions + disconnect RAM-клиента (см. _cleanup). Ошибка + cancelCode (код уже истёк/сессия потеряна) не должна ломать локальную + очистку. + """ db = get_db() - await _load_login(db, login_id, owner_user_id) # владение + существование + row = await _load_login(db, login_id, owner_user_id) # владение + существование + await _cancel_code(db, login_id, row) await _cleanup(db, login_id) +async def _cancel_code(db, login_id: str, row) -> None: + """Попытка auth.cancelCode у Telegram (только если есть RAM-клиент и + phone_code_hash); любая ошибка не мешает локальной очистке.""" + client = _manager().pending_client(login_id) + if client is None or not (row["phone"] and row["phone_code_hash"]): + return + try: + await client(functions.auth.CancelCodeRequest( + phone=row["phone"], phone_code_hash=row["phone_code_hash"], + )) + except Exception as exc: # noqa: BLE001 — код уже истёк/сессия потеряна + print(f"[login] cancel_code({login_id}) failed: {type(exc).__name__}", flush=True) + + async def _finalize(db, manager: AccountManager, login_id: str, row, client) -> None: """Успешный логин: get_me → session_data → upsert accounts → cleanup.""" me = await client.get_me() @@ -360,5 +384,10 @@ f"(age > {stale_min}m, step={row['step']})", flush=True, ) + # как ручная отмена: инвалидируем код у ТГ + локальная очистка + cursor2 = await db.execute("SELECT * FROM login_sessions WHERE id = ?", (row["id"],)) + stale = await cursor2.fetchone() + if stale: + await _cancel_code(db, stale["id"], stale) await _cleanup(db, row["id"]) return len(rows) diff --git a/frontend/src/i18n/messages/en.js b/frontend/src/i18n/messages/en.js index 9061ddf..5da6f68 100644 --- a/frontend/src/i18n/messages/en.js +++ b/frontend/src/i18n/messages/en.js @@ -81,6 +81,7 @@ 'wizard.attemptsLeft': 'Attempts left: {n}', 'wizard.wrongCode': 'Invalid code', 'wizard.wrongPassword': 'Invalid password', + 'wizard.cancelFailed': 'Failed to cancel the login', 'wizard.cancel': 'Cancel login', 'wizard.expired': 'Login window expired — start again', diff --git a/frontend/src/i18n/messages/ru.js b/frontend/src/i18n/messages/ru.js index 0a27370..838ca24 100644 --- a/frontend/src/i18n/messages/ru.js +++ b/frontend/src/i18n/messages/ru.js @@ -81,6 +81,7 @@ 'wizard.attemptsLeft': 'Осталось попыток: {n}', 'wizard.wrongCode': 'Неверный код', 'wizard.wrongPassword': 'Неверный пароль', + 'wizard.cancelFailed': 'Не удалось отменить логин', 'wizard.cancel': 'Отменить логин', 'wizard.expired': 'Время логина истекло — начните заново', diff --git a/frontend/src/i18n/messages/uk.js b/frontend/src/i18n/messages/uk.js index f96458c..7a0c530 100644 --- a/frontend/src/i18n/messages/uk.js +++ b/frontend/src/i18n/messages/uk.js @@ -81,6 +81,7 @@ 'wizard.attemptsLeft': 'Залишилось спроб: {n}', 'wizard.wrongCode': 'Невірний код', 'wizard.wrongPassword': 'Невірний пароль', + 'wizard.cancelFailed': 'Не вдалося скасувати логін', 'wizard.cancel': 'Скасувати логін', 'wizard.expired': 'Час логіна минув — почніть знову', diff --git a/frontend/src/pages/LoginWizard.vue b/frontend/src/pages/LoginWizard.vue index 067e895..f03057b 100644 --- a/frontend/src/pages/LoginWizard.vue +++ b/frontend/src/pages/LoginWizard.vue @@ -193,10 +193,19 @@ } async function cancel() { + // «Отменить логин»: бэкенд сам шлёт Telegram auth.cancelCode (инвалидация + // кода) и чистит сессию; 404/410 — попытка уже отменилась сама, не страшно. if (loginId.value && step.value !== 'phone') { try { await api.cancelLogin(loginId.value) - } catch { /* строка уже истекла — не страшно */ } + } catch (e) { + if (e.status === 404 || e.status === 410) { + // уже отменёна/истекла — просто закрываем + } else { + toast.danger({ title: t('wizard.cancelFailed'), text: e.message }) + return // модалку не закрываем: попытка ещё жива, можно повторить + } + } } open.value = false }