forked from 1jehuang/jcode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathoauth_helper.py
More file actions
103 lines (86 loc) · 3.25 KB
/
oauth_helper.py
File metadata and controls
103 lines (86 loc) · 3.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#!/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()