Files
autoreg-skills-max/skills/account-validation/references/github-oauth-programmatic-login.md

113 lines
3.9 KiB
Markdown

# Programmatic GitHub OAuth Login (requests-based)
Full OAuth flow without a browser — for new-api/one-api/gorouter family services. Works up to the 2FA gate.
## Architecture Discovery
new-api/one-api forks expose OAuth config via `GET /api/status`:
```json
{
"github_oauth": true,
"github_client_id": "Ov23lipc1Ups6bRqeQYE"
}
```
The callback URL is always `{server_address}/oauth/github` (visible in admin panel JS).
## Full Flow
### 1. Construct GitHub OAuth URL
```python
import secrets
state = secrets.token_hex(16)
url = f"https://github.com/login/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&scope=user:email&state={state}"
```
### 2. GET authorize page → parse login form
```python
session = requests.Session()
session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
})
r = session.get(auth_url, allow_redirects=True, timeout=30)
```
If not already logged into GitHub, redirected to `https://github.com/login?...`.
Parse hidden fields:
```python
from bs4 import BeautifulSoup
soup = BeautifulSoup(r.text, 'html.parser')
token = [REDACTED]'input', {'name': 'authenticity_token'}).get('value')
# Also needed (GitHub anti-bot):
timestamp = re.search(r'name="timestamp"[^>]*value="([^"]+)"', r.text).group(1)
timestamp_secret = re.search(r'name="timestamp_secret"[^>]*value="([^"]+)"', r.text).group(1)
```
### 3. POST login credentials
```python
login_data = {
'login': 'USERNAME', # GitHub username, NOT email
'password': 'PASSWORD',
'authenticity_token': token,
'timestamp': timestamp,
'timestamp_secret': timestamp_secret,
}
r2 = session.post('https://github.com/session', data=login_data, allow_redirects=True)
```
### 4. Route by redirect target
| Final URL contains | Meaning | Action |
|---|---|---|
| `two-factor` | 2FA required | **STOP** — cannot proceed without user |
| `authorize` | Login OK, OAuth consent needed | Submit authorization form (step 5) |
| `session` or `login` | Login FAILED | Check for error messages in page |
| `gorouter.app` (or target) | Already authorized | Extract session cookies — done |
### 5. Submit OAuth authorization
```python
soup2 = BeautifulSoup(r2.text, 'html.parser')
auth_token = [REDACTED]'input', {'name': 'authenticity_token'}).get('value')
r3 = session.post(r2.url, data={'authenticity_token': auth_token}, allow_redirects=True)
if 'gorouter.app' in r3.url: # or target service domain
# Session cookies are in session.cookies — use them
print(f"Session: {dict(session.cookies)}")
```
### 6. Use the session
```python
# Access protected endpoints with the authenticated session
r4 = session.get('https://gorouter.app/api/v1/token?p=1&size=20',
headers={'Accept': 'application/json'})
```
## Pitfalls
### 2FA is a hard stop
GitHub 2FA redirects to `https://github.com/sessions/two-factor/app`. There is NO programmatic bypass. The page may offer "Use recovery code" or "Use security key" — none are automatable. Stop and report to user.
### Username, not email
The `login` field in the POST to `/session` takes the GitHub **username** (e.g. `Chunkuotriumph`), NOT the email. Using email may still work but username is canonical.
### Session cookie domain
After successful OAuth, the session cookie is set for the **target service domain** (e.g. `gorouter.app`), not `github.com`. Use the same `requests.Session()` throughout the flow.
### Timestamp fields are dynamic
`timestamp` and `timestamp_secret` change on every page load. Always parse them fresh — never reuse from a previous attempt.
## When to Use
- Validating GitHub credentials against new-api/one-api services
- Programmatic login when browser is unavailable
- Checking if a GitHub account has 2FA before attempting full browser flow
- Batch checking which accounts in a pool can access a service