{"record":{"id":"ffd45b1784b2f6bd","repo":"hcengineering/platform","slug":"apierror-ffd45b","errorCode":null,"errorMessage":"ApiError","messagePattern":"ApiError","errorType":"http","errorClass":"ApiError","httpStatus":401,"severity":"error","filePath":"services/analytics-collector/pod-analytics-collector/src/server.ts","lineNumber":38,"sourceCode":"import { Token } from '@hcengineering/server-token'\nimport cors from 'cors'\nimport express, { type Express, type NextFunction, type Request, type Response } from 'express'\nimport { type Server } from 'http'\nimport config from './config'\nimport { ApiError } from './error'\nimport { geoFieldMapping, getAllPossibleIps, getClientIp, getGeoLocationFromIp } from './geoip'\n\ntype AsyncRequestHandler = (req: Request, res: Response, token: Token, next: NextFunction) => Promise<void>\n\nconst handleRequest = async (\n  fn: AsyncRequestHandler,\n  req: Request,\n  res: Response,\n  next: NextFunction\n): Promise<void> => {\n  const token = extractToken(req.headers)\n  if (token === undefined) {\n    throw new ApiError(401)\n  }\n  try {\n    await fn(req, res, token, next)\n  } catch (err: unknown) {\n    next(err)\n  }\n}\n\nconst wrapRequest = (fn: AsyncRequestHandler) => (req: Request, res: Response, next: NextFunction) => {\n  void handleRequest(fn, req, res, next)\n}\n\nfunction isContentValid (body: any[]): boolean {\n  return !body.some((it) => {\n    if (it == null) return true\n    if (!('event' in it)) return true\n    if (!('properties' in it)) return true\n    if (!('timestamp' in it)) return true","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/analytics-collector/pod-analytics-collector/src/server.ts#L20-L56","documentation":"The analytics-collector's wrapRequest wrapper extracts a bearer token from request headers; if extractToken returns undefined, it throws ApiError(401), rejecting unauthenticated requests before the handler runs. This is the service's authentication gate for all wrapped routes.","triggerScenarios":"POST /collect (or any wrapped route) called without an Authorization header, with a malformed header that extractToken cannot parse, or with an empty token.","commonSituations":"Client forgot to set the Authorization header; a proxy/gateway stripped the header; SDK or curl command omits the token; header prefix (e.g. 'Bearer ') mismatched with what extractToken expects.","solutions":["Send a valid Authorization header (e.g. 'Bearer <token>') on every request to wrapped routes","Inspect the raw request headers (curl -v / gateway logs) to confirm the header survives proxies","Match the header format expected by extractToken in services/analytics-collector/pod-analytics-collector/src/server.ts","Verify API client/SDK configuration includes the auth token for the correct environment"],"exampleFix":"// before\ncurl -X POST https://collector/collect -d '[...]'\n// after\ncurl -X POST https://collector/collect -H 'Authorization: Bearer <token>' -d '[...]'","handlingStrategy":"validation","validationCode":"// check the token exists before sending\nif (!authToken) throw new Error('Analytics collector auth token is not configured')\nconst headers = { Authorization: `Bearer ${authToken}`, 'Content-Type': 'application/json' }","typeGuard":null,"tryCatchPattern":"try {\n  await collector.post('/collect', body, { headers })\n} catch (err) {\n  if (err.response?.status === 401) {\n    // refresh token / fail fast with a clear config error\n  } else throw err\n}","preventionTips":["Centralize auth-header injection in one HTTP client","Assert token presence at startup, not per request","Verify proxies do not strip the Authorization header"],"tags":["authentication","http-401","middleware"],"backgroundTag":"missing-auth-token","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}