eCourt-Doc-Edit/scripts/dev-token.py

115 lines
4.4 KiB
Python
Raw Normal View History

2026-08-19 23:06:57 +07:00
#!/usr/bin/env python3
"""ขอ access token แบบ PKCE (authorization code flow) จาก Keycloak dev — สำหรับทดสอบ backend ผ่าน curl/Swagger.
ทำไมต้องมีสคริปต์นี้: ทุก client ใน realm ปิด direct grants บังคับ PKCE ตาม spec
ส่วน token ของ `admin-cli` ไม่มี claim `aud` จึงเรียก backend ไม่ผ่าน (401)
วิธีใช้:
python3 scripts/dev-token.py # ปริ้น access token
python3 scripts/dev-token.py --clip # คัดลอกเข้า clipboard (macOS)
แก้ user/password ผ่าน env: DEV_TOKEN_USERNAME / DEV_TOKEN_PASSWORD (default testuser/Test1234!)
"""
import argparse
import base64
import hashlib
import http.cookiejar
import json
import os
import re
import ssl
import sys
import urllib.error
import urllib.parse
import urllib.request
REALM = "https://localhost:8443/realms/word-versioning"
CLIENT_ID = "word-versioning-web"
REDIRECT_URI = "http://localhost:5173/callback" # ไม่ต้องมี server จริง — แค่ต้องตรงกับที่ลงทะเบียนไว้
CA_FILE = os.path.expanduser("~/.office-addin-dev-certs/ca.crt")
USERNAME = os.environ.get("DEV_TOKEN_USERNAME", "testuser")
PASSWORD = os.environ.get("DEV_TOKEN_PASSWORD", "Test1234!")
class NoRedirect(urllib.request.HTTPRedirectHandler):
"""หยุดที่ 302 เพื่อเก็บ authorization code จาก Location"""
def redirect_request(self, req, fp, code, msg, headers, newurl): # noqa: ARG002
return None
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--clip", action="store_true", help="คัดลอก token เข้า clipboard (pbcopy)")
args = parser.parse_args()
verifier = base64.urlsafe_b64encode(os.urandom(48)).rstrip(b"=").decode()
challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode()
ctx = ssl.create_default_context(cafile=CA_FILE)
opener = urllib.request.build_opener(
urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()),
NoRedirect,
urllib.request.HTTPSHandler(context=ctx),
)
auth_url = f"{REALM}/protocol/openid-connect/auth?" + urllib.parse.urlencode(
{
"client_id": CLIENT_ID,
"redirect_uri": REDIRECT_URI,
"response_type": "code",
"scope": "openid",
"state": "dev-token",
"code_challenge": challenge,
"code_challenge_method": "S256",
}
)
login_page = opener.open(auth_url).read().decode()
action = re.search(r'action="([^"]+)"', login_page)
if not action:
print("ไม่พบ login form — ตรวจ Keycloak ที่ https://localhost:8443", file=sys.stderr)
return 1
credentials = urllib.parse.urlencode(
{"username": USERNAME, "password": PASSWORD, "credentialId": ""}
).encode()
try:
opener.open(urllib.request.Request(action.group(1).replace("&", "&"), data=credentials))
print("login ไม่ redirect — ตรวจ username/password", file=sys.stderr)
return 1
except urllib.error.HTTPError as error:
location = error.headers.get("Location", "")
code = urllib.parse.parse_qs(urllib.parse.urlparse(location).query).get("code", [None])[0]
if not code:
print("ไม่ได้รับ authorization code (redirect ผิดหรือ login พลาด)", file=sys.stderr)
return 1
token_body = urllib.parse.urlencode(
{
"grant_type": "authorization_code",
"client_id": CLIENT_ID,
"code": code,
"redirect_uri": REDIRECT_URI,
"code_verifier": verifier,
}
).encode()
token_response = json.loads(
opener.open(urllib.request.Request(f"{REALM}/protocol/openid-connect/token", data=token_body)).read()
)
token = token_response["access_token"]
if args.clip:
import subprocess
subprocess.run("pbcopy", input=token.encode(), check=True)
print(f"คัดลอก access token ({len(token)} chars) เข้า clipboard แล้ว")
else:
print(token)
return 0
if __name__ == "__main__":
sys.exit(main())