113 lines
3.9 KiB
Markdown
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
|