Skip to content

Telegram: approve a sign-in on another device from the account page, and the list of signed-in devices - #20

Merged
zyansheep merged 1 commit into
mainfrom
feat/telegram-approve-login
Nov 8, 2026
Merged

zyansheep merged 1 commit into
mainfrom
feat/telegram-approve-login

Conversation

@zyansheep

Copy link
Copy Markdown
Contributor

So the owner can remove the Telegram app from their phone: Telegram asks a device that is already signed in to approve each new sign-in, by scanning the QR code the new device shows. A Telegram account's page here can now be that device. It uses the actions dither-accounts' Telegram extractor gained in 7d072ac (approveLogin, sessions, endSession) through the existing POST /api/accounts/:id/action. There are no hub or protocol changes.

What changed

  • Signed-in devices section on a Telegram account's page (ui/TelegramSignIns.tsx). It works only while the account is online; before that, the buttons are off and it says why.
    • Each session's device, app and version, platform, place (region and country, either may be empty) and last active, in the order sessions gives.
    • This hub's own session (hash "0", current) comes first, is marked "This hub", and has no sign-out.
    • Every other row has Sign out with a short "Sign it out?" / Keep confirm.
    • Hashes stay opaque strings (they can be negative 64-bit ids).
  • Approve a sign-in opens a scanner:
    • The camera turns on by itself. Codes are read by BarcodeDetector where the browser has one, otherwise by jsQR 1.4.0 (Apache-2.0), a separate chunk (130 kB, 48 kB gzip) loaded only when a scanner opens. It is not in the service worker's precache.
    • Or paste a tg://login?token=… link, or choose a picture of the code (a screenshot, say).
    • Only tg://login?token= links go further (tglogin.ts). Anything else shows "That is not a Telegram sign-in code…".
    • The camera stops when the scanner closes, when a code is read, and while the page is hidden. It comes back when the page is shown again, if the scanner is still scanning.
    • A camera that is refused, missing, busy or unavailable without HTTPS is said in words, and the paste field and the picture still work.
  • Warning before approving. It is prominent, not small grey text:
    • Only approve a code your own device is showing you. Anyone who gets you to scan their code gets your Telegram account.
    • If someone sent you the code, don't approve it.
    • Telegram names the device only after approval.
    • While this hub's own session is under a day old, it also says "There is no undo for about N hours".
    • A scan never approves. The approve button arms 1 s after the warning appears, so a tap meant for the previous step can't land on it. The state machine enforces this too (step() in tglogin.ts, unit-tested).
  • After approving: the new session's device, app and place, and That wasn't me: sign it out (endSession {hash}).
  • Every refusal (expired, already used, unknown, not a sign-in, too new, no such session) is shown exactly as the extractor words it.
  • Docs: docs/accounts.md, "Approving a Telegram sign-in".

Android

No change needed. MainActivity.onPermissionRequest already maps RESOURCE_VIDEO_CAPTURE to CAMERA (used by calls), and the manifest already declares android.permission.CAMERA. The WebView therefore asks Android for the camera and grants it to the hub's page. If the user refuses, the page shows the refusal message and the paste field still works. I didn't run it on a device.

How it was verified

  • bun run typecheck and bun run test: all packages pass. New: packages/web/test/tglogin.test.ts covers the link check, place and app text, the approve steps (no approval before arming or without a press), and the undo-blocked window.
  • New scripts/verify-telegram-approve.ts, run against a throwaway dither-accounts manager (7d072ac, TELEGRAM_FAKE=1, fresh data dir) and a throwaway hub in remote mode (fresh data dir). It uses headless Chromium with a fake camera that plays a generated QR code (Y4M). All checks pass. They cover:
    • before sign-in, the section is off and sessions says to sign in first;
    • the devices list: order, own row marked without sign-out, the negative hash kept exact, the empty region showing only the country, no IPs;
    • a login code from 777000 arriving as a message;
    • the camera reading a non-Telegram QR and refusing it;
    • the camera stopping while the page is hidden and coming back after;
    • a pasted link: the warning, the button not yet armed, nothing accepted (checked against the fake's call log) until pressed, then device, app and place;
    • "That wasn't me" refused while the session is under a day old (Telegram's own words), then signed out after ageSession;
    • an expired link's error, shown as it comes;
    • a code read by the camera: the warning, no approval after 2.5 s, then approving and signing out (Germany with an empty region);
    • a picture of a code (a BMP via the file picker);
    • closing the scanner stops the camera;
    • a row's sign-out with Keep, then confirm;
    • at 390 px in the dark and the light theme, no sideways overflow on the page, a row's confirm, the scanner, the warning, or the approved view (with a long device name);
    • in a second browser without the fake camera's auto-accept and with the camera permission set to denied: the refusal message, the paste field still working, and no overflow.

What I could not verify

  • BarcodeDetector: headless Linux Chromium doesn't have it, so the camera and picture runs went through jsQR. The native path is plain API use, untested here.
  • A real phone: the installed PWA and the Android WebView camera prompt. These are checked only by reading MainActivity and by the 390 px emulation.
  • Real Telegram: everything ran against the fake. Nothing touched the live manager or hub, or any real account.

🤖 Generated with Claude Code

…by scanning its QR code or pasting its link, with a warning and a deliberate second press, then its device, app and place and a way to sign it out; and the list of signed-in devices, each but this hub's with a sign-out

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@zyansheep
zyansheep enabled auto-merge (squash) November 8, 2026 01:52
@zyansheep
zyansheep merged commit 89557f1 into main Nov 8, 2026
1 check passed
@zyansheep
zyansheep deleted the feat/telegram-approve-login branch November 8, 2026 01:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant