feat: Implement browser-based OAuth authentication using Chromedp and Rod, add an upload page, and introduce team processing API.

This commit is contained in:
2026-02-02 04:19:53 +08:00
parent 3f1edc0b8f
commit c9e7a9adbf
5 changed files with 460 additions and 72 deletions

View File

@@ -134,6 +134,23 @@ func (r *RodAuth) Close() {
// CompleteOAuth 完成 OAuth 授权
func (r *RodAuth) CompleteOAuth(authURL, email, password, teamID string) (string, error) {
return r.CompleteOAuthLogged(authURL, email, password, teamID, nil)
}
// CompleteOAuthLogged 完成 OAuth 授权(带日志回调)
func (r *RodAuth) CompleteOAuthLogged(authURL, email, password, teamID string, logger *AuthLogger) (string, error) {
// 日志辅助函数
logStep := func(step AuthStep, format string, args ...interface{}) {
if logger != nil {
logger.LogStep(step, format, args...)
}
}
logError := func(step AuthStep, format string, args ...interface{}) {
if logger != nil {
logger.LogError(step, format, args...)
}
}
// Handle proxy auth (407) in headless mode.
// When Fetch domain is enabled without patterns, requests will be paused and must be continued.
// 只在代理需要认证时才启用 Fetch 域
@@ -194,24 +211,31 @@ func (r *RodAuth) CompleteOAuth(authURL, email, password, teamID string) (string
// 增加超时时间到 90 秒
page = page.Timeout(90 * time.Second)
logStep(StepNavigate, "正在访问授权页面...")
if err := page.Navigate(authURL); err != nil {
logError(StepNavigate, "访问失败: %v", err)
return "", fmt.Errorf("访问授权URL失败: %v", err)
}
page.MustWaitDOMStable()
if code := r.checkForCode(page); code != "" {
logStep(StepExtractCode, "已捕获授权码回调")
return code, nil
}
logStep(StepInputEmail, "正在查找邮箱输入框...")
emailInput, err := page.Timeout(5 * time.Second).Element("input[name='email'], input[type='email'], input[name='username']")
if err != nil {
logError(StepInputEmail, "未找到邮箱输入框")
return "", fmt.Errorf("未找到邮箱输入框")
}
emailInput.MustSelectAllText().MustInput(email)
logStep(StepInputEmail, "已输入邮箱")
time.Sleep(200 * time.Millisecond)
logStep(StepSubmitEmail, "正在提交邮箱...")
if btn, _ := page.Timeout(2 * time.Second).Element("button[type='submit']"); btn != nil {
btn.MustClick()
}
@@ -219,25 +243,32 @@ func (r *RodAuth) CompleteOAuth(authURL, email, password, teamID string) (string
time.Sleep(1500 * time.Millisecond)
if code := r.checkForCode(page); code != "" {
logStep(StepExtractCode, "已获取授权码")
return code, nil
}
logStep(StepInputPassword, "正在查找密码输入框...")
passwordInput, err := page.Timeout(8 * time.Second).Element("input[type='password']")
if err != nil {
logError(StepInputPassword, "未找到密码输入框")
return "", fmt.Errorf("未找到密码输入框")
}
passwordInput.MustSelectAllText().MustInput(password)
logStep(StepInputPassword, "已输入密码")
time.Sleep(200 * time.Millisecond)
logStep(StepSubmitPassword, "正在提交密码...")
if btn, _ := page.Timeout(2 * time.Second).Element("button[type='submit']"); btn != nil {
btn.MustClick()
}
logStep(StepWaitCallback, "等待授权回调...")
for i := 0; i < 66; i++ {
time.Sleep(300 * time.Millisecond)
if code := r.checkForCode(page); code != "" {
logStep(StepComplete, "授权完成")
return code, nil
}
@@ -245,12 +276,14 @@ func (r *RodAuth) CompleteOAuth(authURL, email, password, teamID string) (string
currentURL := info.URL
if strings.Contains(currentURL, "consent") {
logStep(StepConsent, "正在处理授权同意页面...")
if btn, _ := page.Timeout(500 * time.Millisecond).Element("button[type='submit']"); btn != nil {
btn.Click(proto.InputMouseButtonLeft, 1)
}
}
if strings.Contains(currentURL, "authorize") && teamID != "" {
logStep(StepSelectWorkspace, "正在选择工作区: %s", teamID)
wsSelector := fmt.Sprintf("[data-workspace-id='%s'], [data-account-id='%s']", teamID, teamID)
if wsBtn, _ := page.Timeout(500 * time.Millisecond).Element(wsSelector); wsBtn != nil {
wsBtn.Click(proto.InputMouseButtonLeft, 1)
@@ -258,6 +291,7 @@ func (r *RodAuth) CompleteOAuth(authURL, email, password, teamID string) (string
}
}
logError(StepWaitCallback, "授权超时")
return "", fmt.Errorf("授权超时")
}
@@ -275,13 +309,33 @@ func (r *RodAuth) checkForCode(page *rod.Page) string {
// CompleteWithRod 使用 Rod + Stealth 完成 S2A 授权
func CompleteWithRod(authURL, email, password, teamID string, headless bool, proxy string) (string, error) {
return CompleteWithRodLogged(authURL, email, password, teamID, headless, proxy, nil)
}
// CompleteWithRodLogged 使用 Rod + Stealth 完成 S2A 授权(带日志回调)
func CompleteWithRodLogged(authURL, email, password, teamID string, headless bool, proxy string, logger *AuthLogger) (string, error) {
// 日志辅助函数
logStep := func(step AuthStep, format string, args ...interface{}) {
if logger != nil {
logger.LogStep(step, format, args...)
}
}
logError := func(step AuthStep, format string, args ...interface{}) {
if logger != nil {
logger.LogError(step, format, args...)
}
}
logStep(StepBrowserStart, "正在启动 Rod 浏览器...")
auth, err := NewRodAuth(headless, proxy)
if err != nil {
logError(StepBrowserStart, "启动失败: %v", err)
return "", err
}
defer auth.Close()
return auth.CompleteOAuth(authURL, email, password, teamID)
logStep(StepBrowserStart, "浏览器启动成功")
return auth.CompleteOAuthLogged(authURL, email, password, teamID, logger)
}
// CompleteWithBrowser 使用 Rod 完成 S2A 授权 (别名)