{"record":{"id":"c230b6f9e716a056","repo":"Wei-Shaw/sub2api","slug":"auth-oidc-callbackmissingtoken-c230b6","errorCode":null,"errorMessage":"auth.oidc.callbackMissingToken","messagePattern":"auth\\.oidc\\.callbackMissingToken","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/views/auth/WechatCallbackView.vue","lineNumber":817,"sourceCode":"    states.includes('bind_login_required') ||\n    states.includes('bind_login') ||\n    states.includes('adopt_existing_user_by_email') ||\n    states.includes('existing_account_required') ||\n    states.includes('existing_account_binding_required')\n}\n\nasync function finalizeCompletion(completion: PendingOAuthExchangeResponse, redirect: string) {\n  if (getOAuthCompletionKind(completion) === 'bind') {\n    const bindRedirect = sanitizeRedirectPath(completion.redirect || '/profile')\n    clearPendingAuthSession()\n    clearAllAffiliateReferralCodes()\n    appStore.showSuccess(bindSuccessMessage)\n    await router.replace(bindRedirect)\n    return\n  }\n\n  if (!isOAuthLoginCompletion(completion)) {\n    throw new Error(t('auth.oidc.callbackMissingToken'))\n  }\n\n  persistOAuthTokenContext(completion)\n  await authStore.setToken(completion.access_token)\n  clearAllAffiliateReferralCodes()\n  appStore.showSuccess(t('auth.loginSuccess'))\n  await router.replace(redirect)\n}\n\nasync function finalizePendingAccountResponse(completion: PendingWeChatCompletion) {\n  applyAdoptionSuggestionState(completion)\n  const redirect = sanitizeRedirectPath(completion.redirect || redirectTo.value)\n\n  if (completion.error === 'invitation_required') {\n    pendingAccountAction.value = 'none'\n    needsInvitation.value = true\n    needsAdoptionConfirmation.value = false\n    isProcessing.value = false","sourceCodeStart":799,"sourceCodeEnd":835,"githubUrl":"https://github.com/Wei-Shaw/sub2api/blob/073e92d17178a1ccdb0a27017f572f10c9c7ab62/frontend/src/views/auth/WechatCallbackView.vue#L799-L835","documentation":"In frontend/src/views/auth/WechatCallbackView.vue:817, finalizeCompletion() handles the WeChat OAuth exchange. Note the message key is 'auth.oidc.callbackMissingToken' (shared/reused OIDC key in the WeChat view) even though this is the WeChat callback. As with the other providers, a completion that is neither bind nor login (no access_token) triggers the throw.","triggerScenarios":"WeChat code exchange returns 2xx without access_token: WeChat OAuth requires an app-specific secret and a server-side code-to-access_token exchange — a wrong appid/secret pairing, expired auth code (WeChat codes are single-use and short-lived ~5 min), or a session lost across the QR-scan redirect produces an empty completion.","commonSituations":"User scanning the WeChat QR then letting the code expire before confirming; mismatched WeChat Open Platform appid vs Official Account appid (unionid flows); cookie loss on mobile WeChat's embedded browser; the reused i18n key 'auth.oidc.*' misleading maintainers debugging WeChat.","solutions":["Verify the WeChat appid/secret used by the backend exchange match the app that generated the QR/authorize URL.","Ensure the auth code is exchanged immediately (single-use, 5-minute validity) and not re-exchanged on refresh — make the callback idempotent.","Rename the i18n key to auth.wechat.callbackMissingToken (or add it) so the error is not misattributed to OIDC.","Route non-bind/non-login completions to a friendly restart-login flow."],"exampleFix":"// before\nif (!isOAuthLoginCompletion(completion)) {\n  throw new Error(t('auth.oidc.callbackMissingToken'))\n}\n\n// after\nif (!isOAuthLoginCompletion(completion)) {\n  appStore.showError(t('auth.wechat.callbackMissingToken'))\n  await router.replace('/login')\n  return\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isWechatLoginCompletion(c: PendingOAuthExchangeResponse): c is PendingOAuthExchangeResponse & { access_token: string } {\n  return getOAuthCompletionKind(c) !== 'bind' && typeof (c as any).access_token === 'string' && (c as any).access_token.length > 0;\n}","tryCatchPattern":"try { await finalizeCompletion(completion, redirect); }\ncatch (e) {\n  if (e.message === t('auth.oidc.callbackMissingToken')) { // note: reused OIDC key in WeChat view\n    showError('WeChat login incomplete — please rescan');\n    await router.replace('/login'); return;\n  }\n  throw e;\n}","preventionTips":["Exchange WeChat auth codes immediately — they are single-use and expire in ~5 minutes; make the callback idempotent","Match the appid/secret pair used for the QR with the one used for the exchange","Rename the reused auth.oidc.* i18n key to a WeChat-specific one to avoid misdiagnosis"],"tags":["oauth","wechat","callback","i18n","session","frontend"],"backgroundTag":null,"analyzedSha":"073e92d17178a1ccdb0a27017f572f10c9c7ab62","analyzedAt":"2026-08-15T14:33:00.750Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}