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:
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user