{"record":{"id":"fa59489845c31d75","repo":"yikart/AiToEarn","slug":"no-access-token-available-user-needs-to-authorize-fa5948","errorCode":null,"errorMessage":"No access token available, user needs to authorize","messagePattern":"No access token available, user needs to authorize","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"project/aitoearn-electron/server/src/modules/plat/youtube/youtube.auth.service.ts","lineNumber":562,"sourceCode":"      //   });\n      //   console.log(`已创建新YouTube账号Token: ${accountTokenInfo.accountId} (${accountTokenInfo.userId})`);\n\n      // }\n    } catch (error) {\n      console.error('更新YouTube账号信息失败:', error);\n      // 不抛出异常，避免影响授权流程\n    }\n  }\n\n  /**\n   * 获取初始化后的YouTube API客户端\n   * @param accountId 账号id\n   * @returns 初始化后的YouTube API客户端\n   */\n  async getYouTubeClient(accountId: string): Promise<any> {\n    const accessToken = await this.getUserAccessToken(accountId);\n    if (!accessToken) {\n      throw new Error('No access token available, user needs to authorize');\n    }\n\n    return this.initializeYouTubeClient(accessToken);\n  }\n\n  /**\n   * 检查用户是否已授权YouTube\n   * @param accountId 账号ID\n   * @returns 是否已授权\n   */\n  async isAuthorized(accountId: string): Promise<boolean> {\n    try {\n      const accessToken = await this.getUserAccessToken(accountId);\n      return !!accessToken;\n    } catch (error) {\n      return false;\n    }\n  }","sourceCodeStart":544,"sourceCodeEnd":580,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-electron/server/src/modules/plat/youtube/youtube.auth.service.ts#L544-L580","documentation":"getYouTubeClient requires an OAuth access token to initialize the YouTube Data API client. It first calls getUserAccessToken(accountId) to fetch the stored token for the given account; if no token is stored (or retrieval returns null), it throws 'No access token available, user needs to authorize'. This indicates the account has never completed the OAuth authorization flow or the stored token has been lost.","triggerScenarios":"Calling any YouTube operation that goes through getYouTubeClient with an accountId that has no stored access token: the account was never authorized, the authorization flow was abandoned, tokens were cleared from the store (google:accessToken:<userId> key missing/expired and deleted), or the wrong accountId is passed.","commonSituations":"Developer adds a YouTube account record in the database without completing the OAuth consent screen; token store flushed after restart without refresh token persistence; user revoked access in Google account settings so stored token was invalidated and removed; passing an internal account id that differs from the one bound to the OAuth grant.","solutions":["Complete the OAuth authorization flow for this account by redirecting the user to the URL from GET /youtube/auth/url?mail=<email> and handling the callback so saveUserTokens persists the tokens","Verify the accountId is correct and corresponds to an account that previously authorized; re-check the token store key google:accessToken:<userId>","Check whether the user revoked access in their Google account (https://myaccount.google.com/permissions) and re-authorize","If tokens are lost after redeploy, ensure the token storage (redis/db) volume persists"],"exampleFix":"// before\nconst client = await youtubeAuthService.getYouTubeClient(accountId);\n// after\nlet client;\ntry {\n  client = await youtubeAuthService.getYouTubeClient(accountId);\n} catch (e) {\n  if (e.message.includes('No access token')) {\n    const authUrl = await youtubeController.getAuthUrl(systemToken, email); // redirect user to authorize\n    throw new Error(`Authorization required. Visit: ${authUrl}`);\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"const token = await tokenStore.get(`google:accessToken:${userId}`);\nif (!token) throw new Error(`Account ${accountId} not authorized; redirect user to /youtube/auth/url?mail=${email}`);","typeGuard":"function isAuthorizedAccount(acct: { accessToken?: string | null }): acct is { accessToken: string } {\n  return typeof acct.accessToken === 'string' && acct.accessToken.length > 0;\n}","tryCatchPattern":"try {\n  const client = await youtubeAuthService.getYouTubeClient(accountId);\n} catch (e) {\n  if (e.message === 'No access token available, user needs to authorize') {\n    return res.redirect(authUrl); // start OAuth flow\n  }\n  throw e;\n}","preventionTips":["Always complete the OAuth callback flow before using any YouTube API for an account","Check token presence in storage before invoking account-dependent operations","Monitor for revoked grants via 401 responses and trigger re-auth","Use stable, correct accountIds; validate they exist before calling"],"tags":["oauth","youtube","authorization","missing-token"],"backgroundTag":"oauth-token-missing","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}