Merge pull request #64 from DingTalk-Real-AI/feat-auth-improvement

fix(auth): prevent auth code expiration error on OAuth callback page …
This commit is contained in:
fantiu
2026-04-01 14:54:30 +08:00
committed by GitHub
2 changed files with 58 additions and 28 deletions
+4 -4
View File
@@ -339,9 +339,9 @@ select:focus{outline:none;border-color:#1890ff;box-shadow:0 0 0 2px rgba(24,144,
.link:hover{text-decoration:underline}
.success-msg{display:none;width:100%;min-height:36px;gap:12px;padding:8px 12px;margin-top:20px;
background:linear-gradient(0deg,rgba(0,102,255,0.12) 0%,rgba(0,102,255,0.12) 100%),linear-gradient(0deg,#FFFFFF 0%,#FFFFFF 100%);
border-radius:12px;align-items:flex-start;justify-content:flex-start}
.success-msg-icon{width:16px;height:16px;flex-shrink:0;margin-top:3px}
.success-msg-text{flex:1;color:#181C1F;font-size:14px;line-height:22px}
border-radius:12px;align-items:center;justify-content:flex-start}
.success-msg-icon{width:16px;height:16px;flex-shrink:0}
.success-msg-text{flex:1;color:#181C1F;font-size:14px;line-height:22px;text-align:left}
.error-msg{color:#ff4d4f;font-size:14px;margin-top:8px;display:none}
.loading{display:inline-block;width:16px;height:16px;border:2px solid #fff;border-top-color:transparent;
border-radius:50%;animation:spin 0.8s linear infinite;margin-right:8px;vertical-align:middle}
@@ -352,7 +352,7 @@ select:focus{outline:none;border-color:#1890ff;box-shadow:0 0 0 2px rgba(24,144,
<h1>该组织尚未开启 CLI 数据访问权限</h1>
<p>你所选择的组织管理员尚未开启「允许成员通过 CLI 访问其个人数据」的权限。</p>
<div class="form-group">
<label class="form-label">你将使用以下账号授权登录</label>
<label class="form-label">选择一位主管理员发送开通申请</label>
<div class="select-wrapper">
<select id="adminSelect"><option value="">加载中...</option></select>
<div class="select-arrow"></div>
+54 -24
View File
@@ -131,10 +131,11 @@ func (p *OAuthProvider) Login(ctx context.Context, force bool) (*TokenData, erro
// Shared state for API handlers (protected by mutex)
var (
callbackToken *TokenData
callbackProcessed bool
callbackProcessedCode string // The auth code that has been successfully processed
callbackAuthDisabled bool
callbackApplySent bool // Whether apply request was sent
callbackSelectedAdminId string // Selected admin ID for apply
callbackCodeInProgress string // Code currently being processed (to prevent concurrent exchange)
callbackTokenMu sync.Mutex
)
@@ -146,23 +147,48 @@ func (p *OAuthProvider) Login(ctx context.Context, force bool) (*TokenData, erro
code = r.URL.Query().Get("code")
}
// Check if this is a page refresh (no code) and callback was already processed
// Check state and handle page refresh or concurrent requests
callbackTokenMu.Lock()
if code == "" && callbackProcessed {
processedCode := callbackProcessedCode
processedAuthDisabled := callbackAuthDisabled
codeInProgress := callbackCodeInProgress
hasToken := callbackToken != nil
// Case 1: This code was already successfully processed - show cached page
if code != "" && code == processedCode {
callbackTokenMu.Unlock()
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if callbackAuthDisabled {
if processedAuthDisabled {
_, _ = fmt.Fprint(w, notEnabledHTML)
} else {
_, _ = fmt.Fprint(w, successHTML)
}
callbackTokenMu.Unlock()
return
}
// Reset state for new authorization (user switched org)
if code != "" && callbackProcessed {
callbackProcessed = false
callbackApplySent = false
callbackSelectedAdminId = ""
// Case 2: This code is being processed by another request - show wait page
if code != "" && code == codeInProgress {
callbackTokenMu.Unlock()
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = fmt.Fprint(w, `<html><head><meta http-equiv="refresh" content="1"></head><body><p>正在处理授权,请稍候...</p></body></html>`)
return
}
// Case 3: No code but we have a processed token - show cached page
if code == "" && hasToken {
callbackTokenMu.Unlock()
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if processedAuthDisabled {
_, _ = fmt.Fprint(w, notEnabledHTML)
} else {
_, _ = fmt.Fprint(w, successHTML)
}
return
}
// Case 4: New code - mark as in-progress and process
if code != "" {
callbackCodeInProgress = code
}
callbackTokenMu.Unlock()
@@ -176,20 +202,13 @@ func (p *OAuthProvider) Login(ctx context.Context, force bool) (*TokenData, erro
return
}
// Exchange code for token immediately in callback
// Exchange code for token
tokenData, exchangeErr := p.exchangeCode(ctx, code)
if exchangeErr != nil {
// Check if we already have a processed state (authCode reused on refresh)
// Clear in-progress state on error
callbackTokenMu.Lock()
if callbackProcessed {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if callbackAuthDisabled {
_, _ = fmt.Fprint(w, notEnabledHTML)
} else {
_, _ = fmt.Fprint(w, successHTML)
}
callbackTokenMu.Unlock()
return
if callbackCodeInProgress == code {
callbackCodeInProgress = ""
}
callbackTokenMu.Unlock()
@@ -202,14 +221,25 @@ func (p *OAuthProvider) Login(ctx context.Context, force bool) (*TokenData, erro
return
}
// Mark as processed immediately after successful exchange
callbackTokenMu.Lock()
previouslyProcessed := callbackProcessedCode != ""
callbackToken = tokenData
callbackProcessedCode = code // Remember this code was successfully processed
callbackCodeInProgress = "" // Clear in-progress state
// Reset apply state for new authorization (user switched org)
if previouslyProcessed {
callbackApplySent = false
callbackSelectedAdminId = ""
}
callbackTokenMu.Unlock()
// Check CLI auth enabled status
authStatus, statusErr := p.CheckCLIAuthEnabled(ctx, tokenData.AccessToken)
cliAuthDisabled := statusErr == nil && authStatus.Success && !authStatus.Result.CLIAuthEnabled
// Store token and state for API handlers and refresh handling
// Update CLI auth disabled state
callbackTokenMu.Lock()
callbackToken = tokenData
callbackProcessed = true
callbackAuthDisabled = cliAuthDisabled
callbackTokenMu.Unlock()