{"record":{"id":"a4d7f868027c6325","repo":"yikart/AiToEarn","slug":"unauthorized","errorCode":null,"errorMessage":"Unauthorized","messagePattern":"Unauthorized","errorType":"http","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"project/aitoearn-backend/libs/aitoearn-auth/src/aitoearn-auth.guard.ts","lineNumber":41,"sourceCode":"\n  async canActivate(context: ExecutionContext): Promise<boolean> {\n    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [\n      context.getHandler(),\n      context.getClass(),\n    ])\n    const isInternal = this.reflector.getAllAndOverride<boolean>(IS_INTERNAL_KEY, [\n      context.getHandler(),\n      context.getClass(),\n    ])\n\n    const request = context.switchToHttp().getRequest()\n\n    if (isInternal) {\n      const token = this.extractTokenFromHeader(request)\n      if (token === this.options.internalToken) {\n        return true\n      }\n      throw new UnauthorizedException()\n    }\n\n    // 1. API Key 认证（x-api-key，既有行为，对所有路由生效）\n    const apiKey = request.headers['x-api-key'] as string | undefined\n    if (apiKey) {\n      await this.resolveApiKey(request, apiKey)\n      return true\n    }\n\n    // 2. 额外声明的 header（@ApiKeyHeader）按 API Key 解析\n    const apiKeyHeader = this.reflector.getAllAndOverride<string>(API_KEY_HEADER_KEY, [\n      context.getHandler(),\n      context.getClass(),\n    ])\n    if (apiKeyHeader) {\n      // Authorization 特殊处理：优先按 JWT 校验，失败再兜底按 API Key 解析\n      if (apiKeyHeader.toLowerCase() === 'authorization') {\n        const token = this.extractTokenFromHeader(request)","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/libs/aitoearn-auth/src/aitoearn-auth.guard.ts#L23-L59","documentation":"The AitoearnAuthGuard's canActivate throws UnauthorizedException when a request presents the internal-token path (isInternal) but the extracted token does not equal options.internalToken. It means internal service-to-service authentication failed before API key or JWT resolution is attempted.","triggerScenarios":"A request routed as internal (isInternal true) carries a missing, malformed, or stale internal token in the Authorization header, or the caller's token was generated for a different environment than the one configured via options.internalToken.","commonSituations":"Environment variable holding the internal token differs between the calling service and the receiving service (e.g. staging vs prod token), the token was rotated on one side only, or a gateway/proxy strips or rewrites the Authorization header.","solutions":["Compare the token sent by the caller against the internalToken configured in the receiving service's options; make both read the exact same env var","If internal access is not intended, resend the request with a valid x-api-key or Bearer JWT instead","Check that no proxy/gateway middleware strips or modifies the Authorization header","Rotate/regenerate the shared internal token and redeploy both sides together"],"exampleFix":"// before (.env of caller)\nINTERNAL_TOKEN=staging-token\n// after\nINTERNAL_TOKEN=same-value-as-receiver-options.internalToken","handlingStrategy":"validation","validationCode":"const token = extractBearer(req.headers.authorization)\nif (!token || token !== process.env.INTERNAL_TOKEN) {\n  throw new Error('Internal token mismatch before calling API')\n}","typeGuard":"function hasValidInternalToken(authHeader: unknown, expected: string): boolean {\n  return typeof authHeader === 'string' && authHeader === `Bearer ${expected}`\n}","tryCatchPattern":"try {\n  await internalCall(url, { headers: { Authorization: `Bearer ${process.env.INTERNAL_TOKEN}` } })\n} catch (e) {\n  if (e.response?.status === 401) {\n    // refresh shared token from config service and retry once\n  }\n  throw e\n}","preventionTips":["Load the internal token from one shared secret/config source on all services","Rotate internal tokens with coordinated rolling deploys","Log token fingerprint (not the token) on mismatch to diagnose env drift","Add an integration test that calls each service with the shared token"],"tags":["auth","unauthorized","internal-token","nestjs"],"backgroundTag":"internal-token-mismatch","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}