TOTP enrolment (owner)
The platform owner has a single Telegram identity (`OWNER_TG_USER_ID`).
The platform owner has a single Telegram identity (OWNER_TG_USER_ID).
That identity already authenticates every /v1/owner/* call via the bot's
initData HMAC. TOTP layers a second factor on top: a six-digit code
from any RFC-6238 authenticator app (1Password, Authy, Google
Authenticator) must accompany every owner-route request.
Enabling
Phase 30 ships the verification half — the panel-driven enrolment flow lands later. Until then, enrolment is manual:
- Generate a secret:
import pyotp secret = pyotp.random_base32() - Set the env:
OWNER_TOTP_ENABLED=true OWNER_TOTP_SECRET=<secret> - Scan: build the provisioning URI manually and feed it to your
authenticator app:
uri = pyotp.TOTP(secret).provisioning_uri( name="owner@helios", issuer_name="Helios" )
After restarting the API service, every owner-route call (including
GDPR ops, parameter overrides, and the Mini App's owner pages) demands
an X-TOTP-Code header with the current 6-digit value.
Using
The Mini App's API client wraps the header automatically when the
owner has enrolled — the page prompts for a code, caches it for the
configured window, and re-prompts on expiry. From outside the Mini App
(e.g. curl), include the header explicitly:
curl -H "X-Telegram-Init-Data: $INIT_DATA" \
-H "X-TOTP-Code: $(oathtool --base32 --totp "$OWNER_TOTP_SECRET")" \
${PUBLIC_BASE_URL}/v1/owner/parameters
Behaviour matrix
OWNER_TOTP_ENABLED | Code header | Result |
|---|---|---|
false (default) | any/none | open — TOTP off |
true | absent / empty | 403 TOTP: missing X-TOTP-Code header |
true | not 6 digits | 403 TOTP: TOTP code must be 6 digits |
true | wrong code | 403 TOTP: invalid TOTP code |
true | correct code | 200 |
Non-owner users never see any TOTP messaging — they get the regular
403 owner only before the second-factor check runs, so the enrolment
state of the owner isn't leaked.
Skew
pyotp.verify(code, valid_window=1) — accepts the previous and next
30-second window plus the current one. Tightens to zero for
HSM-style setups by editing infrastructure/auth/totp.py. The default
is the right tradeoff for one operator with one device.
Recovery
There is no built-in recovery code today. Lose your authenticator and
you must flip OWNER_TOTP_ENABLED=false from a host with shell access,
then re-enrol. Production setups should put a sealed offline copy of
OWNER_TOTP_SECRET in a manager (1Password, Vault) with the rest of
the AES master-key backup.