autoreg-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user