6.2 KiB
OTP Entry & Post-Registration Provisioning
OTP Entry: Multi-Strategy with Fallback
GitHub's verification page has changed over time. The current flow (2026-08-10) uses a single numeric input, NOT the old launch_code[] digit boxes. Use 3 strategies in order:
Strategy 1: launch_code[] digit boxes (LEGACY — GitHub pre-2026-08)
<input name="launch_code[]" type="number" maxlength="1"> × 8
If found (8 boxes), fill each individually and submit the form.
Strategy 2: Single numeric input (CURRENT — works 2026-08-10)
var e = document.querySelector('input[inputmode="numeric"]') ||
document.querySelector('input[aria-label*="code" i]') ||
document.querySelector('input[aria-label*="verif" i]') ||
document.querySelector('input[type="text"]:not([name="login"]):not([name="password"])');
e.value = code;
e.dispatchEvent(new Event('input', {bubbles: true}));
e.dispatchEvent(new Event('change', {bubbles: true}));
// Submit: form.requestSubmit() or click button[type="submit"]
Strategy 3: Brute force (last resort)
Type code into ANY visible input and click any button matching verify/continue/submit/confirm.
Wait for input to appear
The verification page may render inputs dynamically. Wait up to 10s:
for _ in range(20):
any_input = document.querySelectorAll('input:not([type="hidden"]):not([type="submit"])').length
if any_input > 0: break
time.sleep(0.5)
Post-OTP Result Detection
After OTP submission, GitHub can redirect to multiple states. Use BOTH path and full URL for matching:
| URL Pattern | Path | Meaning | Action |
|---|---|---|---|
/dashboard, /welcome, / |
match | Account created + auto-logged-in | Extract session cookies |
/login?return_to=...account_verifications |
/login |
Still on verification page (OTP pending/failed) | Check for error messages after 10s |
/login with flash "account was created successfully" |
/login |
Account created, needs login | Do post-signup login |
/login without flash |
/login |
Registration failed or redirected | Abort |
Critical: /login?return_to=.../account_verifications is the verification page. The path is /login, but the full URL contains account_verif. Check BOTH:
path = urlparse(url).path.rstrip("/")
url_lower = url.lower()
# still_on_verify: check BOTH path and full URL
still_on_verify = ("account_verif" in path and "launch" not in path) or \
("account_verif" in url_lower and "launch" not in url_lower)
Time-limit the still_on_verify check: in the post-OTP loop, only skip for first 10s of the 30s window. After that, evaluate other signals (has_session, dashboard, login, code error):
deadline = time.time() + 30
while time.time() < deadline:
# ... get URL, path, cookies ...
if still_on_verify and time.time() < deadline - 20: # first 10s only
time.sleep(1)
continue
# ... check other signals ...
Post-Signup Login
If GitHub shows "Your account was created successfully! Please sign in to continue":
- Navigate to
https://github.com/login - Fill
#login_field(email) and#password(password) - Click submit button
- Navigate to
https://github.com/settings/profileto verify authenticated - Extract
user_sessioncookie
Session Verification
page.goto("https://github.com/settings/profile")
# If redirected to /login → NOT authenticated
# If stays on /settings/profile → AUTHENTICATED
2FA + PAT via provision.py
After registration, use the provision.py from авторег проект/backend/platforms/github/provision.py for post-processing.
WARNING: HTTP provisioning is rate-limited on the same IP. If the browser registration used the local IP, the HTTP requests-based login to the fresh account triggers "login failed: rate". To avoid this:
- Use the SAME proxy for provisioning as registration, OR
- Extract cookies from the browser session and inject them into
requests.Session, OR - Do 2FA + PAT through the browser (CDP) instead of HTTP
enable_2fa(email, password) → {totp_secret, recovery_codes}
Pure HTTP (requests + pyotp), no browser needed. Flow:
- GET
/login→ extract CSRF - POST
/session→ authenticate - GET
/settings/two_factor_authentication/setup/intro→ extract scoped CSRF - POST
/settings/two_factor_authentication/setup/initiate(type=app) → get secret + recovery codes - POST
/settings/two_factor_authentication/setup/verify(type=app, otp=TOTP) - POST
/settings/two_factor_authentication/setup/enable
generate_token(email, password, totp_secret, scopes) → ghp_/github_pat_...
Creates a classic PAT. Flow:
- Login with 2FA
- Handle sudo re-auth if needed
- GET
/settings/tokens/new→ extract CSRF - POST
/settings/tokenswith scopes - Extract
ghp_orgithub_pat_from response
Rate Limiting
GitHub applies a secondary rate limit after 2-3 fast signups. Symptoms:
- Form fields not found (all
querySelectorreturn null —{"email":false,"password":false,"login":false}) - Page stays on
/signupafter submit - HTTP login returns "login failed: rate"
Mitigation:
- Wait 10-15 minutes between batches
- Use proxy rotation (SOCKS5 from residential pool)
- Rate limit on same IP persists 1+ hour — 15-minute wait is insufficient
- HTTP provisioning (requests) hits the SAME rate limit as browser — use same proxy or browser cookies
Verified
- 2026-08-10:
launch_code[]boxes NO LONGER present on GitHub verification page. Singleinput[inputmode="numeric"]strategy works (S2). - 2026-08-10: Multi-strategy OTP entry (S1→S2→S3) with explicit logging confirmed working
- 2026-08-10: Post-OTP
still_on_verifycheck must be time-limited (10s max) — otherwise catches/login?return_to=...account_verificationsindefinitely - 2026-08-10: Both path AND full URL needed for verification page detection (path alone misses
/login?return_to=...account_verifications) - 2026-08-10: HTTP provisioning rate-limited on local IP (
login failed: rate). Must use same proxy as browser or inject browser cookies. - provision.py verified working (2026-08-06, from
авторег проектbackend)