{"record":{"id":"a1ced9ba422dc310","repo":"yikart/AiToEarn","slug":"unauthorized-a1ced9","errorCode":null,"errorMessage":"Unauthorized","messagePattern":"Unauthorized","errorType":"http","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"project/aitoearn-backend/libs/common/src/interceptors/request-context.interceptor.ts","lineNumber":41,"sourceCode":"const SUPPORTED_LANGUAGES: Locale[] = ['en-US', 'zh-CN']\n\nexport function getLocale(): Locale {\n  return requestContext.getStore()?.locale || 'en-US'\n}\n\nexport function getRequestContext(): RequestContextStore | undefined {\n  return requestContext.getStore()\n}\n\n/**\n * Get authenticated user from request context.\n * Throws UnauthorizedException if user is not authenticated.\n * Use this for protected endpoints that require authentication.\n */\nexport function getUser(): TokenInfo {\n  const user = requestContext.getStore()?.user\n  if (!user) {\n    throw new UnauthorizedException()\n  }\n  return user\n}\n\n/**\n * Get authenticated user from request context, or undefined if not authenticated.\n * Does not throw. Use this for public endpoints that optionally use user info.\n */\nexport function getUserOptional(): TokenInfo | undefined {\n  return requestContext.getStore()?.user\n}\n\n@Injectable()\nexport class RequestContextInterceptor implements NestInterceptor {\n  public intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {\n    const locale = this.parseLocale(context)\n    const user = this.extractUser(context)\n    return requestContext.run({ locale, user }, () => next.handle())","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/libs/common/src/interceptors/request-context.interceptor.ts#L23-L59","documentation":"The request-context interceptor stores the authenticated user in AsyncLocalStorage per request. The getUser() helper throws Nest's UnauthorizedException (401) when the store has no user, i.e. the request reached a protected endpoint without a valid authenticated session.","triggerScenarios":"Calling an endpoint that calls getUser() without a Bearer token, with an expired/invalid token, or the auth guard/context middleware did not run before the interceptor populated the context.","commonSituations":"Missing Authorization header in API clients; expired API keys; third-party calls (e.g. webhooks, MCP) that bypass the auth middleware; environment mismatch where a token issued for one environment is used against another (aitoearn.cn vs aitoearn.ai).","solutions":["Attach a valid Authorization: Bearer <token> header to the request","Re-authenticate or refresh the token if expired","Verify the auth middleware/guard runs and populates requestContext.user for this route","Ensure the API key/token matches the environment (CN key with .cn URLs, international key with .ai URLs)"],"exampleFix":"// before\nconst res = await fetch('https://aitoearn.cn/api/v1/user');\n// after\nconst res = await fetch('https://aitoearn.cn/api/v1/user', {\n  headers: { Authorization: `Bearer ${token}` },\n});","handlingStrategy":"validation","validationCode":"const headers = { Authorization: `Bearer ${token}` };\nif (!token) throw new Error('Missing auth token: login first');\nconst res = await fetch(url, { headers });\nif (res.status === 401) await reauthenticate();","typeGuard":"function hasUser(ctx?: { user?: TokenInfo }): ctx is { user: TokenInfo } {\n  return !!ctx?.user;\n}","tryCatchPattern":"try {\n  const data = await api.call();\n} catch (e) {\n  if (e.response?.status === 401) {\n    await refreshTokenOrLogin();\n    return api.call();\n  }\n  throw e;\n}","preventionTips":["Always attach a valid Bearer token to authenticated endpoints","Refresh tokens before expiry; handle 401 with re-login flow","Match API key/token to environment (.cn key with .cn URLs, .ai key with .ai URLs)","Ensure auth middleware populates requestContext before guarded handlers run"],"tags":["auth","unauthorized","http-401","request-context"],"backgroundTag":"unauthorized-401","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}