Files
1jehuang--jcode/scripts/oauth_helper.py
T
wehub-resource-sync a789495a98
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:34 +08:00

104 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""Helper script to complete Claude OAuth flow with proper PKCE."""
import base64
import hashlib
import json
import os
import secrets
import sys
import urllib.parse
import requests
CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"
AUTHORIZE_URL = "https://claude.ai/oauth/authorize"
TOKEN_URL = "https://console.anthropic.com/v1/oauth/token"
REDIRECT_URI = "https://console.anthropic.com/oauth/code/callback"
SCOPES = "org:create_api_key user:profile user:inference"
def generate_pkce():
"""Generate PKCE verifier and challenge."""
verifier = secrets.token_urlsafe(48)[:64] # 64 chars
digest = hashlib.sha256(verifier.encode()).digest()
challenge = base64.urlsafe_b64encode(digest).rstrip(b'=').decode()
return verifier, challenge
def generate_state():
"""Generate random state for CSRF protection."""
return secrets.token_hex(16)
def main():
if len(sys.argv) > 1 and sys.argv[1] == "--exchange":
# Exchange mode: read code from stdin or arg
code = sys.argv[2] if len(sys.argv) > 2 else input("Enter code: ").strip()
# Load saved state
with open("/tmp/claude_oauth_state.json") as f:
saved = json.load(f)
# Exchange code for tokens
resp = requests.post(TOKEN_URL, data={
"grant_type": "authorization_code",
"client_id": CLIENT_ID,
"code": code,
"code_verifier": saved["verifier"],
"redirect_uri": REDIRECT_URI,
})
if resp.status_code != 200:
print(f"Error: {resp.text}")
sys.exit(1)
tokens = resp.json()
print(f"\nAccess token: {tokens['access_token'][:50]}...")
print(f"Refresh token: {tokens['refresh_token'][:50]}...")
# Save in Claude Code format
creds_dir = os.path.expanduser("~/.claude")
os.makedirs(creds_dir, exist_ok=True)
import time
expires_at = int(time.time() * 1000) + (tokens["expires_in"] * 1000)
creds = {
"claudeAiOauth": {
"accessToken": tokens["access_token"],
"refreshToken": tokens["refresh_token"],
"expiresAt": expires_at,
}
}
with open(os.path.join(creds_dir, ".credentials.json"), "w") as f:
json.dump(creds, f, indent=2)
print(f"\nCredentials saved to ~/.claude/.credentials.json")
else:
# Generate new auth URL
verifier, challenge = generate_pkce()
state = generate_state()
params = {
"response_type": "code",
"client_id": CLIENT_ID,
"redirect_uri": REDIRECT_URI,
"scope": SCOPES,
"code_challenge": challenge,
"code_challenge_method": "S256",
"state": state,
}
auth_url = f"{AUTHORIZE_URL}?{urllib.parse.urlencode(params)}"
with open("/tmp/claude_oauth_state.json", "w") as f:
json.dump({
"verifier": verifier,
"challenge": challenge,
"state": state,
"auth_url": auth_url
}, f)
print(f"Auth URL: {auth_url}")
print(f"\nState saved to /tmp/claude_oauth_state.json")
print(f"Verifier: {verifier}")
if __name__ == "__main__":
main()