{"record":{"id":"425034b58fafbccc","repo":"Wei-Shaw/sub2api","slug":"auth-linuxdo-callbackmissingtoken","errorCode":null,"errorMessage":"auth.linuxdo.callbackMissingToken","messagePattern":"auth\\.linuxdo\\.callbackMissingToken","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/views/auth/LinuxDoCallbackView.vue","lineNumber":583,"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.linuxdo.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: LinuxDoPendingActionResponse) {\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":565,"sourceCodeEnd":601,"githubUrl":"https://github.com/Wei-Shaw/sub2api/blob/073e92d17178a1ccdb0a27017f572f10c9c7ab62/frontend/src/views/auth/LinuxDoCallbackView.vue#L565-L601","documentation":"In frontend/src/views/auth/LinuxDoCallbackView.vue:583, finalizeCompletion() handles the LinuxDo OAuth exchange completion. After the bind branch, it requires isOAuthLoginCompletion(completion) — a response with access_token. Any other shape throws the localized 'auth.linuxdo.callbackMissingToken'. This mirrors the DingTalk callback: the backend exchange returned a completion that is neither bind nor login.","triggerScenarios":"LinuxDo OAuth code exchange returns 2xx without access_token: state/session cookie lost between redirect and callback so the server returns an incomplete completion; response field renamed by a backend update; double-callback (user refreshes) consuming the one-time exchange.","commonSituations":"Strict cookie settings (ITP/Safari) dropping the OAuth session cookie across the redirect; frontend/backend version skew; callback URL opened twice; LinuxDo provider outage returning an error the backend wrapped as an empty completion.","solutions":["Inspect the exchange response payload in the network tab to see exactly what the backend returned.","Verify the OAuth session cookie survives the redirect (SameSite settings, cross-site context).","Route any non-bind/non-login completion to a friendly restart-login flow instead of throwing raw.","Align frontend/backend versions to eliminate schema drift."],"exampleFix":"// before\nif (!isOAuthLoginCompletion(completion)) {\n  throw new Error(t('auth.linuxdo.callbackMissingToken'))\n}\n\n// after\nif (!isOAuthLoginCompletion(completion)) {\n  appStore.showError(t('auth.linuxdo.callbackMissingToken'))\n  await router.replace('/login')\n  return\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isLoginCompletion(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.linuxdo.callbackMissingToken')) {\n    showError('Login session expired — please retry');\n    await router.replace('/login'); return;\n  }\n  throw e;\n}","preventionTips":["Ensure the OAuth session cookie survives the provider redirect (SameSite/domain config)","Add a pending/error branch to finalizeCompletion instead of throwing on unknown shapes","Keep frontend and backend versions aligned"],"tags":["oauth","linuxdo","callback","session","frontend"],"backgroundTag":null,"analyzedSha":"073e92d17178a1ccdb0a27017f572f10c9c7ab62","analyzedAt":"2026-08-15T14:33:00.750Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}