87 lines
3.0 KiB
Python
87 lines
3.0 KiB
Python
"""GitHub OAuth 授权码模式端点(SPA 友好的 JSON 流程)。
|
||
|
||
流程:前端调 authorize 拿 authorize_url → 浏览器跳转 GitHub →
|
||
GitHub 回跳前端页(带 code+state)→ 前端调 callback 换平台 token。
|
||
"""
|
||
import logging
|
||
import secrets
|
||
from urllib.parse import urlencode
|
||
|
||
from chongming_worker import rpc_method
|
||
|
||
from ..models import GithubAuthorizeGetResponse, GithubCallbackPostRequest, LoginPostResponse
|
||
from ..providers import github as github_provider
|
||
from .. import store
|
||
|
||
logger = logging.getLogger("plugins.auth_oauth.github")
|
||
|
||
_STATE_TTL = 300 # state 有效期(秒),一次性消费防 CSRF
|
||
|
||
|
||
@rpc_method(
|
||
subject="auth_oauth.github_authorize",
|
||
method="GET",
|
||
summary="GitHub 授权登录:生成授权跳转 URL(state 存缓存防 CSRF)",
|
||
)
|
||
async def github_authorize() -> GithubAuthorizeGetResponse:
|
||
from ..global_var import cache, plugin_config
|
||
|
||
gh = plugin_config.get("github") or {}
|
||
client_id = gh.get("client_id", "")
|
||
if not client_id or client_id.startswith("your-"):
|
||
return GithubAuthorizeGetResponse(
|
||
result="fail", authorize_url="",
|
||
state="GitHub OAuth 未配置(config/workers/auth_oauth/config.toml 的 [workers.config.github])",
|
||
)
|
||
|
||
state = secrets.token_urlsafe(24)
|
||
cache.set(f"auth_oauth:state:{state}", "1", expire=_STATE_TTL)
|
||
|
||
params = urlencode({
|
||
"client_id": client_id,
|
||
"redirect_uri": gh.get("redirect_uri", ""),
|
||
"scope": gh.get("scope", "read:user user:email"),
|
||
"state": state,
|
||
})
|
||
return GithubAuthorizeGetResponse(
|
||
result="success",
|
||
authorize_url=f"https://github.com/login/oauth/authorize?{params}",
|
||
state=state,
|
||
)
|
||
|
||
|
||
@rpc_method(
|
||
subject="auth_oauth.github_callback",
|
||
method="POST",
|
||
summary="GitHub 授权回调:校验 state、换取用户信息,find-or-create 后签发平台 token",
|
||
)
|
||
async def github_callback(req: GithubCallbackPostRequest) -> LoginPostResponse:
|
||
from ..global_var import cache, db, plugin_config
|
||
|
||
if db is None or cache is None:
|
||
return store.fail_response("插件未初始化")
|
||
|
||
# state 一次性消费(防 CSRF / 重放)
|
||
state_key = f"auth_oauth:state:{req.state}"
|
||
if not cache.get(state_key):
|
||
return store.fail_response("state 无效或已过期,请重新发起授权")
|
||
cache.delete(state_key)
|
||
|
||
gh = plugin_config.get("github") or {}
|
||
try:
|
||
access_token = await github_provider.exchange_code(
|
||
gh.get("client_id", ""), gh.get("client_secret", ""),
|
||
req.code, gh.get("redirect_uri", ""),
|
||
)
|
||
profile = await github_provider.fetch_user(access_token)
|
||
except RuntimeError as e:
|
||
logger.warning("GitHub 回调失败: %s", e)
|
||
return store.fail_response(str(e))
|
||
|
||
user, message = await store.find_or_create_user(
|
||
db, "github", profile["uid"], profile
|
||
)
|
||
if user is None:
|
||
return store.fail_response(message)
|
||
return store.issue_login_response(user)
|