-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathauth.py
More file actions
106 lines (89 loc) · 4.45 KB
/
Copy pathauth.py
File metadata and controls
106 lines (89 loc) · 4.45 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
104
105
106
# 修改历史 (Revision History)
# ==================================
# 版本: v1.2
# 日期: 2026-06-24
# 修改说明: 延长缓存 Token 寿命 AUTH_MAX_AGE 至 30 分钟,并重构 load_auth 以在后台静默无头刷新失败时,将过期的缓存凭证作为 Fallback 返回使用,消除静默刷新偶发超时引起的 client.py 报错中断。
# 版本: v1.1
# 日期: 2026-06-24
# 修改说明: 缩短 AUTH_MAX_AGE 至 15 分钟防止 Token 过期,且在 load_auth 获取 Token 时加入 10 秒轮询等待,避免页面刚加载完成、异步 silent refresh 未结束便读取 Token 导致失败的竞争条件。
"""Signed-in session caching for the pure-HTTP path.
Bridges the interactive browser login to the headless :class:`copilot.client.Copilot`
driver: keeps a short-lived snapshot of cookies + MSAL access token on disk and
transparently refreshes it from the persistent browser profile when it goes stale.
"""
import json
import time
from pathlib import Path
from typing import Optional
# All session state (browser profile + cached auth) lives under one folder.
SESSION_DIR = "session"
DEFAULT_PROFILE_DIR = f"{SESSION_DIR}/profile"
DEFAULT_AUTH_FILE = f"{SESSION_DIR}/token.json"
# Microsoft access tokens live ~60-90 min; refresh well before that.
AUTH_MAX_AGE = 30 * 60
def load_auth(
path: str = DEFAULT_AUTH_FILE,
profile_dir: str = DEFAULT_PROFILE_DIR,
max_age: int = AUTH_MAX_AGE,
proxy: Optional[str] = None,
auto_login: bool = True,
) -> dict:
"""Return ``{cookies, access_token, saved_at}`` for the signed-in user.
Uses the cached snapshot at ``path`` while fresh; otherwise spins up a
headless browser against the persistent ``profile_dir`` to read a fresh MSAL
token (the profile stays signed in via its long-lived refresh token) and
re-snapshots.
When the profile is *not* signed in (e.g. first-ever use) and ``auto_login``
is true, this opens a visible browser for interactive Microsoft sign-in
instead of failing — so the very first call just works. Set
``auto_login=False`` (or run headless/CI) to get a ``RuntimeError`` instead.
Intended for the pure-HTTP :class:`copilot.client.Copilot` path::
auth = load_auth()
Copilot().create_completion(..., cookies=auth["cookies"],
access_token=auth["access_token"])
"""
p = Path(path)
cached = None
if p.exists():
try:
cached = json.loads(p.read_text(encoding="utf-8"))
if cached.get("access_token") and (time.time() - cached.get("saved_at", 0)) < max_age:
return cached
except (ValueError, OSError):
pass # corrupt/unreadable -> refresh below
from .browser import BrowserCopilot
# Try a headless read first: a signed-in profile just needs a fresh token.
bot = BrowserCopilot(profile_dir=profile_dir, headless=True, proxy=proxy)
try:
bot.start()
# 轮询最多 10 秒以等待异步的 MSAL 刷新动作在 LocalStorage 中填充 Token
token = None
for _ in range(10):
token = bot.access_token()
if token:
break
time.sleep(1)
if token and not bot.region_blocked():
return bot.export_auth(path=path, stamp=time.time())
finally:
bot.close()
# 如果无头静默刷新失败,但是本地已经有缓存凭证(即使在逻辑上已过期),作为 fallback 降级使用它
if cached and cached.get("access_token"):
print(f"[Warning] Headless silent token refresh failed. Falling back to stale cached token (saved {int(time.time() - cached.get('saved_at', 0))}s ago).")
return cached
# No signed-in session in the profile.
if not auto_login:
raise RuntimeError(
"Not signed in (no access token in the browser profile). "
"Run `python -m copilot login` and sign in first."
)
# First-time use: create the session interactively, then return its auth.
print("No saved Copilot session found — opening a browser to sign in...")
auth = BrowserCopilot(profile_dir=profile_dir, headless=False, proxy=proxy).login(path=path)
if not auth.get("access_token"):
raise RuntimeError(
"Sign-in did not complete (no access token captured). "
"Re-run and finish the Microsoft sign-in before pressing Enter, "
"or sign in manually with `python -m copilot login`."
)
return auth