{"version":3,"sources":["../../src/adonisjs/middleware.ts","../../src/common/consumerRegistry.ts","../../src/common/requestLogger.ts","../../src/common/sentry.ts","../../src/common/utils.ts"],"sourcesContent":["import { HttpContext } from \"@adonisjs/core/http\";\nimport { NextFn } from \"@adonisjs/core/types/http\";\nimport type { OutgoingHttpHeaders } from \"http\";\nimport { performance } from \"perf_hooks\";\n\nimport type { ApitallyClient } from \"../common/client.js\";\nimport { consumerFromStringOrObject } from \"../common/consumerRegistry.js\";\nimport { convertHeaders } from \"../common/requestLogger.js\";\nimport type { ApitallyConsumer } from \"../common/types.js\";\nimport { parseContentLength } from \"../common/utils.js\";\n\ndeclare module \"@adonisjs/core/http\" {\n  interface HttpContext {\n    apitallyConsumer?: ApitallyConsumer | string;\n    apitallyError?: Error;\n  }\n}\n\nexport default class ApitallyMiddleware {\n  async handle(ctx: HttpContext, next: NextFn) {\n    const client: ApitallyClient =\n      await ctx.containerResolver.make(\"apitallyClient\");\n\n    if (!client.isEnabled()) {\n      await next();\n      return;\n    }\n\n    const path = ctx.route?.pattern;\n    const timestamp = Date.now() / 1000;\n    const startTime = performance.now();\n\n    await next();\n\n    const responseTime = performance.now() - startTime;\n    const requestSize = parseContentLength(\n      ctx.request.header(\"content-length\"),\n    );\n    const requestContentType = ctx.request.header(\"content-type\")?.toString();\n    let responseStatus = ctx.response.getStatus();\n    let responseHeaders = ctx.response.getHeaders();\n    let responseSize: number | undefined;\n    let responseContentType: string | undefined;\n\n    const consumer = ctx.apitallyConsumer\n      ? consumerFromStringOrObject(ctx.apitallyConsumer)\n      : null;\n    client.consumerRegistry.addOrUpdateConsumer(consumer);\n\n    const onWriteHead = (statusCode: number, headers: OutgoingHttpHeaders) => {\n      responseStatus = statusCode;\n      responseHeaders = headers;\n      responseSize = parseContentLength(headers[\"content-length\"]);\n      responseContentType = headers[\"content-type\"]?.toString();\n      if (path) {\n        client.requestCounter.addRequest({\n          consumer: consumer?.identifier,\n          method: ctx.request.method(),\n          path,\n          statusCode: responseStatus,\n          responseTime,\n          requestSize,\n          responseSize,\n        });\n\n        if (\n          responseStatus === 422 &&\n          ctx.apitallyError &&\n          \"code\" in ctx.apitallyError &&\n          \"messages\" in ctx.apitallyError &&\n          ctx.apitallyError.code === \"E_VALIDATION_ERROR\" &&\n          Array.isArray(ctx.apitallyError.messages)\n        ) {\n          ctx.apitallyError.messages.forEach((message) => {\n            client.validationErrorCounter.addValidationError({\n              consumer: consumer?.identifier,\n              method: ctx.request.method(),\n              path,\n              loc: message.field,\n              msg: message.message,\n              type: message.rule,\n            });\n          });\n        }\n\n        if (responseStatus === 500 && ctx.apitallyError) {\n          client.serverErrorCounter.addServerError({\n            consumer: consumer?.identifier,\n            method: ctx.request.method(),\n            path,\n            type: ctx.apitallyError.name,\n            msg: ctx.apitallyError.message,\n            traceback: ctx.apitallyError.stack || \"\",\n          });\n        }\n      }\n    };\n\n    // Capture the final status code and response headers just before they are sent\n    const originalWriteHead = ctx.response.response.writeHead;\n    ctx.response.response.writeHead = (...args: any) => {\n      originalWriteHead.apply(ctx.response.response, args);\n      onWriteHead(args[0], typeof args[1] === \"string\" ? args[2] : args[1]);\n      return ctx.response.response;\n    };\n\n    if (client.requestLogger.enabled) {\n      const onEnd = (chunk: any) => {\n        const requestBody =\n          client.requestLogger.config.logRequestBody &&\n          client.requestLogger.isSupportedContentType(requestContentType)\n            ? ctx.request.raw()\n            : undefined;\n        const responseBody =\n          client.requestLogger.config.logResponseBody &&\n          client.requestLogger.isSupportedContentType(responseContentType)\n            ? chunk\n            : undefined;\n\n        client.requestLogger.logRequest(\n          {\n            timestamp,\n            method: ctx.request.method(),\n            path,\n            url: ctx.request.completeUrl(true),\n            headers: convertHeaders(ctx.request.headers()),\n            size: requestSize,\n            consumer: consumer?.identifier,\n            body: requestBody ? Buffer.from(requestBody) : undefined,\n          },\n          {\n            statusCode: responseStatus,\n            responseTime: responseTime / 1000,\n            headers: convertHeaders(responseHeaders),\n            size: responseSize,\n            body: responseBody ? Buffer.from(responseBody) : undefined,\n          },\n          ctx.apitallyError,\n        );\n      };\n\n      // Capture the final response body just before it is sent\n      const originalEnd = ctx.response.response.end;\n      ctx.response.response.end = (...args: any) => {\n        originalEnd.apply(ctx.response.response, args);\n        onEnd(typeof args[0] !== \"function\" ? args[0] : undefined);\n        return ctx.response.response;\n      };\n    }\n  }\n}\n","import { ApitallyConsumer } from \"./types.js\";\n\nexport const consumerFromStringOrObject = (\n  consumer: ApitallyConsumer | string,\n) => {\n  if (typeof consumer === \"string\") {\n    consumer = String(consumer).trim().substring(0, 128);\n    return consumer ? { identifier: consumer } : null;\n  } else {\n    consumer.identifier = String(consumer.identifier).trim().substring(0, 128);\n    consumer.name = consumer.name?.trim().substring(0, 64);\n    consumer.group = consumer.group?.trim().substring(0, 64);\n    return consumer.identifier ? consumer : null;\n  }\n};\n\nexport default class ConsumerRegistry {\n  private consumers: Map<string, ApitallyConsumer>;\n  private updated: Set<string>;\n\n  constructor() {\n    this.consumers = new Map();\n    this.updated = new Set();\n  }\n\n  public addOrUpdateConsumer(consumer?: ApitallyConsumer | null) {\n    if (!consumer || (!consumer.name && !consumer.group)) {\n      return;\n    }\n    const existing = this.consumers.get(consumer.identifier);\n    if (!existing) {\n      this.consumers.set(consumer.identifier, consumer);\n      this.updated.add(consumer.identifier);\n    } else {\n      if (consumer.name && consumer.name !== existing.name) {\n        existing.name = consumer.name;\n        this.updated.add(consumer.identifier);\n      }\n      if (consumer.group && consumer.group !== existing.group) {\n        existing.group = consumer.group;\n        this.updated.add(consumer.identifier);\n      }\n    }\n  }\n\n  public getAndResetUpdatedConsumers() {\n    const data: Array<ApitallyConsumer> = [];\n    this.updated.forEach((identifier) => {\n      const consumer = this.consumers.get(identifier);\n      if (consumer) {\n        data.push(consumer);\n      }\n    });\n    this.updated.clear();\n    return data;\n  }\n}\n","import AsyncLock from \"async-lock\";\nimport { Buffer } from \"buffer\";\nimport { randomUUID } from \"crypto\";\nimport { unlinkSync, writeFileSync } from \"fs\";\nimport { IncomingHttpHeaders, OutgoingHttpHeaders } from \"http\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\n\nimport { getSentryEventId } from \"./sentry.js\";\nimport {\n  truncateExceptionMessage,\n  truncateExceptionStackTrace,\n} from \"./serverErrorCounter.js\";\nimport TempGzipFile from \"./tempGzipFile.js\";\n\nconst MAX_BODY_SIZE = 50_000; // 50 KB (uncompressed)\nconst MAX_FILE_SIZE = 1_000_000; // 1 MB (compressed)\nconst MAX_FILES = 50;\nconst MAX_PENDING_WRITES = 100;\nconst BODY_TOO_LARGE = Buffer.from(\"<body too large>\");\nconst BODY_MASKED = Buffer.from(\"<masked>\");\nconst MASKED = \"******\";\nconst ALLOWED_CONTENT_TYPES = [\"application/json\", \"text/plain\"];\nconst EXCLUDE_PATH_PATTERNS = [\n  /\\/_?healthz?$/i,\n  /\\/_?health[_-]?checks?$/i,\n  /\\/_?heart[_-]?beats?$/i,\n  /\\/ping$/i,\n  /\\/ready$/i,\n  /\\/live$/i,\n];\nconst EXCLUDE_USER_AGENT_PATTERNS = [\n  /health[-_ ]?check/i,\n  /microsoft-azure-application-lb/i,\n  /googlehc/i,\n  /kube-probe/i,\n];\nconst MASK_QUERY_PARAM_PATTERNS = [\n  /auth/i,\n  /api-?key/i,\n  /secret/i,\n  /token/i,\n  /password/i,\n  /pwd/i,\n];\nconst MASK_HEADER_PATTERNS = [\n  /auth/i,\n  /api-?key/i,\n  /secret/i,\n  /token/i,\n  /cookie/i,\n];\n\nexport type Request = {\n  timestamp: number;\n  method: string;\n  path?: string;\n  url: string;\n  headers: [string, string][];\n  size?: number;\n  consumer?: string;\n  body?: Buffer;\n};\n\nexport type Response = {\n  statusCode: number;\n  responseTime: number;\n  headers: [string, string][];\n  size?: number;\n  body?: Buffer;\n};\n\nexport type RequestLoggingConfig = {\n  enabled: boolean;\n  logQueryParams: boolean;\n  logRequestHeaders: boolean;\n  logRequestBody: boolean;\n  logResponseHeaders: boolean;\n  logResponseBody: boolean;\n  logException: boolean;\n  maskQueryParams: RegExp[];\n  maskHeaders: RegExp[];\n  maskRequestBodyCallback?: (request: Request) => Buffer | null | undefined;\n  maskResponseBodyCallback?: (\n    request: Request,\n    response: Response,\n  ) => Buffer | null | undefined;\n  excludePaths: RegExp[];\n  excludeCallback?: (request: Request, response: Response) => boolean;\n};\n\nconst DEFAULT_CONFIG: RequestLoggingConfig = {\n  enabled: false,\n  logQueryParams: true,\n  logRequestHeaders: false,\n  logRequestBody: false,\n  logResponseHeaders: true,\n  logResponseBody: false,\n  logException: true,\n  maskQueryParams: [],\n  maskHeaders: [],\n  excludePaths: [],\n};\n\nexport default class RequestLogger {\n  public config: RequestLoggingConfig;\n  public enabled: boolean;\n  public suspendUntil: number | null = null;\n  private pendingWrites: string[] = [];\n  private currentFile: TempGzipFile | null = null;\n  private files: TempGzipFile[] = [];\n  private maintainIntervalId?: NodeJS.Timeout;\n  private lock = new AsyncLock();\n\n  constructor(config?: Partial<RequestLoggingConfig>) {\n    this.config = { ...DEFAULT_CONFIG, ...config };\n    this.enabled = this.config.enabled && checkWritableFs();\n\n    if (this.enabled) {\n      this.maintainIntervalId = setInterval(() => {\n        this.maintain();\n      }, 1000);\n    }\n  }\n\n  private shouldExcludePath(urlPath: string) {\n    const patterns = [...this.config.excludePaths, ...EXCLUDE_PATH_PATTERNS];\n    return matchPatterns(urlPath, patterns);\n  }\n\n  private shouldExcludeUserAgent(userAgent?: string) {\n    return userAgent\n      ? matchPatterns(userAgent, EXCLUDE_USER_AGENT_PATTERNS)\n      : false;\n  }\n\n  private shouldMaskQueryParam(name: string) {\n    const patterns = [\n      ...this.config.maskQueryParams,\n      ...MASK_QUERY_PARAM_PATTERNS,\n    ];\n    return matchPatterns(name, patterns);\n  }\n\n  private shouldMaskHeader(name: string) {\n    const patterns = [...this.config.maskHeaders, ...MASK_HEADER_PATTERNS];\n    return matchPatterns(name, patterns);\n  }\n\n  private hasSupportedContentType(headers: [string, string][]) {\n    const contentType = headers.find(\n      ([k]) => k.toLowerCase() === \"content-type\",\n    )?.[1];\n    return this.isSupportedContentType(contentType);\n  }\n\n  public isSupportedContentType(contentType?: string | null) {\n    return (\n      typeof contentType === \"string\" &&\n      ALLOWED_CONTENT_TYPES.some((t) => contentType.startsWith(t))\n    );\n  }\n\n  private maskQueryParams(search: string) {\n    const params = new URLSearchParams(search);\n    for (const [key] of params) {\n      if (this.shouldMaskQueryParam(key)) {\n        params.set(key, MASKED);\n      }\n    }\n    return params.toString();\n  }\n\n  private maskHeaders(headers: [string, string][]): [string, string][] {\n    return headers.map(([k, v]) => [k, this.shouldMaskHeader(k) ? MASKED : v]);\n  }\n\n  logRequest(request: Request, response: Response, error?: Error) {\n    if (!this.enabled || this.suspendUntil !== null) return;\n\n    const url = new URL(request.url);\n    const path = request.path ?? url.pathname;\n    const userAgent = request.headers.find(\n      ([k]) => k.toLowerCase() === \"user-agent\",\n    )?.[1];\n\n    if (\n      this.shouldExcludePath(path) ||\n      this.shouldExcludeUserAgent(userAgent) ||\n      (this.config.excludeCallback?.(request, response) ?? false)\n    ) {\n      return;\n    }\n\n    // Process query params\n    url.search = this.config.logQueryParams\n      ? this.maskQueryParams(url.search)\n      : \"\";\n    request.url = url.toString();\n\n    // Process request body\n    if (\n      !this.config.logRequestBody ||\n      !this.hasSupportedContentType(request.headers)\n    ) {\n      request.body = undefined;\n    } else if (request.body) {\n      if (request.body.length > MAX_BODY_SIZE) {\n        request.body = BODY_TOO_LARGE;\n      } else if (this.config.maskRequestBodyCallback) {\n        try {\n          request.body =\n            this.config.maskRequestBodyCallback(request) ?? BODY_MASKED;\n          if (request.body.length > MAX_BODY_SIZE) {\n            request.body = BODY_TOO_LARGE;\n          }\n        } catch {\n          request.body = undefined;\n        }\n      }\n    }\n\n    // Process response body\n    if (\n      !this.config.logResponseBody ||\n      !this.hasSupportedContentType(response.headers)\n    ) {\n      response.body = undefined;\n    } else if (response.body) {\n      if (response.body.length > MAX_BODY_SIZE) {\n        response.body = BODY_TOO_LARGE;\n      } else if (this.config.maskResponseBodyCallback) {\n        try {\n          response.body =\n            this.config.maskResponseBodyCallback(request, response) ??\n            BODY_MASKED;\n          if (response.body.length > MAX_BODY_SIZE) {\n            response.body = BODY_TOO_LARGE;\n          }\n        } catch {\n          response.body = undefined;\n        }\n      }\n    }\n\n    // Process headers\n    request.headers = this.config.logRequestHeaders\n      ? this.maskHeaders(request.headers)\n      : [];\n    response.headers = this.config.logResponseHeaders\n      ? this.maskHeaders(response.headers)\n      : [];\n\n    const item = {\n      uuid: randomUUID(),\n      request: skipEmptyValues(request),\n      response: skipEmptyValues(response),\n      exception:\n        error && this.config.logException\n          ? {\n              type: error.name,\n              message: truncateExceptionMessage(error.message),\n              stacktrace: truncateExceptionStackTrace(error.stack || \"\"),\n              sentryEventId: getSentryEventId(),\n            }\n          : null,\n    };\n    [item.request.body, item.response.body].forEach((body) => {\n      if (body) {\n        // @ts-expect-error Different return type\n        body.toJSON = function () {\n          return this.toString(\"base64\");\n        };\n      }\n    });\n    this.pendingWrites.push(JSON.stringify(item));\n\n    if (this.pendingWrites.length > MAX_PENDING_WRITES) {\n      this.pendingWrites.shift();\n    }\n  }\n\n  async writeToFile() {\n    if (!this.enabled || this.pendingWrites.length === 0) {\n      return;\n    }\n    return this.lock.acquire(\"file\", async () => {\n      if (!this.currentFile) {\n        this.currentFile = new TempGzipFile();\n      }\n      while (this.pendingWrites.length > 0) {\n        const item = this.pendingWrites.shift();\n        if (item) {\n          await this.currentFile.writeLine(Buffer.from(item));\n        }\n      }\n    });\n  }\n\n  getFile() {\n    return this.files.shift();\n  }\n\n  retryFileLater(file: TempGzipFile) {\n    this.files.unshift(file);\n  }\n\n  async rotateFile() {\n    return this.lock.acquire(\"file\", async () => {\n      if (this.currentFile) {\n        await this.currentFile.close();\n        this.files.push(this.currentFile);\n        this.currentFile = null;\n      }\n    });\n  }\n\n  async maintain() {\n    await this.writeToFile();\n    if (this.currentFile && this.currentFile.size > MAX_FILE_SIZE) {\n      await this.rotateFile();\n    }\n    while (this.files.length > MAX_FILES) {\n      const file = this.files.shift();\n      file?.delete();\n    }\n    if (this.suspendUntil !== null && this.suspendUntil < Date.now()) {\n      this.suspendUntil = null;\n    }\n  }\n\n  async clear() {\n    this.pendingWrites = [];\n    await this.rotateFile();\n    this.files.forEach((file) => {\n      file.delete();\n    });\n    this.files = [];\n  }\n\n  async close() {\n    this.enabled = false;\n    await this.clear();\n    if (this.maintainIntervalId) {\n      clearInterval(this.maintainIntervalId);\n    }\n  }\n}\n\nexport function convertHeaders(\n  headers:\n    | Headers\n    | IncomingHttpHeaders\n    | OutgoingHttpHeaders\n    | Record<string, string | string[] | number | undefined>,\n) {\n  if (headers instanceof Headers) {\n    return Array.from(headers.entries());\n  }\n  return Object.entries(headers).flatMap(([key, value]) => {\n    if (value === undefined) {\n      return [];\n    }\n    if (Array.isArray(value)) {\n      return value.map((v) => [key, v]);\n    }\n    return [[key, value.toString()]];\n  }) as [string, string][];\n}\n\nexport function convertBody(body: any, contentType?: string | null) {\n  if (!body || !contentType) {\n    return;\n  }\n  try {\n    if (contentType.startsWith(\"application/json\")) {\n      if (isValidJsonString(body)) {\n        return Buffer.from(body);\n      } else {\n        return Buffer.from(JSON.stringify(body));\n      }\n    }\n    if (contentType.startsWith(\"text/\") && typeof body === \"string\") {\n      return Buffer.from(body);\n    }\n  } catch (error) {\n    return;\n  }\n}\n\nfunction isValidJsonString(body: any) {\n  if (typeof body !== \"string\") {\n    return false;\n  }\n  try {\n    JSON.parse(body);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\nfunction matchPatterns(value: string, patterns: RegExp[]) {\n  return patterns.some((pattern) => {\n    return pattern.test(value);\n  });\n}\n\nfunction skipEmptyValues<T extends Record<string, any>>(data: T) {\n  return Object.fromEntries(\n    Object.entries(data).filter(([_, v]) => {\n      if (v == null || Number.isNaN(v)) return false;\n      if (Array.isArray(v) || Buffer.isBuffer(v) || typeof v === \"string\") {\n        return v.length > 0;\n      }\n      return true;\n    }),\n  ) as Partial<T>;\n}\n\nfunction checkWritableFs() {\n  try {\n    const testPath = join(tmpdir(), `apitally-${randomUUID()}`);\n    writeFileSync(testPath, \"test\");\n    unlinkSync(testPath);\n    return true;\n  } catch (error) {\n    return false;\n  }\n}\n","import type * as Sentry from \"@sentry/node\";\n\nlet sentry: typeof Sentry | undefined;\n\n// Initialize Sentry when the module is loaded\n(async () => {\n  try {\n    sentry = await import(\"@sentry/node\");\n  } catch (e) {\n    // Sentry SDK is not installed, ignore\n  }\n})();\n\n/**\n * Returns the last Sentry event ID if available\n */\nexport function getSentryEventId(): string | undefined {\n  if (sentry && sentry.lastEventId) {\n    return sentry.lastEventId();\n  }\n  return undefined;\n}\n","import { OutgoingHttpHeader } from \"http\";\n\nexport function parseContentLength(\n  contentLength: OutgoingHttpHeader | undefined | null,\n): number | undefined {\n  if (contentLength === undefined || contentLength === null) {\n    return undefined;\n  }\n  if (typeof contentLength === \"number\") {\n    return contentLength;\n  }\n  if (typeof contentLength === \"string\") {\n    const parsed = parseInt(contentLength);\n    return isNaN(parsed) ? undefined : parsed;\n  }\n  if (Array.isArray(contentLength)) {\n    return parseContentLength(contentLength[0]);\n  }\n  return undefined;\n}\n"],"mappings":"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","names":["consumerFromStringOrObject","consumer","String","trim","substring","identifier","name","group","sentry","e","BODY_TOO_LARGE","Buffer","from","BODY_MASKED","convertHeaders","headers","Headers","Array","from","entries","Object","flatMap","key","value","undefined","isArray","map","v","toString","parseContentLength","contentLength","undefined","parsed","parseInt","isNaN","Array","isArray","ApitallyMiddleware","handle","ctx","next","client","containerResolver","make","isEnabled","path","route","pattern","timestamp","Date","now","startTime","performance","responseTime","requestSize","parseContentLength","request","header","requestContentType","toString","responseStatus","response","getStatus","responseHeaders","getHeaders","responseSize","responseContentType","consumer","apitallyConsumer","consumerFromStringOrObject","consumerRegistry","addOrUpdateConsumer","onWriteHead","statusCode","headers","_a","requestCounter","addRequest","identifier","method","apitallyError","code","Array","isArray","messages","forEach","message","validationErrorCounter","addValidationError","loc","field","msg","type","rule","serverErrorCounter","addServerError","name","traceback","stack","originalWriteHead","writeHead","args","apply","requestLogger","enabled","onEnd","chunk","requestBody","config","logRequestBody","isSupportedContentType","raw","undefined","responseBody","logResponseBody","logRequest","url","completeUrl","convertHeaders","size","body","Buffer","from","originalEnd","end"]}