fix(market): harden Auth login and catalog integration - #2566
Conversation
Walkthrough本次更新将 Market catalog 改为本地同源桥接访问,扩展 OAuth 状态与并发会话处理,补充前端状态展示、多语言文案、错误诊断及大量集成与竞态测试,喵。 ChangesMarket OAuth 与 catalog 桥接
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| baseURL: `${baseUrl}/api/v1`, | ||
| // Keep catalog traffic same-origin. The local bridge forwards only the | ||
| // public, read-only catalog routes and avoids browser CORS failures. | ||
| baseURL: '/market/catalog/api/v1', |
There was a problem hiding this comment.
| if (marketAuth.value.auth_state === 'pending') { | ||
| marketAuthBusy.value = true | ||
| return |
There was a problem hiding this comment.
Pending verification retries forever
When Auth repeatedly returns auth_state: 'pending' with retryable: true, each status request schedules another readiness retry and this branch keeps marketAuthBusy true without the five-minute deadline used by the OAuth polling loop, leaving the account control permanently loading with no way to restart login until the component unmounts喵。
Knowledge Base Used: Frontend Plugin Manager
Greptile Summary此 PR 强化了 Auth/Market 集成并将目录流量迁移到本地同源桥接喵。
Confidence Score: 3/5合并前需要修复开发目录代理和无期限 pending 重试,否则目录加载或认证控件会进入不可用状态喵。 Vite 不会转发前端新使用的目录路径,同时持续返回 pending 的 Auth 状态会无限续订重试并保持忙碌状态,分别破坏开发环境目录访问和认证恢复流程喵。 Files Needing Attention: frontend/plugin-manager/src/api/market.ts, frontend/plugin-manager/vite.config.ts, frontend/plugin-manager/src/composables/useMarketAuth.ts
|
| Filename | Overview |
|---|---|
| plugin/server/routes/market_bridge.py | Adds fixed catalog proxy routes, split Auth/Market state handling, session CAS guards, and sanitized diagnostics喵。 |
| frontend/plugin-manager/src/composables/useMarketAuth.ts | Adds generation-guarded status and retry transitions, but pending Auth verification has no terminal deadline and can leave the UI permanently busy喵。 |
| frontend/plugin-manager/src/api/market.ts | Moves catalog requests to the local bridge, but the new path is not forwarded by the Vite development proxy喵。 |
| frontend/plugin-manager/src/views/PluginList.vue | Displays pending Auth verification and detailed Market readiness states in the account controls喵。 |
| plugin/tests/integration/test_market_bridge_e2e.py | Adds extensive coverage for catalog restrictions, safe diagnostics, identity validation, and OAuth concurrency paths喵。 |
Sequence Diagram
sequenceDiagram
participant UI as Plugin Manager
participant Local as Local Market Bridge
participant Auth as Auth Service
participant Market as Plugin Market
UI->>Local: GET /market/status
UI->>Local: GET /market/catalog/api/v1/plugins
Local->>Market: GET /api/v1/plugins
Market-->>Local: Catalog response
Local-->>UI: Same-origin catalog response
UI->>Local: POST /market/oauth/complete
Local->>Auth: Exchange code and verify userinfo
Auth-->>Local: OIDC identity
Local->>Market: Probe account readiness
Market-->>Local: ready / conflict / unavailable
Local-->>UI: Independent Auth and Market states
Reviews (1): Last reviewed commit: "fix(market): harden auth and catalog int..." | Re-trigger Greptile
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8e5f675c87
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| baseURL: `${baseUrl}/api/v1`, | ||
| // Keep catalog traffic same-origin. The local bridge forwards only the | ||
| // public, read-only catalog routes and avoids browser CORS failures. | ||
| baseURL: '/market/catalog/api/v1', |
There was a problem hiding this comment.
Add the catalog bridge to the Vite proxy
When the plugin manager is run via npm run dev, this new relative base URL is served by Vite first, but the dev proxy in frontend/plugin-manager/vite.config.ts only forwards /market/status, auth, install, installed, token-exchange, and tasks routes; /market/catalog/... is not matched. As a result catalog requests hit the Vite dev server instead of the FastAPI bridge and the Market panel cannot load plugins in local development, even though production same-origin hosting works.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (4)
plugin/server/routes/market_bridge.py (1)
1973-2014: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
_fetch_auth_userinfo自己手搓了一套分类与日志,跟新加的 helper 撞车了喵。分类三元表达式其实就是
_auth_oauth_http_failure_category(只差 400 的处理),日志行也和_log_auth_oauth_failure格式重复;而且res.status_code != 200判断写了两遍。收敛到 helper 能保证operation=字段与其它 Auth 日志一致,后续加字段也只改一处呐。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugin/server/routes/market_bridge.py` around lines 1973 - 2014, Update _fetch_auth_userinfo to use _auth_oauth_http_failure_category for non-200 response classification and _log_auth_oauth_failure for the warning, ensuring the log includes the helper’s standard operation= field. Remove the duplicated status checks while preserving 401/403 rejection and returning None for other non-200 responses; keep invalid JSON handling aligned with the shared helper where applicable.frontend/plugin-manager/src/api/market.ts (1)
247-253: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick wincatalog 已经走同源桥接了,却还被远端
market_url卡着门呐喵~
baseURL现在固定是本地桥接路径,但getClient()依旧在getMarketBaseUrl()返回null时直接放弃。于是/market/status超时(3s)或没有market_url时,目录请求根本不会发出,后端market_catalog_not_configured/market_catalog_unavailable的结构化错误也就永远看不到啦。建议只用它预热 web URL 缓存,不要拿它当 catalog 的开关喵。♻️ 建议改动
- const configuredMarketUrl = await getMarketBaseUrl() - if (!configuredMarketUrl) return null - + // 只用于预热 market_web_url 缓存;catalog 走同源桥接,可用性由后端判定 + await getMarketBaseUrl() + _marketClient = axios.create({ // Keep catalog traffic same-origin. The local bridge forwards only the // public, read-only catalog routes and avoids browser CORS failures. baseURL: '/market/catalog/api/v1',🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/plugin-manager/src/api/market.ts` around lines 247 - 253, Update getClient so a null result from getMarketBaseUrl does not prevent creating the catalog client; use getMarketBaseUrl only to warm the web URL cache, while preserving the fixed same-origin baseURL and allowing catalog requests to reach the bridge and return structured backend errors.frontend/plugin-manager/src/views/PluginList.vue (1)
54-62: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value这个按钮点了什么都不会发生,对键盘用户不太友好喵。
它没有
@click,只是个状态指示器,却仍然可聚焦、可点击。建议加disabled(或直接换成el-tag/文本 + spinner)并补aria-live,别让人家白按一场啦~♻️ 小改法
<el-button v-if="marketUrl && marketAuth.auth_state === 'pending'" class="market-auth-trigger" :loading="true" + disabled + aria-live="polite" plain >🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/plugin-manager/src/views/PluginList.vue` around lines 54 - 62, Update the pending market-auth button in PluginList.vue so it is non-interactive by adding the appropriate disabled state, preventing keyboard users from activating a control with no click behavior. Add an aria-live announcement for the pending verification status while preserving the existing loading indicator and label.frontend/plugin-manager/src/composables/useMarketAuth.ts (1)
205-216: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win未知
market_state会让t(undefined)上桌,建议加个兜底文案喵。
marketAuth.value直接由res.json()原样赋值,后端将来新增一个MarketOAuthState值时,marketAuthStateMessageKeys[state]是undefined,这里就会t(undefined)。第 75-81 行的 computed 同理(返回 undefined,模板刚好被v-else-if挡住,算运气好)。🛡️ 兜底建议
- ElMessage.warning(t(marketAuthStateMessageKeys[state])) + const key = marketAuthStateMessageKeys[state] + ElMessage.warning(key ? t(key) : t('market.loginFailed'))🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/plugin-manager/src/composables/useMarketAuth.ts` around lines 205 - 216, 在 notifyMarketAuthState 中处理未知的 market_state:仅当 state 存在且能在 marketAuthStateMessageKeys 中找到对应文案时调用 t,否则使用明确的默认提示文案。同步更新第 75-81 行附近的 computed 逻辑,确保未知状态返回有效的兜底文案而不是 undefined。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/plugin-manager/src/composables/useMarketAuth.ts`:
- Around line 249-259: Update the readiness retry flow around
scheduleMarketReadinessRetry and the market_state === 'ready' branch so
ElMessage.success(t('market.loginSuccess')) is shown only for retries initiated
by the login flow. Propagate a notifyOnReady indicator through the retry chain,
leaving automatic recovery from initial status loading silent while preserving
account-summary loading and authentication checks.
- Around line 142-168: Update loadMarketAuthStatus and its
scheduleMarketReadinessRetry flow to cap readiness retries and apply exponential
backoff instead of retrying every five seconds indefinitely. Track and reset the
retry state on a successful status load, preserve generation checks, and stop
scheduling once the maximum retry count is reached, including for
unauthenticated users and network/5xx failures.
In `@plugin/server/routes/market_bridge.py`:
- Around line 907-908: Update the locale selection logic near accept_language to
parse Accept-Language entries, honor their q weights and ordering, select the
highest-priority supported language, and then map its primary tag to zh-CN, ja,
or en instead of using substring checks.
- Around line 1004-1033: Update the successful subject branch in the OAuth
status flow to persist subject, subject_pending, and auth_state through
_write_oauth_token_if_matches immediately, before calling
_fresh_cached_market_user. Refresh token_snapshot/token_data from the successful
CAS result for subsequent writes, and preserve the existing conflict response
behavior if persistence fails.
---
Nitpick comments:
In `@frontend/plugin-manager/src/api/market.ts`:
- Around line 247-253: Update getClient so a null result from getMarketBaseUrl
does not prevent creating the catalog client; use getMarketBaseUrl only to warm
the web URL cache, while preserving the fixed same-origin baseURL and allowing
catalog requests to reach the bridge and return structured backend errors.
In `@frontend/plugin-manager/src/composables/useMarketAuth.ts`:
- Around line 205-216: 在 notifyMarketAuthState 中处理未知的 market_state:仅当 state
存在且能在 marketAuthStateMessageKeys 中找到对应文案时调用 t,否则使用明确的默认提示文案。同步更新第 75-81 行附近的
computed 逻辑,确保未知状态返回有效的兜底文案而不是 undefined。
In `@frontend/plugin-manager/src/views/PluginList.vue`:
- Around line 54-62: Update the pending market-auth button in PluginList.vue so
it is non-interactive by adding the appropriate disabled state, preventing
keyboard users from activating a control with no click behavior. Add an
aria-live announcement for the pending verification status while preserving the
existing loading indicator and label.
In `@plugin/server/routes/market_bridge.py`:
- Around line 1973-2014: Update _fetch_auth_userinfo to use
_auth_oauth_http_failure_category for non-200 response classification and
_log_auth_oauth_failure for the warning, ensuring the log includes the helper’s
standard operation= field. Remove the duplicated status checks while preserving
401/403 rejection and returning None for other non-200 responses; keep invalid
JSON handling aligned with the shared helper where applicable.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 9511bbd5-1466-4a9e-8be3-e8f3e42c0d16
📒 Files selected for processing (15)
frontend/plugin-manager/src/api/market.test.tsfrontend/plugin-manager/src/api/market.tsfrontend/plugin-manager/src/composables/useMarketAuth.test.tsfrontend/plugin-manager/src/composables/useMarketAuth.tsfrontend/plugin-manager/src/i18n/locales/en-US.tsfrontend/plugin-manager/src/i18n/locales/es.tsfrontend/plugin-manager/src/i18n/locales/ja.tsfrontend/plugin-manager/src/i18n/locales/ko.tsfrontend/plugin-manager/src/i18n/locales/pt.tsfrontend/plugin-manager/src/i18n/locales/ru.tsfrontend/plugin-manager/src/i18n/locales/zh-CN.tsfrontend/plugin-manager/src/i18n/locales/zh-TW.tsfrontend/plugin-manager/src/views/PluginList.vueplugin/server/routes/market_bridge.pyplugin/tests/integration/test_market_bridge_e2e.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Project-N-E-K-O/N.E.K.O.-PC(manual)
| async function loadMarketAuthStatus(): Promise<boolean> { | ||
| const generation = ++marketAuthStatusGeneration | ||
| const token = await ensureBridgeToken({ forceRefresh: true }) | ||
| if (!token) return | ||
| if (!token || generation !== marketAuthStatusGeneration) return false | ||
| try { | ||
| const res = await authedFetch('/market/oauth/status') | ||
| if (!res.ok) return | ||
| marketAuth.value = await res.json() | ||
| if (!res.ok || generation !== marketAuthStatusGeneration) { | ||
| if (res.status >= 500 && generation === marketAuthStatusGeneration) { | ||
| scheduleMarketReadinessRetry() | ||
| } | ||
| return false | ||
| } | ||
| const status = await res.json() | ||
| if (generation !== marketAuthStatusGeneration) return false | ||
| marketAuth.value = status | ||
| if (status.retryable) { | ||
| scheduleMarketReadinessRetry() | ||
| } | ||
| return true | ||
| } catch { | ||
| // 登录态只是增强能力,失败不影响 Market 浏览和安装。 | ||
| if (generation === marketAuthStatusGeneration) { | ||
| scheduleMarketReadinessRetry() | ||
| } | ||
| return false | ||
| } | ||
| } |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
就绪重试没有上限也没有退避,喵,后端挂掉时会永远每 5 秒敲一次门哦~
PluginList.vue 第 769 行在挂载时无条件调用 loadMarketAuthStatus(),所以即使用户从未登录,只要 /market/oauth/status 返回 5xx 或网络异常,就会进入 scheduleMarketReadinessRetry() → 5 秒后再失败 → 再调度的无限循环(每次都还会 forceRefresh 重取 bridge-token,即两个请求)。建议加上最大重试次数与指数退避,笨蛋开发者不要让本喵的电脑一直发热啦。
♻️ 参考改法(次数上限 + 退避)
+ let marketReadinessRetryAttempts = 0
+ const MARKET_READINESS_MAX_RETRIES = 6
function stopMarketReadinessRetry(): void {
marketAuthStatusGeneration += 1
+ marketReadinessRetryAttempts = 0
if (marketReadinessRetryTimer !== null) {
clearTimeout(marketReadinessRetryTimer)
marketReadinessRetryTimer = null
}
}
function scheduleMarketReadinessRetry(): void {
+ const attempt = marketReadinessRetryAttempts + 1
+ if (attempt > MARKET_READINESS_MAX_RETRIES) return
stopMarketReadinessRetry()
+ marketReadinessRetryAttempts = attempt
- marketReadinessRetryTimer = window.setTimeout(async () => {
+ const delay = Math.min(5000 * 2 ** (attempt - 1), 60000)
+ marketReadinessRetryTimer = window.setTimeout(async () => {
...
- }, 5000)
+ }, delay)
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/plugin-manager/src/composables/useMarketAuth.ts` around lines 142 -
168, Update loadMarketAuthStatus and its scheduleMarketReadinessRetry flow to
cap readiness retries and apply exponential backoff instead of retrying every
five seconds indefinitely. Track and reset the retry state on a successful
status load, preserve generation checks, and stop scheduling once the maximum
retry count is reached, including for unauthenticated users and network/5xx
failures.
| marketAuthBusy.value = false | ||
| if (!marketAuth.value.authenticated) return | ||
| if (marketAuth.value.market_state === 'ready') { | ||
| const appliedGeneration = marketAuthStatusGeneration | ||
| await loadMarketAccountSummary() | ||
| if ( | ||
| appliedGeneration !== marketAuthStatusGeneration | ||
| || !marketAuth.value.authenticated | ||
| ) return | ||
| ElMessage.success(t('market.loginSuccess')) | ||
| return |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
后台恢复也弹「登录成功」,用户会一脸问号的喵。
这个分支不区分「用户刚点了登录」和「页面挂载时状态请求失败后自动恢复」。测试 useMarketAuth.test.ts 第 216-223 行正是后者却断言 market.loginSuccess——用户什么都没点却收到"授权登录已完成",怪怪的喵。建议只在由登录流程触发的重试链上提示成功(例如给 scheduleMarketReadinessRetry 传一个 notifyOnReady 标记)。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/plugin-manager/src/composables/useMarketAuth.ts` around lines 249 -
259, Update the readiness retry flow around scheduleMarketReadinessRetry and the
market_state === 'ready' branch so ElMessage.success(t('market.loginSuccess'))
is shown only for retries initiated by the login flow. Propagate a notifyOnReady
indicator through the retry chain, leaving automatic recovery from initial
status loading silent while preserving account-summary loading and
authentication checks.
| accept_language = request.headers.get("accept-language", "").lower() | ||
| locale = "zh-CN" if "zh" in accept_language else "ja" if "ja" in accept_language else "en" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Accept-Language 用子串匹配会选错语言喵!
"zh" in accept_language 不看权重也不看位置,en-GB,zh-TW;q=0.1 会渲染成中文页面。按 q 值取最优标签再取主语言比较稳妥呐。
🌐 建议改动
- accept_language = request.headers.get("accept-language", "").lower()
- locale = "zh-CN" if "zh" in accept_language else "ja" if "ja" in accept_language else "en"
+ accept_language = request.headers.get("accept-language", "").lower()
+ preferred, best_q = "", -1.0
+ for part in accept_language.split(","):
+ tag, _, params = part.strip().partition(";")
+ if not tag:
+ continue
+ quality = 1.0
+ if params.strip().startswith("q="):
+ try:
+ quality = float(params.strip()[2:])
+ except ValueError:
+ quality = 0.0
+ if quality > best_q:
+ preferred, best_q = tag, quality
+ locale = {"zh": "zh-CN", "ja": "ja"}.get(preferred.split("-")[0], "en")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| accept_language = request.headers.get("accept-language", "").lower() | |
| locale = "zh-CN" if "zh" in accept_language else "ja" if "ja" in accept_language else "en" | |
| accept_language = request.headers.get("accept-language", "").lower() | |
| preferred, best_q = "", -1.0 | |
| for part in accept_language.split(","): | |
| tag, _, params = part.strip().partition(";") | |
| if not tag: | |
| continue | |
| quality = 1.0 | |
| if params.strip().startswith("q="): | |
| try: | |
| quality = float(params.strip()[2:]) | |
| except ValueError: | |
| quality = 0.0 | |
| if quality > best_q: | |
| preferred, best_q = tag, quality | |
| locale = {"zh": "zh-CN", "ja": "ja"}.get(preferred.split("-")[0], "en") |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@plugin/server/routes/market_bridge.py` around lines 907 - 908, Update the
locale selection logic near accept_language to parse Accept-Language entries,
honor their q weights and ordering, select the highest-priority supported
language, and then map its primary tag to zh-CN, ja, or en instead of using
substring checks.
| if subject: | ||
| token_data["subject"] = subject | ||
| token_data["subject_pending"] = False | ||
| token_data["auth_state"] = "ready" | ||
| else: | ||
| token_data["auth_state"] = "pending" | ||
| token_data["updated_at"] = time.time() | ||
| if not _write_oauth_token_if_matches(token_snapshot, token_data): | ||
| return MarketOAuthStatusResponse( | ||
| authenticated=False, | ||
| market_web_url=MARKET_WEB_URL, | ||
| ) | ||
| return MarketOAuthStatusResponse( | ||
| authenticated=False, | ||
| auth_state="pending", | ||
| retryable=True, | ||
| expires_at=token_data.get("expires_at"), | ||
| market_web_url=MARKET_WEB_URL, | ||
| ) | ||
|
|
||
| cached_user = _fresh_cached_market_user(token_data) | ||
| if cached_user is not None: | ||
| return MarketOAuthStatusResponse( | ||
| authenticated=True, | ||
| auth_state="ready", | ||
| market_state="ready", | ||
| user=cached_user, | ||
| expires_at=token_data.get("expires_at"), | ||
| market_web_url=MARKET_WEB_URL, | ||
| ) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
刚解析好的 subject 可能没落盘就被 return 掉了喵~
1004-1007 把 subject / subject_pending=False 只写进内存字典;如果紧接着 _fresh_cached_market_user 命中(1024-1033 提前返回),CAS 写回永远不会执行,下一次 /status 又得重新打 userinfo。建议解析成功后立刻 CAS 落盘,并刷新快照给后续写入用呐。
💾 建议改动
if subject:
token_data["subject"] = subject
token_data["subject_pending"] = False
token_data["auth_state"] = "ready"
+ token_data["updated_at"] = time.time()
+ if not _write_oauth_token_if_matches(token_snapshot, token_data):
+ return MarketOAuthStatusResponse(
+ authenticated=False,
+ market_web_url=MARKET_WEB_URL,
+ )
+ token_snapshot = dict(token_data)
else:📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if subject: | |
| token_data["subject"] = subject | |
| token_data["subject_pending"] = False | |
| token_data["auth_state"] = "ready" | |
| else: | |
| token_data["auth_state"] = "pending" | |
| token_data["updated_at"] = time.time() | |
| if not _write_oauth_token_if_matches(token_snapshot, token_data): | |
| return MarketOAuthStatusResponse( | |
| authenticated=False, | |
| market_web_url=MARKET_WEB_URL, | |
| ) | |
| return MarketOAuthStatusResponse( | |
| authenticated=False, | |
| auth_state="pending", | |
| retryable=True, | |
| expires_at=token_data.get("expires_at"), | |
| market_web_url=MARKET_WEB_URL, | |
| ) | |
| cached_user = _fresh_cached_market_user(token_data) | |
| if cached_user is not None: | |
| return MarketOAuthStatusResponse( | |
| authenticated=True, | |
| auth_state="ready", | |
| market_state="ready", | |
| user=cached_user, | |
| expires_at=token_data.get("expires_at"), | |
| market_web_url=MARKET_WEB_URL, | |
| ) | |
| if subject: | |
| token_data["subject"] = subject | |
| token_data["subject_pending"] = False | |
| token_data["auth_state"] = "ready" | |
| token_data["updated_at"] = time.time() | |
| if not _write_oauth_token_if_matches(token_snapshot, token_data): | |
| return MarketOAuthStatusResponse( | |
| authenticated=False, | |
| market_web_url=MARKET_WEB_URL, | |
| ) | |
| token_snapshot = dict(token_data) | |
| else: | |
| token_data["auth_state"] = "pending" | |
| token_data["updated_at"] = time.time() | |
| if not _write_oauth_token_if_matches(token_snapshot, token_data): | |
| return MarketOAuthStatusResponse( | |
| authenticated=False, | |
| market_web_url=MARKET_WEB_URL, | |
| ) | |
| return MarketOAuthStatusResponse( | |
| authenticated=False, | |
| auth_state="pending", | |
| retryable=True, | |
| expires_at=token_data.get("expires_at"), | |
| market_web_url=MARKET_WEB_URL, | |
| ) | |
| cached_user = _fresh_cached_market_user(token_data) | |
| if cached_user is not None: | |
| return MarketOAuthStatusResponse( | |
| authenticated=True, | |
| auth_state="ready", | |
| market_state="ready", | |
| user=cached_user, | |
| expires_at=token_data.get("expires_at"), | |
| market_web_url=MARKET_WEB_URL, | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@plugin/server/routes/market_bridge.py` around lines 1004 - 1033, Update the
successful subject branch in the OAuth status flow to persist subject,
subject_pending, and auth_state through _write_oauth_token_if_matches
immediately, before calling _fresh_cached_market_user. Refresh
token_snapshot/token_data from the successful CAS result for subsequent writes,
and preserve the existing conflict response behavior if persistence fails.
改动概述
本 PR 修复 N.E.K.O App 与 Auth / Plugin Market 集成中的登录误判、目录跨域和异步竞态问题:
边界审核
Authentication / Session
sub的响应才能建立 Auth 身份。access_token + session_id + refresh_generation做 compare-and-swap,旧请求不能覆盖新会话。Proxy / Network
plugin_id会进行路径编码,查询参数只透传到固定上游路径。Logging / Sensitive Data
Frontend Async State
范围排除
plugin/plugins/bilibili_danmaku/data/user_records/用户数据未纳入提交。UI 前后对照
以下截图来自本地受控 API 状态下的实际构建:
改动前:
改动后:
Standards 审核
Spec / 信任边界审核
测试
.venv/bin/python -m pytest -q plugin/tests/integration/test_market_bridge_e2e.py—57 passednpm exec vitest run -- src/composables/useMarketAuth.test.ts src/api/market.test.ts—13 passednpm run check:i18n—607 keys across 8 localesnpm run type-check— passednpm run build-only— passedgit diff --check— passed回归报告
不适用:未改动
app/、main_logic/、main_routers/或memory/下的 Python 文件。不拆分理由
不适用:按项目计数规则,仅 4 个非测试、非 locale、非新增文件计入,未超过 20。
Summary by CodeRabbit