@json-to-office/jto 0.26.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +134 -77
- package/dist/cli.js.map +1 -1
- package/dist/client/assets/{HomePage-CcmtpSuV.js → HomePage-BX2wX21e.js} +3 -3
- package/dist/client/assets/HomePage-BX2wX21e.js.map +1 -0
- package/dist/client/assets/{JsonEditorPage-RDa8GWDd.js → JsonEditorPage-DseCjMok.js} +3 -3
- package/dist/client/assets/{JsonEditorPage-RDa8GWDd.js.map → JsonEditorPage-DseCjMok.js.map} +1 -1
- package/dist/client/assets/{MonacoPluginProvider-DM63WAJx.js → MonacoPluginProvider-DvO7h20V.js} +3 -3
- package/dist/client/assets/{MonacoPluginProvider-DM63WAJx.js.map → MonacoPluginProvider-DvO7h20V.js.map} +1 -1
- package/dist/client/assets/{button-CyTe9rTl.js → button-DzgfrDEj.js} +2 -2
- package/dist/client/assets/{button-CyTe9rTl.js.map → button-DzgfrDEj.js.map} +1 -1
- package/dist/client/assets/{editor-DCtGYNz2.js → editor-BDi6QWgh.js} +2 -2
- package/dist/client/assets/{editor-DCtGYNz2.js.map → editor-BDi6QWgh.js.map} +1 -1
- package/dist/client/assets/{editor-monaco-json-CU9ciwPv.js → editor-monaco-json-CsQqBG2A.js} +2 -2
- package/dist/client/assets/{editor-monaco-json-CU9ciwPv.js.map → editor-monaco-json-CsQqBG2A.js.map} +1 -1
- package/dist/client/assets/{index-CHAIoQtG.js → index-Cxjik2Rg.js} +3 -3
- package/dist/client/assets/{index-CHAIoQtG.js.map → index-Cxjik2Rg.js.map} +1 -1
- package/dist/client/assets/preview-BOF6SWhK.js +3 -0
- package/dist/client/assets/preview-BOF6SWhK.js.map +1 -0
- package/dist/client/index.html +1 -1
- package/dist/client/templates/management-plan.pptx.json +18 -18
- package/dist/client/templates/minimalist-pitch-deck.pptx.json +19 -19
- package/dist/client/templates/modern-annual-report-2.docx.json +12 -12
- package/dist/client/templates/modern-annual-report-3.docx.json +14 -14
- package/dist/client/templates/standard-annual-report.docx.json +2 -2
- package/dist/render-server.js +62 -48
- package/dist/render-server.js.map +1 -1
- package/package.json +7 -8
- package/dist/client/assets/HomePage-CcmtpSuV.js.map +0 -1
- package/dist/client/assets/preview-oxnxMi4x.js +0 -3
- package/dist/client/assets/preview-oxnxMi4x.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/render-server.ts","../src/server/rasterize-route.ts","../src/server/lib/typebox-validator.ts","../src/server/security/outbound-source-policy.ts","../src/server/middleware/hono/rate-limit.ts","../src/server/middleware/hono/concurrency-limit.ts","../src/server/middleware/hono/auth.ts","../src/server/config/index.ts"],"sourcesContent":["/** Public front server for the chart exporter and pptx rasterizer. */\n\nimport { fileURLToPath } from 'node:url';\nimport { resolve } from 'node:path';\nimport { Hono } from 'hono';\nimport { bodyLimit } from 'hono/body-limit';\nimport { HTTPException } from 'hono/http-exception';\nimport { secureHeaders } from 'hono/secure-headers';\nimport type { StatusCode } from 'hono/utils/http-status';\nimport type { PptxRasterizer } from '@json-to-office/shared';\nimport { registerRasterizeRoute } from './server/rasterize-route.js';\nimport { rateLimiter } from './server/middleware/hono/rate-limit.js';\nimport { concurrencyLimiter } from './server/middleware/hono/concurrency-limit.js';\nimport {\n createApiKeyAuthMiddleware,\n type ApiKeyAuthOptions,\n} from './server/middleware/hono/auth.js';\nimport {\n assertSafeRendererPayload,\n type OutboundSourcePolicy,\n UnsafeOutboundSourceError,\n} from './server/security/outbound-source-policy.js';\n\nconst DEFAULT_UPSTREAM = 'http://127.0.0.1:7801';\nconst DEFAULT_PORT = 10_000;\nconst DEFAULT_PROXY_TIMEOUT_MS = 30_000;\nconst DEFAULT_HEALTH_TIMEOUT_MS = 2_000;\nconst DEFAULT_EXPORT_BODY_BYTES = 4 * 1024 * 1024;\nconst DEFAULT_RASTERIZE_BODY_BYTES = 32 * 1024 * 1024;\nconst DEFAULT_RESPONSE_BYTES = 24 * 1024 * 1024;\nconst MAX_CHART_DIMENSION = 4_096;\nconst MAX_CHART_PIXELS = 16_000_000;\nconst MAX_CHART_SCALE = 4;\n\ntype FetchImplementation = typeof fetch;\n\nexport interface RenderServerOptions {\n upstreamUrl?: string;\n proxyTimeoutMs?: number;\n healthTimeoutMs?: number;\n maxExportBodyBytes?: number;\n maxRasterizeBodyBytes?: number;\n maxResponseBytes?: number;\n maxConcurrent?: number;\n exportRateLimit?: number;\n rasterizeRateLimit?: number;\n rateLimitWindowMs?: number;\n trustProxyHeaders?: boolean;\n auth?: ApiKeyAuthOptions;\n sourcePolicy?: OutboundSourcePolicy;\n fetch?: FetchImplementation;\n getRasterizer?: () => PptxRasterizer;\n}\n\nclass UpstreamResponseTooLargeError extends Error {}\n\nfunction positiveInteger(value: string | undefined, fallback: number): number {\n const parsed = Number.parseInt(value ?? '', 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;\n}\n\n/**\n * Only the two explicitly local environments relax defaults. `staging`, `prod`,\n * or a typo must not silently downgrade auth, rate limits, or source policy.\n */\nfunction isHardenedEnv(): boolean {\n const nodeEnv = process.env.NODE_ENV;\n return (\n nodeEnv !== undefined && nodeEnv !== 'development' && nodeEnv !== 'test'\n );\n}\n\nfunction envAuthOptions(): ApiKeyAuthOptions {\n const production = isHardenedEnv();\n const requestedMode = process.env.RENDER_AUTH_MODE;\n const mode =\n requestedMode === 'auto' ||\n requestedMode === 'required' ||\n requestedMode === 'disabled'\n ? requestedMode\n : production\n ? 'required'\n : 'auto';\n return {\n mode,\n apiKey: process.env.RENDER_API_KEY || process.env.API_KEY,\n headerName: process.env.RENDER_API_KEY_HEADER || 'x-api-key',\n };\n}\n\nfunction envSourcePolicy(): OutboundSourcePolicy {\n const requestedMode = process.env.OUTBOUND_SOURCE_MODE;\n const mode =\n requestedMode === 'safe' || requestedMode === 'development'\n ? requestedMode\n : isHardenedEnv()\n ? 'safe'\n : 'development';\n return {\n mode,\n allowedHosts: (process.env.OUTBOUND_HOST_ALLOWLIST || '')\n .split(',')\n .map((host) => host.trim().toLowerCase())\n .filter(Boolean),\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Resolve one export dimension the way Highcharts does: `exporting.sourceWidth`\n * / `sourceHeight` outrank `chart.width` / `chart.height`, which in turn outrank\n * the 600x400 defaults. Checking only `chart.*` would let a nested `exporting`\n * block request an arbitrarily large raster past the pixel budget below.\n */\nfunction resolveDimension(\n exporting: Record<string, unknown> | undefined,\n chart: Record<string, unknown> | undefined,\n sourceKey: 'sourceWidth' | 'sourceHeight',\n chartKey: 'width' | 'height',\n fallback: number\n): number {\n for (const candidate of [exporting?.[sourceKey], chart?.[chartKey]]) {\n if (candidate === undefined) continue;\n if (\n typeof candidate !== 'number' ||\n !Number.isFinite(candidate) ||\n candidate <= 0 ||\n candidate > MAX_CHART_DIMENSION\n ) {\n throw new HTTPException(400, {\n message: 'Requested chart dimensions are too large',\n });\n }\n return candidate;\n }\n return fallback;\n}\n\nfunction resolveScale(value: unknown, fallback: number): number {\n if (value === undefined) return fallback;\n if (\n typeof value !== 'number' ||\n !Number.isFinite(value) ||\n value <= 0 ||\n value > MAX_CHART_SCALE\n ) {\n throw new HTTPException(400, {\n message: `scale must be between 0 and ${MAX_CHART_SCALE}`,\n });\n }\n return value;\n}\n\nfunction validateExportRequest(\n value: unknown\n): asserts value is Record<string, unknown> {\n if (!isRecord(value)) {\n throw new HTTPException(400, { message: 'Export body must be an object' });\n }\n const allowedKeys = new Set(['infile', 'type', 'b64', 'scale', 'resources']);\n const unknown = Object.keys(value).filter((key) => !allowedKeys.has(key));\n if (unknown.length > 0) {\n throw new HTTPException(400, {\n message: `Unsupported export field: ${unknown[0]}`,\n });\n }\n if (!isRecord(value.infile)) {\n throw new HTTPException(400, {\n message: 'Export infile must be a chart options object',\n });\n }\n if (value.type !== 'png' || value.b64 !== true) {\n throw new HTTPException(400, {\n message: 'Only base64 PNG exports are allowed',\n });\n }\n\n const chart = isRecord(value.infile.chart) ? value.infile.chart : undefined;\n const exporting = isRecord(value.infile.exporting)\n ? value.infile.exporting\n : undefined;\n\n // A request-level `scale` outranks `exporting.scale`; validate both so\n // neither path can smuggle an oversized multiplier past the pixel budget.\n const scale = resolveScale(value.scale, resolveScale(exporting?.scale, 1));\n const width = resolveDimension(exporting, chart, 'sourceWidth', 'width', 600);\n const height = resolveDimension(\n exporting,\n chart,\n 'sourceHeight',\n 'height',\n 400\n );\n\n if (width * height * scale * scale > MAX_CHART_PIXELS) {\n throw new HTTPException(400, {\n message: 'Requested chart dimensions are too large',\n });\n }\n}\n\nfunction isTimeout(error: unknown): boolean {\n const name = (error as { name?: string })?.name;\n return name === 'TimeoutError' || name === 'AbortError';\n}\n\nasync function readLimitedBody(\n response: Response,\n maxBytes: number\n): Promise<Uint8Array> {\n const declaredLength = Number(response.headers.get('content-length'));\n if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {\n await response.body?.cancel();\n throw new UpstreamResponseTooLargeError();\n }\n if (!response.body) return new Uint8Array();\n\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;) {\n const { value, done } = await reader.read();\n if (done) break;\n total += value.byteLength;\n if (total > maxBytes) {\n await reader.cancel();\n throw new UpstreamResponseTooLargeError();\n }\n chunks.push(value);\n }\n\n const output = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n output.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return output;\n}\n\nexport function createRenderServerApp(options: RenderServerOptions = {}): Hono {\n const production = isHardenedEnv();\n const upstream = (\n options.upstreamUrl ||\n process.env.HIGHCHARTS_UPSTREAM_URL ||\n DEFAULT_UPSTREAM\n ).replace(/\\/$/, '');\n const proxyTimeoutMs =\n options.proxyTimeoutMs ??\n positiveInteger(process.env.PROXY_TIMEOUT_MS, DEFAULT_PROXY_TIMEOUT_MS);\n const healthTimeoutMs =\n options.healthTimeoutMs ??\n positiveInteger(process.env.HEALTH_TIMEOUT_MS, DEFAULT_HEALTH_TIMEOUT_MS);\n const maxExportBodyBytes =\n options.maxExportBodyBytes ??\n positiveInteger(\n process.env.MAX_EXPORT_BODY_SIZE,\n DEFAULT_EXPORT_BODY_BYTES\n );\n const maxRasterizeBodyBytes =\n options.maxRasterizeBodyBytes ??\n positiveInteger(\n process.env.MAX_RASTERIZE_BODY_SIZE,\n DEFAULT_RASTERIZE_BODY_BYTES\n );\n const maxResponseBytes =\n options.maxResponseBytes ??\n positiveInteger(\n process.env.MAX_RENDER_RESPONSE_SIZE,\n DEFAULT_RESPONSE_BYTES\n );\n const maxConcurrent =\n options.maxConcurrent ??\n positiveInteger(process.env.MAX_CONCURRENT_RENDERS, production ? 4 : 16);\n const windowMs =\n options.rateLimitWindowMs ??\n positiveInteger(process.env.RENDER_RATE_LIMIT_WINDOW_MS, 15 * 60 * 1000);\n const fetchImpl = options.fetch ?? fetch;\n const sourcePolicy = options.sourcePolicy ?? envSourcePolicy();\n const trustProxyHeaders =\n options.trustProxyHeaders ?? process.env.TRUST_PROXY_HEADERS === 'true';\n\n const app = new Hono();\n const auth = createApiKeyAuthMiddleware(options.auth ?? envAuthOptions());\n const capacity = concurrencyLimiter({ limit: maxConcurrent });\n\n app.use('*', secureHeaders());\n\n app.onError((error, c) => {\n if (error instanceof HTTPException) {\n return c.json({ success: false, error: error.message }, error.status);\n }\n return c.json({ success: false, error: 'Internal server error' }, 500);\n });\n\n app.get('/health', async (c) => {\n try {\n const response = await fetchImpl(`${upstream}/health`, {\n signal: AbortSignal.timeout(healthTimeoutMs),\n });\n if (response.ok) return c.text('ok');\n return c.json({ status: 'degraded', upstream: response.status }, 503);\n } catch {\n return c.json({ status: 'degraded', upstream: 'unreachable' }, 503);\n }\n });\n\n // Admission controls precede `auth` deliberately: middleware runs in order,\n // so authenticating first would answer 401 before the limiter counted the\n // attempt, leaving API-key guessing unbounded.\n registerRasterizeRoute(app, {\n getRasterizer: options.getRasterizer,\n preMiddleware: [\n rateLimiter({\n limit:\n options.rasterizeRateLimit ??\n positiveInteger(\n process.env.RASTERIZE_RATE_LIMIT,\n production ? 30 : 1000\n ),\n window: windowMs,\n namespace: 'rasterize',\n trustProxy: trustProxyHeaders,\n }),\n capacity,\n bodyLimit({\n maxSize: maxRasterizeBodyBytes,\n onError: () => {\n throw new HTTPException(413, { message: 'Request body too large' });\n },\n }),\n auth,\n ],\n sourcePolicy,\n });\n\n app.post(\n '/export',\n rateLimiter({\n limit:\n options.exportRateLimit ??\n positiveInteger(process.env.EXPORT_RATE_LIMIT, production ? 60 : 1000),\n window: windowMs,\n namespace: 'export',\n trustProxy: trustProxyHeaders,\n }),\n capacity,\n bodyLimit({\n maxSize: maxExportBodyBytes,\n onError: () => {\n throw new HTTPException(413, { message: 'Request body too large' });\n },\n }),\n auth,\n async (c) => {\n const contentType = c.req.header('content-type');\n if (!contentType?.toLowerCase().includes('application/json')) {\n throw new HTTPException(415, {\n message: 'Content-Type must be application/json',\n });\n }\n\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n throw new HTTPException(400, { message: 'Invalid JSON body' });\n }\n validateExportRequest(body);\n try {\n assertSafeRendererPayload(body, sourcePolicy);\n } catch (error) {\n if (error instanceof UnsafeOutboundSourceError) {\n throw new HTTPException(400, { message: error.message });\n }\n throw error;\n }\n\n let response: Response;\n try {\n response = await fetchImpl(`${upstream}/export`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'text/plain, image/png, application/json',\n },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(proxyTimeoutMs),\n });\n } catch (error) {\n return isTimeout(error)\n ? c.json(\n {\n error: `Highcharts upstream timed out after ${proxyTimeoutMs}ms`,\n },\n 504\n )\n : c.json({ error: 'Highcharts upstream unreachable' }, 502);\n }\n\n let payload: Uint8Array;\n try {\n payload = await readLimitedBody(response, maxResponseBytes);\n } catch (error) {\n if (error instanceof UpstreamResponseTooLargeError) {\n return c.json({ error: 'Highcharts response too large' }, 502);\n }\n if (isTimeout(error)) {\n return c.json(\n {\n error: `Highcharts upstream timed out after ${proxyTimeoutMs}ms`,\n },\n 504\n );\n }\n return c.json({ error: 'Failed to read Highcharts response' }, 502);\n }\n\n const headers: Record<string, string> = {};\n for (const name of [\n 'content-type',\n 'content-disposition',\n 'cache-control',\n ]) {\n const value = response.headers.get(name);\n if (value) headers[name] = value;\n }\n return c.body(payload, response.status as StatusCode, headers);\n }\n );\n\n for (const route of ['/export', '/rasterize']) {\n app.all(route, (c) => {\n c.header('Allow', 'POST');\n return c.json({ success: false, error: 'Method not allowed' }, 405);\n });\n }\n app.notFound((c) => c.json({ success: false, error: 'Not found' }, 404));\n\n return app;\n}\n\nfunction isMainModule(): boolean {\n if (!process.argv[1]) return false;\n try {\n return resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url));\n } catch {\n return false;\n }\n}\n\nif (isMainModule()) {\n const port = positiveInteger(process.env.PORT, DEFAULT_PORT);\n const app = createRenderServerApp();\n void import('@hono/node-server').then(({ serve }) => {\n serve({ fetch: app.fetch, port, hostname: '0.0.0.0' }, (info) => {\n // eslint-disable-next-line no-console\n console.log(`[jto-render-server] listening on :${info.port}`);\n });\n });\n}\n","/**\n * Shared POST /rasterize route — one validated handler mounted on BOTH the\n * playground format router and the standalone jto-render-server, so the public\n * and in-app rasterize surfaces can't drift in validation, limits, or error\n * mapping. Renders a single-slide pptx presentation to a PNG.\n */\n\nimport type { Hono, MiddlewareHandler } from 'hono';\nimport { Type } from '@sinclair/typebox';\nimport { bodyLimit } from 'hono/body-limit';\nimport { HTTPException } from 'hono/http-exception';\nimport {\n clampVisualDpi,\n DEFAULT_VISUAL_DPI,\n MIN_VISUAL_DPI,\n MAX_VISUAL_DPI,\n type PptxRasterizer,\n} from '@json-to-office/shared';\nimport { createLibreOfficePptxRasterizer } from '@json-to-office/jto-cli';\nimport { tbValidator, getValidated } from './lib/typebox-validator.js';\nimport {\n assertSafeOutboundSources,\n type OutboundSourcePolicy,\n UnsafeOutboundSourceError,\n} from './security/outbound-source-policy.js';\n\n/** Body for POST /rasterize: a single-slide pptx presentation + optional dpi. */\nexport const RasterizeRequestSchema = Type.Object(\n {\n presentation: Type.Object({}, { additionalProperties: true }),\n dpi: Type.Optional(\n Type.Number({ minimum: MIN_VISUAL_DPI, maximum: MAX_VISUAL_DPI })\n ),\n },\n { additionalProperties: false }\n);\n\n// One rasterizer per process — shares the on-disk content-addressed cache\n// across every /rasterize surface in the process.\nlet sharedRasterizer: PptxRasterizer | undefined;\nexport function getSharedRasterizer(): PptxRasterizer {\n if (!sharedRasterizer) {\n sharedRasterizer = createLibreOfficePptxRasterizer();\n }\n return sharedRasterizer;\n}\n\nconst jsonOnly: MiddlewareHandler = async (c, next) => {\n const contentType = c.req.header('content-type');\n if (!contentType || !contentType.includes('application/json')) {\n throw new HTTPException(400, {\n message: 'Content-Type must be application/json',\n });\n }\n await next();\n};\n\n/**\n * Register `POST /rasterize` on a Hono router with body-size limit, content-type\n * + schema validation (dpi clamped to [MIN,MAX]_VISUAL_DPI), the shared\n * rasterizer, and structured 400/413/503/500 error mapping.\n *\n * @param preMiddleware - extra middleware (e.g. a rate limiter) run first.\n */\nexport function registerRasterizeRoute(\n router: Hono<any>,\n options: {\n getRasterizer?: () => PptxRasterizer;\n preMiddleware?: MiddlewareHandler[];\n sourcePolicy?: OutboundSourcePolicy;\n onError?: (error: unknown) => void;\n } = {}\n): void {\n const getRasterizer = options.getRasterizer ?? getSharedRasterizer;\n\n router.post(\n '/rasterize',\n ...(options.preMiddleware ?? []),\n bodyLimit({\n maxSize: 32 * 1024 * 1024,\n onError: () => {\n throw new HTTPException(413, { message: 'Request body too large' });\n },\n }),\n jsonOnly,\n tbValidator(RasterizeRequestSchema),\n async (c) => {\n const { presentation, dpi } = getValidated<{\n presentation: unknown;\n dpi?: number;\n }>(c, 'json');\n\n try {\n if (options.sourcePolicy) {\n assertSafeOutboundSources(\n presentation,\n options.sourcePolicy,\n 'presentation'\n );\n }\n const result = await getRasterizer()({\n presentation,\n dpi: clampVisualDpi(dpi ?? DEFAULT_VISUAL_DPI),\n });\n return c.json(result);\n } catch (error) {\n options.onError?.(error);\n if (error instanceof HTTPException) throw error;\n if (error instanceof UnsafeOutboundSourceError) {\n throw new HTTPException(400, { message: error.message });\n }\n const msg =\n error instanceof Error ? error.message.toLowerCase() : String(error);\n if (msg.includes('not found') || msg.includes('rasterization needs')) {\n throw new HTTPException(503, { message: (error as Error).message });\n }\n if (msg.includes('invalid') || msg.includes('validation')) {\n throw new HTTPException(400, { message: (error as Error).message });\n }\n throw new HTTPException(500, {\n message: 'Internal server error during rasterization',\n });\n }\n }\n );\n}\n","import { TSchema } from '@sinclair/typebox';\nimport { Value } from '@sinclair/typebox/value';\nimport { Context, Env, ValidationTargets, MiddlewareHandler } from 'hono';\nimport { HTTPException } from 'hono/http-exception';\n\n/**\n * Auto-detect raw document JSON (has `name` + `children` but no `jsonDefinition`)\n * and wrap it so callers can POST the document tree directly.\n */\nfunction autoWrapDocumentBody(value: unknown): unknown {\n if (\n typeof value === 'object' &&\n value !== null &&\n 'name' in value &&\n 'children' in value &&\n !('jsonDefinition' in value)\n ) {\n return { jsonDefinition: value };\n }\n return value;\n}\n\nexport function tbValidator<\n T extends TSchema,\n E extends Env = Env,\n P extends string = string,\n>(schema: T) {\n return (async (c: Context<E, P>, next: () => Promise<void>) => {\n let value: unknown;\n\n try {\n value = await c.req.json();\n } catch {\n throw new HTTPException(400, {\n message: 'Invalid JSON in request body',\n });\n }\n\n value = autoWrapDocumentBody(value);\n\n const isValid = Value.Check(schema, value);\n\n if (isValid) {\n (c.req as { validatedData?: Record<string, unknown> }).validatedData = {\n json: value,\n };\n await next();\n } else {\n const errors = [...Value.Errors(schema, value)].map((error) => ({\n path: error.path,\n message: error.message,\n value: error.value,\n }));\n\n const errorMessages = errors.map((e) => `${e.path || '/'}: ${e.message}`);\n\n throw new HTTPException(400, {\n message:\n errorMessages.length === 1\n ? errorMessages[0]\n : `Validation failed:\\n${errorMessages.join('\\n')}`,\n cause: { errors },\n });\n }\n }) as MiddlewareHandler<E, P, Record<string, unknown>>;\n}\n\nexport function getValidated<T>(\n c: Context,\n target: keyof ValidationTargets\n): T {\n const validatedData = (c.req as { validatedData?: Record<string, unknown> })\n .validatedData;\n if (!validatedData || !validatedData[target]) {\n throw new Error(`No validated data found for target: ${target}`);\n }\n return validatedData[target] as T;\n}\n","import type { OutboundSourceMode } from '../config/index.js';\n\nexport interface OutboundSourcePolicy {\n mode: OutboundSourceMode;\n allowedHosts: readonly string[];\n}\n\nexport class UnsafeOutboundSourceError extends Error {\n constructor(\n readonly path: string,\n reason: string\n ) {\n super(`Unsafe outbound source at ${path}: ${reason}`);\n this.name = 'UnsafeOutboundSourceError';\n }\n}\n\ntype JsonRecord = Record<string, unknown>;\n\nconst DANGEROUS_KEYS = new Set(['__proto__', 'prototype', 'constructor']);\nconst COMPONENT_SOURCE_NAMES = new Set(['image']);\n\nfunction isRecord(value: unknown): value is JsonRecord {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction hostMatches(host: string, pattern: string): boolean {\n const normalized = pattern.toLowerCase().replace(/\\.$/, '');\n if (normalized.startsWith('*.')) {\n const suffix = normalized.slice(1);\n return host.endsWith(suffix) && host.length > suffix.length;\n }\n return host === normalized;\n}\n\nfunction isPrivateIpv4(host: string): boolean {\n const parts = host.split('.');\n if (parts.length !== 4) return false;\n const octets = parts.map(Number);\n if (\n octets.some(\n (part, index) =>\n !Number.isInteger(part) ||\n part < 0 ||\n part > 255 ||\n String(part) !== parts[index]\n )\n ) {\n return false;\n }\n const [a, b] = octets;\n return (\n a === 0 ||\n a === 10 ||\n a === 127 ||\n (a === 100 && b >= 64 && b <= 127) ||\n (a === 169 && b === 254) ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 192 && (b === 0 || b === 168)) ||\n (a === 198 && (b === 18 || b === 19)) ||\n a >= 224\n );\n}\n\nfunction isPrivateHost(host: string): boolean {\n const normalized = host\n .toLowerCase()\n .replace(/^\\[|\\]$/g, '')\n .replace(/\\.$/, '');\n if (\n normalized === 'localhost' ||\n normalized.endsWith('.localhost') ||\n normalized === '::' ||\n normalized === '::1'\n ) {\n return true;\n }\n if (isPrivateIpv4(normalized)) return true;\n if (normalized.startsWith('::ffff:')) {\n const mapped = normalized.slice('::ffff:'.length);\n if (isPrivateIpv4(mapped)) return true;\n const groups = mapped.match(/^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);\n if (groups) {\n const value =\n Number.parseInt(groups[1], 16) * 0x1_0000 +\n Number.parseInt(groups[2], 16);\n return isPrivateIpv4(\n [\n value >>> 24,\n (value >>> 16) & 255,\n (value >>> 8) & 255,\n value & 255,\n ].join('.')\n );\n }\n }\n const firstIpv6Group = Number.parseInt(normalized.split(':')[0] || '0', 16);\n return (\n (firstIpv6Group >= 0xfc00 && firstIpv6Group <= 0xfdff) ||\n (firstIpv6Group >= 0xfe80 && firstIpv6Group <= 0xfebf)\n );\n}\n\nfunction assertAllowedUrl(\n source: string,\n path: string,\n allowedHosts: readonly string[]\n): void {\n if (source.startsWith('data:')) return;\n\n let url: URL;\n try {\n url = new URL(source);\n } catch {\n throw new UnsafeOutboundSourceError(\n path,\n 'local and relative file paths are disabled for HTTP requests'\n );\n }\n\n if (url.protocol !== 'https:') {\n throw new UnsafeOutboundSourceError(path, 'only HTTPS URLs are allowed');\n }\n if (url.username || url.password) {\n throw new UnsafeOutboundSourceError(\n path,\n 'URLs containing credentials are not allowed'\n );\n }\n\n const host = url.hostname.toLowerCase().replace(/\\.$/, '');\n if (isPrivateHost(host)) {\n throw new UnsafeOutboundSourceError(\n path,\n `private or local host \"${host}\" is not allowed`\n );\n }\n if (!allowedHosts.some((pattern) => hostMatches(host, pattern))) {\n throw new UnsafeOutboundSourceError(\n path,\n `host \"${host}\" is not in OUTBOUND_HOST_ALLOWLIST`\n );\n }\n}\n\nfunction extractCssReferences(input: string): string[] {\n const references: string[] = [];\n const urlPattern = /url\\(\\s*(['\"]?)([^)'\"\\s]+)\\1\\s*\\)/gi;\n const importPattern = /@import\\s+(?:url\\(\\s*)?(['\"])([^'\"]+)\\1/gi;\n for (const pattern of [urlPattern, importPattern]) {\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(input))) references.push(match[2]);\n }\n return references;\n}\n\nfunction extractAbsoluteUrls(input: string): string[] {\n return input.match(/(?:https?|file):\\/\\/[^\\s'\"<>)}\\]]+/gi) ?? [];\n}\n\nfunction assertSafeSvg(\n svg: string,\n path: string,\n allowedHosts: readonly string[]\n): void {\n if (\n /<\\s*script\\b/i.test(svg) ||\n /<\\s*(?:foreignObject|iframe|object|embed)\\b/i.test(svg) ||\n /\\bon[a-z]+\\s*=/i.test(svg)\n ) {\n throw new UnsafeOutboundSourceError(path, 'active SVG content is disabled');\n }\n if (/<!\\s*(?:DOCTYPE|ENTITY)\\b/i.test(svg)) {\n throw new UnsafeOutboundSourceError(\n path,\n 'SVG document types and entities are disabled'\n );\n }\n\n const refPattern = /\\b(?:href|src)\\s*=\\s*(['\"])(.*?)\\1/gi;\n let match: RegExpExecArray | null;\n while ((match = refPattern.exec(svg))) {\n const ref = match[2].trim();\n if (!ref || ref.startsWith('#') || ref.startsWith('data:')) continue;\n assertAllowedUrl(ref, path, allowedHosts);\n }\n for (const ref of extractCssReferences(svg)) {\n if (ref.startsWith('#') || ref.startsWith('data:')) continue;\n assertAllowedUrl(ref, path, allowedHosts);\n }\n}\n\nfunction assertSafeEncodedImage(\n source: string,\n path: string,\n allowedHosts: readonly string[]\n): void {\n let candidate = source.trim();\n let declaredSvg = false;\n\n if (candidate.startsWith('data:')) {\n const comma = candidate.indexOf(',');\n if (comma < 0) {\n throw new UnsafeOutboundSourceError(path, 'malformed image data URL');\n }\n const metadata = candidate.slice(5, comma);\n declaredSvg = metadata.split(';')[0].toLowerCase() === 'image/svg+xml';\n if (!declaredSvg) return;\n const payload = candidate.slice(comma + 1);\n try {\n candidate = /(?:^|;)base64(?:;|$)/i.test(metadata)\n ? Buffer.from(payload, 'base64').toString('utf8')\n : decodeURIComponent(payload);\n } catch {\n throw new UnsafeOutboundSourceError(path, 'malformed SVG data URL');\n }\n } else if (!/<\\s*svg\\b/i.test(candidate)) {\n // The documented `base64` shape is a data URL, but inspect raw base64 too\n // so a migration client cannot smuggle active SVG through the same field.\n candidate = Buffer.from(candidate, 'base64').toString('utf8');\n }\n\n const svgStart = candidate.search(/<\\s*svg\\b/i);\n if (svgStart < 0) {\n if (declaredSvg) {\n throw new UnsafeOutboundSourceError(path, 'malformed SVG image data');\n }\n return;\n }\n assertSafeSvg(candidate, path, allowedHosts);\n}\n\nfunction assertSafeResources(\n resources: JsonRecord,\n path: string,\n allowedHosts: readonly string[]\n): void {\n if (typeof resources.js === 'string' && resources.js.trim()) {\n throw new UnsafeOutboundSourceError(\n `${path}.js`,\n 'remote renderer JavaScript resources are disabled'\n );\n }\n if (typeof resources.css === 'string') {\n for (const ref of extractCssReferences(resources.css)) {\n if (ref.startsWith('data:')) continue;\n assertAllowedUrl(ref, `${path}.css`, allowedHosts);\n }\n }\n // Highcharts loads `resources.files` as JavaScript. Allowlisting a host for\n // fonts or stylesheets must not also grant it script execution inside the\n // renderer, so reject the field outright rather than URL-checking it.\n if (Array.isArray(resources.files) && resources.files.length > 0) {\n throw new UnsafeOutboundSourceError(\n `${path}.files`,\n 'remote renderer JavaScript resources are disabled'\n );\n }\n}\n\nfunction assertNoRemoteReferences(\n value: unknown,\n path: string,\n allowedHosts: readonly string[],\n seen: WeakSet<object>\n): void {\n if (typeof value === 'string') {\n if (\n /\\b(?:fetch|XMLHttpRequest|WebSocket|EventSource)\\s*\\(/i.test(value) ||\n /\\b(?:javascript|file):/i.test(value)\n ) {\n throw new UnsafeOutboundSourceError(\n path,\n 'network-capable JavaScript and file URLs are disabled'\n );\n }\n for (const url of [\n ...extractAbsoluteUrls(value),\n ...extractCssReferences(value),\n ]) {\n if (url.startsWith('data:')) continue;\n assertAllowedUrl(url, path, allowedHosts);\n }\n if (/^\\/\\//.test(value.trim())) {\n throw new UnsafeOutboundSourceError(\n path,\n 'protocol-relative URLs are disabled'\n );\n }\n return;\n }\n if (!value || typeof value !== 'object' || seen.has(value)) return;\n seen.add(value);\n if (Array.isArray(value)) {\n value.forEach((entry, index) =>\n assertNoRemoteReferences(entry, `${path}[${index}]`, allowedHosts, seen)\n );\n return;\n }\n for (const [key, child] of Object.entries(value as JsonRecord)) {\n assertNoRemoteReferences(child, `${path}.${key}`, allowedHosts, seen);\n }\n}\n\nfunction visit(\n value: unknown,\n path: string,\n containerKey: string | undefined,\n allowedHosts: readonly string[],\n seen: WeakSet<object>\n): void {\n if (!value || typeof value !== 'object' || seen.has(value)) return;\n seen.add(value);\n\n if (Array.isArray(value)) {\n value.forEach((entry, index) =>\n visit(entry, `${path}[${index}]`, containerKey, allowedHosts, seen)\n );\n return;\n }\n\n const record = value as JsonRecord;\n for (const key of Object.keys(record)) {\n if (DANGEROUS_KEYS.has(key)) {\n throw new UnsafeOutboundSourceError(\n `${path}.${key}`,\n 'prototype mutation keys are disabled'\n );\n }\n }\n\n const kind = typeof record.kind === 'string' ? record.kind : undefined;\n if (kind === 'file' && typeof record.path === 'string') {\n throw new UnsafeOutboundSourceError(\n `${path}.path`,\n 'local file sources are disabled for HTTP requests'\n );\n }\n if (\n (kind === 'url' || kind === 'variable') &&\n typeof record.url === 'string'\n ) {\n assertAllowedUrl(record.url, `${path}.url`, allowedHosts);\n }\n\n const componentName =\n typeof record.name === 'string' ? record.name.toLowerCase() : undefined;\n const props = isRecord(record.props) ? record.props : undefined;\n if (componentName && props) {\n if (\n COMPONENT_SOURCE_NAMES.has(componentName) &&\n typeof props.path === 'string'\n ) {\n assertAllowedUrl(props.path, `${path}.props.path`, allowedHosts);\n assertSafeEncodedImage(props.path, `${path}.props.path`, allowedHosts);\n }\n if (\n COMPONENT_SOURCE_NAMES.has(componentName) &&\n typeof props.base64 === 'string'\n ) {\n assertSafeEncodedImage(\n props.base64,\n `${path}.props.base64`,\n allowedHosts\n );\n }\n if (\n (componentName === 'highcharts' || componentName === 'visual') &&\n typeof props.serverUrl === 'string'\n ) {\n assertAllowedUrl(\n props.serverUrl,\n `${path}.props.serverUrl`,\n allowedHosts\n );\n }\n if (componentName === 'highcharts') {\n if (isRecord(props.resources)) {\n assertSafeResources(\n props.resources,\n `${path}.props.resources`,\n allowedHosts\n );\n }\n assertNoRemoteReferences(\n props.options,\n `${path}.props.options`,\n allowedHosts,\n new WeakSet()\n );\n }\n }\n\n // Slide/template/visual canvas backgrounds store an image source in a plain\n // `{ image: { path } }` object rather than an image component.\n if (containerKey === 'image' && typeof record.path === 'string') {\n assertAllowedUrl(record.path, `${path}.path`, allowedHosts);\n assertSafeEncodedImage(record.path, `${path}.path`, allowedHosts);\n }\n if (typeof record.svg === 'string') {\n assertSafeSvg(record.svg, `${path}.svg`, allowedHosts);\n }\n if (containerKey === 'resources') {\n assertSafeResources(record, path, allowedHosts);\n }\n\n for (const [key, child] of Object.entries(record)) {\n visit(child, `${path}.${key}`, key, allowedHosts, seen);\n }\n}\n\n/**\n * Reject request-controlled sources capable of making the server read local\n * files or reach arbitrary network hosts. Development mode intentionally\n * preserves local playground workflows.\n */\nexport function assertSafeOutboundSources(\n value: unknown,\n policy: OutboundSourcePolicy,\n rootPath = 'request'\n): void {\n if (policy.mode === 'development') return;\n\n let parsed = value;\n if (typeof value === 'string') {\n try {\n parsed = JSON.parse(value);\n } catch {\n // Structural validation owns malformed JSON reporting.\n return;\n }\n }\n visit(parsed, rootPath, undefined, policy.allowedHosts, new WeakSet());\n}\n\n/** Extra checks for payloads executed by the Chromium-based chart renderer. */\nexport function assertSafeRendererPayload(\n value: unknown,\n policy: OutboundSourcePolicy\n): void {\n assertSafeOutboundSources(value, policy, 'export');\n if (policy.mode === 'development' || !isRecord(value)) return;\n assertNoRemoteReferences(\n value.infile,\n 'export.infile',\n policy.allowedHosts,\n new WeakSet()\n );\n}\n","import { MiddlewareHandler } from 'hono';\nimport { HTTPException } from 'hono/http-exception';\n\nexport interface RateLimitOptions {\n limit: number;\n window: number; // milliseconds\n keyGenerator?: (c: any) => string;\n namespace?: string | ((c: any) => string);\n maxEntries?: number;\n trustProxy?: boolean;\n}\n\nfunction clientAddress(c: any, trustProxy: boolean): string {\n if (trustProxy) {\n const forwarded =\n c.req.header('X-Real-IP') ||\n c.req.header('X-Forwarded-For')?.split(',')[0]?.trim();\n if (forwarded) return forwarded;\n }\n const incoming = c.env?.incoming || c.env?.req;\n return incoming?.socket?.remoteAddress || 'anonymous';\n}\n\n/**\n * Bounded, process-local rate limiting. Distributed deployments should put a\n * shared limiter at the edge too; this remains the last line of defence for a\n * single worker and never grows without bound.\n */\nexport const rateLimiter = (options: RateLimitOptions): MiddlewareHandler => {\n const { limit, window, keyGenerator } = options;\n const maxEntries = options.maxEntries ?? 10_000;\n const rateLimitStore = new Map<\n string,\n { count: number; resetTime: number }\n >();\n let lastCleanup = 0;\n\n return async (c, next) => {\n const clientKey = keyGenerator\n ? keyGenerator(c)\n : clientAddress(c, options.trustProxy === true);\n const namespace =\n typeof options.namespace === 'function'\n ? options.namespace(c)\n : options.namespace || `${c.req.method}:${c.req.path}`;\n const key = `${namespace}:${String(clientKey).slice(0, 256)}`;\n const now = Date.now();\n\n // Amortize cleanup; scanning an attacker-filled map on every request would\n // turn the protection itself into a CPU denial of service.\n if (now - lastCleanup >= Math.min(window, 60_000)) {\n for (const [storedKey, value] of rateLimitStore.entries()) {\n if (value.resetTime <= now) rateLimitStore.delete(storedKey);\n }\n lastCleanup = now;\n }\n\n const record = rateLimitStore.get(key);\n\n if (!record) {\n if (rateLimitStore.size >= maxEntries) {\n const oldestKey = rateLimitStore.keys().next().value as\n | string\n | undefined;\n if (oldestKey) rateLimitStore.delete(oldestKey);\n }\n rateLimitStore.set(key, {\n count: 1,\n resetTime: now + window,\n });\n } else if (record.resetTime <= now) {\n // Window expired, reset\n record.count = 1;\n record.resetTime = now + window;\n } else if (record.count >= limit) {\n // Rate limit exceeded\n const retryAfter = Math.ceil((record.resetTime - now) / 1000);\n\n c.header('X-RateLimit-Limit', String(limit));\n c.header('X-RateLimit-Remaining', '0');\n c.header('X-RateLimit-Reset', String(record.resetTime));\n c.header('Retry-After', String(retryAfter));\n\n throw new HTTPException(429, {\n message: 'Too many requests, please try again later',\n });\n } else {\n // Increment count\n record.count++;\n }\n\n // Add rate limit headers\n const currentRecord = rateLimitStore.get(key)!;\n c.header('X-RateLimit-Limit', String(limit));\n c.header(\n 'X-RateLimit-Remaining',\n String(Math.max(0, limit - currentRecord.count))\n );\n c.header('X-RateLimit-Reset', String(currentRecord.resetTime));\n\n await next();\n };\n};\n","import type { MiddlewareHandler } from 'hono';\n\nexport interface ConcurrencyLimitOptions {\n limit: number;\n retryAfterSeconds?: number;\n}\n\n/**\n * Process-local admission control for expensive generation/rendering work.\n * It rejects excess work instead of queueing unbounded request bodies in RAM.\n */\nexport function concurrencyLimiter(\n options: ConcurrencyLimitOptions\n): MiddlewareHandler {\n const limit = Math.max(1, Math.floor(options.limit));\n const retryAfterSeconds = options.retryAfterSeconds ?? 1;\n let active = 0;\n\n return async (c, next) => {\n c.header('X-Concurrency-Limit', String(limit));\n if (active >= limit) {\n c.header('Retry-After', String(retryAfterSeconds));\n return c.json(\n {\n success: false,\n error: 'Server is at capacity',\n code: 'CONCURRENCY_LIMIT_EXCEEDED',\n },\n 503\n );\n }\n\n active += 1;\n c.header('X-Concurrency-Remaining', String(Math.max(0, limit - active)));\n try {\n await next();\n } finally {\n active -= 1;\n }\n };\n}\n","import { createHash, timingSafeEqual } from 'node:crypto';\nimport { MiddlewareHandler } from 'hono';\nimport { config } from '../../config';\nimport type { ApiAuthMode } from '../../config';\n\nexport interface ApiKeyAuthOptions {\n mode: ApiAuthMode;\n apiKey?: string;\n headerName?: string;\n}\n\nfunction keysEqual(received: string, expected: string): boolean {\n const receivedDigest = createHash('sha256').update(received).digest();\n const expectedDigest = createHash('sha256').update(expected).digest();\n return timingSafeEqual(receivedDigest, expectedDigest);\n}\n\nfunction readCredential(\n headers: Headers,\n headerName: string\n): string | undefined {\n const direct = headers.get(headerName)?.trim();\n if (direct) {\n if (\n headerName.toLowerCase() === 'authorization' &&\n direct.toLowerCase().startsWith('bearer ')\n ) {\n return direct.slice(7).trim() || undefined;\n }\n return direct;\n }\n\n const authorization = headers.get('authorization')?.trim();\n if (authorization?.toLowerCase().startsWith('bearer ')) {\n return authorization.slice(7).trim() || undefined;\n }\n return undefined;\n}\n\n/**\n * API Key authentication middleware for Hono\n */\nexport function createApiKeyAuthMiddleware(\n options: ApiKeyAuthOptions\n): MiddlewareHandler {\n const headerName = options.headerName || 'x-api-key';\n\n return async (c, next) => {\n if (c.req.method === 'OPTIONS' || options.mode === 'disabled') {\n return next();\n }\n\n if (!options.apiKey) {\n if (options.mode === 'auto') return next();\n return c.json(\n {\n success: false,\n error: 'API authentication is not configured',\n code: 'AUTH_CONFIGURATION_ERROR',\n },\n 503\n );\n }\n\n const apiKey = readCredential(c.req.raw.headers, headerName);\n\n if (!apiKey) {\n return c.json(\n {\n success: false,\n error: 'API key required',\n code: 'UNAUTHORIZED',\n },\n 401\n );\n }\n\n if (!keysEqual(apiKey, options.apiKey)) {\n return c.json(\n {\n success: false,\n error: 'Invalid API key',\n code: 'UNAUTHORIZED',\n },\n 401\n );\n }\n\n await next();\n };\n}\n\nexport const apiKeyAuthMiddleware = createApiKeyAuthMiddleware({\n mode: config.API_AUTH_MODE,\n apiKey: config.API_KEY,\n headerName: config.API_KEY_HEADER,\n});\n","import dotenv from 'dotenv';\n\ndotenv.config();\n\nexport type ApiAuthMode = 'auto' | 'required' | 'disabled';\nexport type OutboundSourceMode = 'development' | 'safe';\n\nfunction positiveInteger(value: string | undefined, fallback: number): number {\n const parsed = Number.parseInt(value ?? '', 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;\n}\n\nfunction parseAuthMode(\n value: string | undefined,\n nodeEnv: 'development' | 'production' | 'test'\n): ApiAuthMode {\n if (value === 'auto' || value === 'required' || value === 'disabled') {\n return value;\n }\n // Public production APIs must opt out explicitly. Local playgrounds remain\n // zero-config; supplying a key there preserves the historical auto-enable.\n return nodeEnv === 'production' ? 'required' : 'auto';\n}\n\nfunction parseOutboundSourceMode(\n value: string | undefined,\n nodeEnv: 'development' | 'production' | 'test'\n): OutboundSourceMode {\n if (value === 'development' || value === 'safe') return value;\n return nodeEnv === 'production' ? 'safe' : 'development';\n}\n\n/**\n * Only the two explicitly local environments keep permissive defaults. Every\n * other value — `staging`, `prod`, a typo — gets production-grade hardening, so\n * a mislabelled deployment cannot silently disable auth, rate limits, or the\n * outbound-source (SSRF) checks.\n */\nfunction normalizeNodeEnv(\n value: string | undefined\n): 'development' | 'production' | 'test' {\n return value === 'development' || value === 'test' ? value : 'production';\n}\n\nfunction parseEnv(env: NodeJS.ProcessEnv) {\n const nodeEnv = normalizeNodeEnv(env.NODE_ENV || 'development');\n return {\n NODE_ENV: nodeEnv,\n // No PORT here: the listener is opened by the dev server from the CLI\n // config (`-p` > `server.port` > `PORT` env > format default), so a second\n // copy of it in this module would be silently ignored.\n CORS_ORIGIN: env.CORS_ORIGIN || '*',\n API_KEY: env.API_KEY,\n API_KEY_HEADER: env.API_KEY_HEADER || 'x-api-key',\n API_AUTH_MODE: parseAuthMode(env.API_AUTH_MODE, nodeEnv),\n RATE_LIMIT_WINDOW_MS: positiveInteger(env.RATE_LIMIT_WINDOW_MS, 900_000),\n RATE_LIMIT_MAX: positiveInteger(\n env.RATE_LIMIT_MAX,\n nodeEnv === 'production' ? 100 : 1000\n ),\n TRUST_PROXY_HEADERS: env.TRUST_PROXY_HEADERS === 'true',\n MAX_FILE_SIZE: positiveInteger(env.MAX_FILE_SIZE, 10 * 1024 * 1024),\n MAX_REQUEST_BODY_SIZE: positiveInteger(\n env.MAX_REQUEST_BODY_SIZE,\n 32 * 1024 * 1024\n ),\n MAX_CONCURRENT_REQUESTS: positiveInteger(\n env.MAX_CONCURRENT_REQUESTS,\n nodeEnv === 'production' ? 8 : 64\n ),\n OUTBOUND_SOURCE_MODE: parseOutboundSourceMode(\n env.OUTBOUND_SOURCE_MODE,\n nodeEnv\n ),\n OUTBOUND_HOST_ALLOWLIST: (env.OUTBOUND_HOST_ALLOWLIST || '')\n .split(',')\n .map((host) => host.trim().toLowerCase())\n .filter(Boolean),\n LIBREOFFICE_PATH: env.LIBREOFFICE_PATH,\n LIBREOFFICE_TIMEOUT_MS: env.LIBREOFFICE_TIMEOUT_MS\n ? positiveInteger(env.LIBREOFFICE_TIMEOUT_MS, 30000)\n : 30000,\n LOG_LEVEL: (env.LOG_LEVEL || 'info') as 'error' | 'warn' | 'info' | 'debug',\n CACHE_ENABLED: env.CACHE_ENABLED !== 'false',\n CACHE_MAX_SIZE_MB: positiveInteger(env.CACHE_MAX_SIZE_MB, 100),\n CACHE_TTL_SECONDS: positiveInteger(env.CACHE_TTL_SECONDS, 3600),\n CACHE_MAX_ITEMS: positiveInteger(env.CACHE_MAX_ITEMS, 1000),\n };\n}\n\nconst parsedEnv = parseEnv(process.env);\n\nexport const config = {\n ...parsedEnv,\n\n isDevelopment: parsedEnv.NODE_ENV === 'development',\n isProduction: parsedEnv.NODE_ENV === 'production',\n isTest: parsedEnv.NODE_ENV === 'test',\n\n features: {\n apiKey: parsedEnv.API_AUTH_MODE !== 'disabled',\n cache: parsedEnv.CACHE_ENABLED,\n },\n\n cors: {\n origin:\n parsedEnv.CORS_ORIGIN === '*'\n ? parsedEnv.CORS_ORIGIN\n : parsedEnv.CORS_ORIGIN.split(',').map((o) => o.trim()),\n credentials: true,\n },\n\n rateLimit: {\n windowMs: parsedEnv.RATE_LIMIT_WINDOW_MS,\n max: parsedEnv.RATE_LIMIT_MAX,\n trustProxy: parsedEnv.TRUST_PROXY_HEADERS,\n },\n\n requestLimits: {\n maxBodySize: parsedEnv.MAX_REQUEST_BODY_SIZE,\n maxConcurrent: parsedEnv.MAX_CONCURRENT_REQUESTS,\n maxFileSize: parsedEnv.MAX_FILE_SIZE,\n },\n\n outboundSources: {\n mode: parsedEnv.OUTBOUND_SOURCE_MODE,\n allowedHosts: parsedEnv.OUTBOUND_HOST_ALLOWLIST,\n },\n\n cache: {\n enabled: parsedEnv.CACHE_ENABLED,\n maxSizeMB: parsedEnv.CACHE_MAX_SIZE_MB,\n ttlSeconds: parsedEnv.CACHE_TTL_SECONDS,\n maxItems: parsedEnv.CACHE_MAX_ITEMS,\n },\n} as const;\n\nexport type ServerConfig = typeof config;\n"],"mappings":"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","names":["bodyLimit","HTTPException","HTTPException","HTTPException","HTTPException","positiveInteger","isRecord","HTTPException","bodyLimit"]}
|
|
1
|
+
{"version":3,"sources":["../src/render-server.ts","../src/server/rasterize-route.ts","../src/server/lib/typebox-validator.ts","../src/server/security/outbound-source-policy.ts","../src/server/middleware/hono/rate-limit.ts","../src/server/middleware/hono/concurrency-limit.ts","../src/server/middleware/hono/auth.ts","../src/server/config/index.ts"],"sourcesContent":["/** Public front server for the chart exporter and pptx rasterizer. */\n\nimport { fileURLToPath } from 'node:url';\nimport { resolve } from 'node:path';\nimport { Hono } from 'hono';\nimport { bodyLimit } from 'hono/body-limit';\nimport { HTTPException } from 'hono/http-exception';\nimport { secureHeaders } from 'hono/secure-headers';\nimport type { StatusCode } from 'hono/utils/http-status';\nimport type { PptxRasterizer } from '@json-to-office/shared';\nimport { registerRasterizeRoute } from './server/rasterize-route.js';\nimport { rateLimiter } from './server/middleware/hono/rate-limit.js';\nimport { concurrencyLimiter } from './server/middleware/hono/concurrency-limit.js';\nimport {\n createApiKeyAuthMiddleware,\n type ApiKeyAuthOptions,\n} from './server/middleware/hono/auth.js';\nimport {\n assertSafeRendererPayload,\n type OutboundSourcePolicy,\n UnsafeOutboundSourceError,\n} from './server/security/outbound-source-policy.js';\n\nconst DEFAULT_UPSTREAM = 'http://127.0.0.1:7801';\nconst DEFAULT_PORT = 10_000;\nconst DEFAULT_PROXY_TIMEOUT_MS = 30_000;\nconst DEFAULT_HEALTH_TIMEOUT_MS = 2_000;\nconst DEFAULT_EXPORT_BODY_BYTES = 4 * 1024 * 1024;\nconst DEFAULT_RASTERIZE_BODY_BYTES = 32 * 1024 * 1024;\nconst DEFAULT_RESPONSE_BYTES = 24 * 1024 * 1024;\nconst MAX_CHART_DIMENSION = 4_096;\nconst MAX_CHART_PIXELS = 16_000_000;\nconst MAX_CHART_SCALE = 4;\n\ntype FetchImplementation = typeof fetch;\n\nexport interface RenderServerOptions {\n upstreamUrl?: string;\n proxyTimeoutMs?: number;\n healthTimeoutMs?: number;\n maxExportBodyBytes?: number;\n maxRasterizeBodyBytes?: number;\n maxResponseBytes?: number;\n maxConcurrent?: number;\n exportRateLimit?: number;\n rasterizeRateLimit?: number;\n rateLimitWindowMs?: number;\n trustProxyHeaders?: boolean;\n auth?: ApiKeyAuthOptions;\n sourcePolicy?: OutboundSourcePolicy;\n fetch?: FetchImplementation;\n getRasterizer?: () => PptxRasterizer;\n}\n\nclass UpstreamResponseTooLargeError extends Error {}\n\nfunction positiveInteger(value: string | undefined, fallback: number): number {\n const parsed = Number.parseInt(value ?? '', 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;\n}\n\n/**\n * Only the two explicitly local environments relax defaults. `staging`, `prod`,\n * or a typo must not silently downgrade auth, rate limits, or source policy.\n */\nfunction isHardenedEnv(): boolean {\n const nodeEnv = process.env.NODE_ENV;\n return (\n nodeEnv !== undefined && nodeEnv !== 'development' && nodeEnv !== 'test'\n );\n}\n\nfunction envAuthOptions(): ApiKeyAuthOptions {\n const production = isHardenedEnv();\n const requestedMode = process.env.RENDER_AUTH_MODE;\n const mode =\n requestedMode === 'auto' ||\n requestedMode === 'required' ||\n requestedMode === 'disabled'\n ? requestedMode\n : production\n ? 'required'\n : 'auto';\n return {\n mode,\n apiKey: process.env.RENDER_API_KEY || process.env.API_KEY,\n headerName: process.env.RENDER_API_KEY_HEADER || 'x-api-key',\n };\n}\n\nfunction envSourcePolicy(): OutboundSourcePolicy {\n const requestedMode = process.env.OUTBOUND_SOURCE_MODE;\n const mode =\n requestedMode === 'safe' || requestedMode === 'development'\n ? requestedMode\n : isHardenedEnv()\n ? 'safe'\n : 'development';\n return {\n mode,\n allowedHosts: (process.env.OUTBOUND_HOST_ALLOWLIST || '')\n .split(',')\n .map((host) => host.trim().toLowerCase())\n .filter(Boolean),\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Resolve one export dimension the way Highcharts does: `exporting.sourceWidth`\n * / `sourceHeight` outrank `chart.width` / `chart.height`, which in turn outrank\n * the 600x400 defaults. Checking only `chart.*` would let a nested `exporting`\n * block request an arbitrarily large raster past the pixel budget below.\n */\nfunction resolveDimension(\n exporting: Record<string, unknown> | undefined,\n chart: Record<string, unknown> | undefined,\n sourceKey: 'sourceWidth' | 'sourceHeight',\n chartKey: 'width' | 'height',\n fallback: number\n): number {\n for (const candidate of [exporting?.[sourceKey], chart?.[chartKey]]) {\n if (candidate === undefined) continue;\n if (\n typeof candidate !== 'number' ||\n !Number.isFinite(candidate) ||\n candidate <= 0 ||\n candidate > MAX_CHART_DIMENSION\n ) {\n throw new HTTPException(400, {\n message: 'Requested chart dimensions are too large',\n });\n }\n return candidate;\n }\n return fallback;\n}\n\nfunction resolveScale(value: unknown, fallback: number): number {\n if (value === undefined) return fallback;\n if (\n typeof value !== 'number' ||\n !Number.isFinite(value) ||\n value <= 0 ||\n value > MAX_CHART_SCALE\n ) {\n throw new HTTPException(400, {\n message: `scale must be between 0 and ${MAX_CHART_SCALE}`,\n });\n }\n return value;\n}\n\nfunction validateExportRequest(\n value: unknown\n): asserts value is Record<string, unknown> {\n if (!isRecord(value)) {\n throw new HTTPException(400, { message: 'Export body must be an object' });\n }\n const allowedKeys = new Set(['infile', 'type', 'b64', 'scale', 'resources']);\n const unknown = Object.keys(value).filter((key) => !allowedKeys.has(key));\n if (unknown.length > 0) {\n throw new HTTPException(400, {\n message: `Unsupported export field: ${unknown[0]}`,\n });\n }\n if (!isRecord(value.infile)) {\n throw new HTTPException(400, {\n message: 'Export infile must be a chart options object',\n });\n }\n if (value.type !== 'png' || value.b64 !== true) {\n throw new HTTPException(400, {\n message: 'Only base64 PNG exports are allowed',\n });\n }\n\n const chart = isRecord(value.infile.chart) ? value.infile.chart : undefined;\n const exporting = isRecord(value.infile.exporting)\n ? value.infile.exporting\n : undefined;\n\n // A request-level `scale` outranks `exporting.scale`; validate both so\n // neither path can smuggle an oversized multiplier past the pixel budget.\n const scale = resolveScale(value.scale, resolveScale(exporting?.scale, 1));\n const width = resolveDimension(exporting, chart, 'sourceWidth', 'width', 600);\n const height = resolveDimension(\n exporting,\n chart,\n 'sourceHeight',\n 'height',\n 400\n );\n\n if (width * height * scale * scale > MAX_CHART_PIXELS) {\n throw new HTTPException(400, {\n message: 'Requested chart dimensions are too large',\n });\n }\n}\n\nfunction isTimeout(error: unknown): boolean {\n const name = (error as { name?: string })?.name;\n return name === 'TimeoutError' || name === 'AbortError';\n}\n\nasync function readLimitedBody(\n response: Response,\n maxBytes: number\n): Promise<Uint8Array> {\n const declaredLength = Number(response.headers.get('content-length'));\n if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {\n await response.body?.cancel();\n throw new UpstreamResponseTooLargeError();\n }\n if (!response.body) return new Uint8Array();\n\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;) {\n const { value, done } = await reader.read();\n if (done) break;\n total += value.byteLength;\n if (total > maxBytes) {\n await reader.cancel();\n throw new UpstreamResponseTooLargeError();\n }\n chunks.push(value);\n }\n\n const output = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n output.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return output;\n}\n\nexport function createRenderServerApp(options: RenderServerOptions = {}): Hono {\n const production = isHardenedEnv();\n const upstream = (\n options.upstreamUrl ||\n process.env.HIGHCHARTS_UPSTREAM_URL ||\n DEFAULT_UPSTREAM\n ).replace(/\\/$/, '');\n const proxyTimeoutMs =\n options.proxyTimeoutMs ??\n positiveInteger(process.env.PROXY_TIMEOUT_MS, DEFAULT_PROXY_TIMEOUT_MS);\n const healthTimeoutMs =\n options.healthTimeoutMs ??\n positiveInteger(process.env.HEALTH_TIMEOUT_MS, DEFAULT_HEALTH_TIMEOUT_MS);\n const maxExportBodyBytes =\n options.maxExportBodyBytes ??\n positiveInteger(\n process.env.MAX_EXPORT_BODY_SIZE,\n DEFAULT_EXPORT_BODY_BYTES\n );\n const maxRasterizeBodyBytes =\n options.maxRasterizeBodyBytes ??\n positiveInteger(\n process.env.MAX_RASTERIZE_BODY_SIZE,\n DEFAULT_RASTERIZE_BODY_BYTES\n );\n const maxResponseBytes =\n options.maxResponseBytes ??\n positiveInteger(\n process.env.MAX_RENDER_RESPONSE_SIZE,\n DEFAULT_RESPONSE_BYTES\n );\n const maxConcurrent =\n options.maxConcurrent ??\n positiveInteger(process.env.MAX_CONCURRENT_RENDERS, production ? 4 : 16);\n const windowMs =\n options.rateLimitWindowMs ??\n positiveInteger(process.env.RENDER_RATE_LIMIT_WINDOW_MS, 15 * 60 * 1000);\n const fetchImpl = options.fetch ?? fetch;\n const sourcePolicy = options.sourcePolicy ?? envSourcePolicy();\n const trustProxyHeaders =\n options.trustProxyHeaders ?? process.env.TRUST_PROXY_HEADERS === 'true';\n\n const app = new Hono();\n const auth = createApiKeyAuthMiddleware(options.auth ?? envAuthOptions());\n const capacity = concurrencyLimiter({ limit: maxConcurrent });\n\n app.use('*', secureHeaders());\n\n app.onError((error, c) => {\n if (error instanceof HTTPException) {\n return c.json({ success: false, error: error.message }, error.status);\n }\n return c.json({ success: false, error: 'Internal server error' }, 500);\n });\n\n app.get('/health', async (c) => {\n try {\n const response = await fetchImpl(`${upstream}/health`, {\n signal: AbortSignal.timeout(healthTimeoutMs),\n });\n if (response.ok) return c.text('ok');\n return c.json({ status: 'degraded', upstream: response.status }, 503);\n } catch {\n return c.json({ status: 'degraded', upstream: 'unreachable' }, 503);\n }\n });\n\n // Admission controls precede `auth` deliberately: middleware runs in order,\n // so authenticating first would answer 401 before the limiter counted the\n // attempt, leaving API-key guessing unbounded.\n registerRasterizeRoute(app, {\n getRasterizer: options.getRasterizer,\n preMiddleware: [\n rateLimiter({\n limit:\n options.rasterizeRateLimit ??\n positiveInteger(\n process.env.RASTERIZE_RATE_LIMIT,\n production ? 30 : 1000\n ),\n window: windowMs,\n namespace: 'rasterize',\n trustProxy: trustProxyHeaders,\n }),\n capacity,\n bodyLimit({\n maxSize: maxRasterizeBodyBytes,\n onError: () => {\n throw new HTTPException(413, { message: 'Request body too large' });\n },\n }),\n auth,\n ],\n sourcePolicy,\n });\n\n app.post(\n '/export',\n rateLimiter({\n limit:\n options.exportRateLimit ??\n positiveInteger(process.env.EXPORT_RATE_LIMIT, production ? 60 : 1000),\n window: windowMs,\n namespace: 'export',\n trustProxy: trustProxyHeaders,\n }),\n capacity,\n bodyLimit({\n maxSize: maxExportBodyBytes,\n onError: () => {\n throw new HTTPException(413, { message: 'Request body too large' });\n },\n }),\n auth,\n async (c) => {\n const contentType = c.req.header('content-type');\n if (!contentType?.toLowerCase().includes('application/json')) {\n throw new HTTPException(415, {\n message: 'Content-Type must be application/json',\n });\n }\n\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n throw new HTTPException(400, { message: 'Invalid JSON body' });\n }\n validateExportRequest(body);\n try {\n assertSafeRendererPayload(body, sourcePolicy);\n } catch (error) {\n if (error instanceof UnsafeOutboundSourceError) {\n throw new HTTPException(400, { message: error.message });\n }\n throw error;\n }\n\n let response: Response;\n try {\n response = await fetchImpl(`${upstream}/export`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'text/plain, image/png, application/json',\n },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(proxyTimeoutMs),\n });\n } catch (error) {\n return isTimeout(error)\n ? c.json(\n {\n error: `Highcharts upstream timed out after ${proxyTimeoutMs}ms`,\n },\n 504\n )\n : c.json({ error: 'Highcharts upstream unreachable' }, 502);\n }\n\n let payload: Uint8Array;\n try {\n payload = await readLimitedBody(response, maxResponseBytes);\n } catch (error) {\n if (error instanceof UpstreamResponseTooLargeError) {\n return c.json({ error: 'Highcharts response too large' }, 502);\n }\n if (isTimeout(error)) {\n return c.json(\n {\n error: `Highcharts upstream timed out after ${proxyTimeoutMs}ms`,\n },\n 504\n );\n }\n return c.json({ error: 'Failed to read Highcharts response' }, 502);\n }\n\n const headers: Record<string, string> = {};\n for (const name of [\n 'content-type',\n 'content-disposition',\n 'cache-control',\n ]) {\n const value = response.headers.get(name);\n if (value) headers[name] = value;\n }\n return c.body(payload, response.status as StatusCode, headers);\n }\n );\n\n for (const route of ['/export', '/rasterize']) {\n app.all(route, (c) => {\n c.header('Allow', 'POST');\n return c.json({ success: false, error: 'Method not allowed' }, 405);\n });\n }\n app.notFound((c) => c.json({ success: false, error: 'Not found' }, 404));\n\n return app;\n}\n\nfunction isMainModule(): boolean {\n if (!process.argv[1]) return false;\n try {\n return resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url));\n } catch {\n return false;\n }\n}\n\nif (isMainModule()) {\n const port = positiveInteger(process.env.PORT, DEFAULT_PORT);\n const app = createRenderServerApp();\n void import('@hono/node-server').then(({ serve }) => {\n serve({ fetch: app.fetch, port, hostname: '0.0.0.0' }, (info) => {\n // eslint-disable-next-line no-console\n console.log(`[jto-render-server] listening on :${info.port}`);\n });\n });\n}\n","/**\n * Shared POST /rasterize route — one validated handler mounted on BOTH the\n * playground format router and the standalone jto-render-server, so the public\n * and in-app rasterize surfaces can't drift in validation, limits, or error\n * mapping. Renders a single-slide pptx presentation to a PNG.\n */\n\nimport path from 'node:path';\nimport type { Hono, MiddlewareHandler } from 'hono';\nimport { Type } from '@sinclair/typebox';\nimport { bodyLimit } from 'hono/body-limit';\nimport { HTTPException } from 'hono/http-exception';\nimport {\n clampVisualDpi,\n DEFAULT_VISUAL_DPI,\n MIN_VISUAL_DPI,\n MAX_VISUAL_DPI,\n type PptxRasterizer,\n} from '@json-to-office/shared';\nimport { createLibreOfficePptxRasterizer } from '@json-to-office/jto-cli';\nimport { tbValidator, getValidated } from './lib/typebox-validator.js';\nimport {\n assertSafeOutboundSources,\n type OutboundSourcePolicy,\n UnsafeOutboundSourceError,\n} from './security/outbound-source-policy.js';\n\n/** Body for POST /rasterize: a single-slide pptx presentation + optional dpi. */\nexport const RasterizeRequestSchema = Type.Object(\n {\n presentation: Type.Object({}, { additionalProperties: true }),\n dpi: Type.Optional(\n Type.Number({ minimum: MIN_VISUAL_DPI, maximum: MAX_VISUAL_DPI })\n ),\n baseDir: Type.Optional(Type.String()),\n },\n { additionalProperties: false }\n);\n\n// One rasterizer per process — shares the on-disk content-addressed cache\n// across every /rasterize surface in the process.\nlet sharedRasterizer: PptxRasterizer | undefined;\nexport function getSharedRasterizer(): PptxRasterizer {\n if (!sharedRasterizer) {\n sharedRasterizer = createLibreOfficePptxRasterizer();\n }\n return sharedRasterizer;\n}\n\nconst jsonOnly: MiddlewareHandler = async (c, next) => {\n const contentType = c.req.header('content-type');\n if (!contentType || !contentType.includes('application/json')) {\n throw new HTTPException(400, {\n message: 'Content-Type must be application/json',\n });\n }\n await next();\n};\n\n/**\n * Register `POST /rasterize` on a Hono router with body-size limit, content-type\n * + schema validation (dpi clamped to [MIN,MAX]_VISUAL_DPI), the shared\n * rasterizer, and structured 400/413/503/500 error mapping.\n *\n * @param preMiddleware - extra middleware (e.g. a rate limiter) run first.\n */\nexport function registerRasterizeRoute(\n router: Hono<any>,\n options: {\n getRasterizer?: () => PptxRasterizer;\n preMiddleware?: MiddlewareHandler[];\n sourcePolicy?: OutboundSourcePolicy;\n onError?: (error: unknown) => void;\n } = {}\n): void {\n const getRasterizer = options.getRasterizer ?? getSharedRasterizer;\n\n router.post(\n '/rasterize',\n ...(options.preMiddleware ?? []),\n bodyLimit({\n maxSize: 32 * 1024 * 1024,\n onError: () => {\n throw new HTTPException(413, { message: 'Request body too large' });\n },\n }),\n jsonOnly,\n tbValidator(RasterizeRequestSchema),\n async (c) => {\n const { presentation, dpi, baseDir } = getValidated<{\n presentation: unknown;\n dpi?: number;\n baseDir?: string;\n }>(c, 'json');\n\n // `baseDir` selects the directory local media paths resolve against.\n // An unrestricted value would let any HTTP caller point the rasterizer\n // at arbitrary server directories and exfiltrate readable files as\n // rendered pixels, so it must stay inside the server's own working\n // tree. Callers rendering documents that live elsewhere should run the\n // server from that tree (or use an in-process rasterizer).\n let safeBaseDir: string | undefined;\n if (baseDir !== undefined) {\n const resolved = path.resolve(baseDir);\n const cwd = process.cwd();\n if (resolved !== cwd && !resolved.startsWith(cwd + path.sep)) {\n throw new HTTPException(400, {\n message: 'baseDir must be inside the server working directory',\n });\n }\n safeBaseDir = resolved;\n }\n\n try {\n if (options.sourcePolicy) {\n assertSafeOutboundSources(\n presentation,\n options.sourcePolicy,\n 'presentation'\n );\n }\n const result = await getRasterizer()({\n presentation,\n dpi: clampVisualDpi(dpi ?? DEFAULT_VISUAL_DPI),\n baseDir: safeBaseDir,\n });\n return c.json(result);\n } catch (error) {\n options.onError?.(error);\n if (error instanceof HTTPException) throw error;\n if (error instanceof UnsafeOutboundSourceError) {\n throw new HTTPException(400, { message: error.message });\n }\n const msg =\n error instanceof Error ? error.message.toLowerCase() : String(error);\n if (msg.includes('not found') || msg.includes('rasterization needs')) {\n throw new HTTPException(503, { message: (error as Error).message });\n }\n if (msg.includes('invalid') || msg.includes('validation')) {\n throw new HTTPException(400, { message: (error as Error).message });\n }\n throw new HTTPException(500, {\n message: 'Internal server error during rasterization',\n });\n }\n }\n );\n}\n","import { TSchema } from '@sinclair/typebox';\nimport { Value } from '@sinclair/typebox/value';\nimport { Context, Env, ValidationTargets, MiddlewareHandler } from 'hono';\nimport { HTTPException } from 'hono/http-exception';\n\n/**\n * Auto-detect raw document JSON (has `name` + `children` but no `jsonDefinition`)\n * and wrap it so callers can POST the document tree directly.\n */\nfunction autoWrapDocumentBody(value: unknown): unknown {\n if (\n typeof value === 'object' &&\n value !== null &&\n 'name' in value &&\n 'children' in value &&\n !('jsonDefinition' in value)\n ) {\n return { jsonDefinition: value };\n }\n return value;\n}\n\nexport function tbValidator<\n T extends TSchema,\n E extends Env = Env,\n P extends string = string,\n>(schema: T) {\n return (async (c: Context<E, P>, next: () => Promise<void>) => {\n let value: unknown;\n\n try {\n value = await c.req.json();\n } catch {\n throw new HTTPException(400, {\n message: 'Invalid JSON in request body',\n });\n }\n\n value = autoWrapDocumentBody(value);\n\n const isValid = Value.Check(schema, value);\n\n if (isValid) {\n (c.req as { validatedData?: Record<string, unknown> }).validatedData = {\n json: value,\n };\n await next();\n } else {\n const errors = [...Value.Errors(schema, value)].map((error) => ({\n path: error.path,\n message: error.message,\n value: error.value,\n }));\n\n const errorMessages = errors.map((e) => `${e.path || '/'}: ${e.message}`);\n\n throw new HTTPException(400, {\n message:\n errorMessages.length === 1\n ? errorMessages[0]\n : `Validation failed:\\n${errorMessages.join('\\n')}`,\n cause: { errors },\n });\n }\n }) as MiddlewareHandler<E, P, Record<string, unknown>>;\n}\n\nexport function getValidated<T>(\n c: Context,\n target: keyof ValidationTargets\n): T {\n const validatedData = (c.req as { validatedData?: Record<string, unknown> })\n .validatedData;\n if (!validatedData || !validatedData[target]) {\n throw new Error(`No validated data found for target: ${target}`);\n }\n return validatedData[target] as T;\n}\n","import type { OutboundSourceMode } from '../config/index.js';\n\nexport interface OutboundSourcePolicy {\n mode: OutboundSourceMode;\n allowedHosts: readonly string[];\n}\n\nexport class UnsafeOutboundSourceError extends Error {\n constructor(\n readonly path: string,\n reason: string\n ) {\n super(`Unsafe outbound source at ${path}: ${reason}`);\n this.name = 'UnsafeOutboundSourceError';\n }\n}\n\ntype JsonRecord = Record<string, unknown>;\n\nconst DANGEROUS_KEYS = new Set(['__proto__', 'prototype', 'constructor']);\nconst COMPONENT_SOURCE_NAMES = new Set(['image']);\n\nfunction isRecord(value: unknown): value is JsonRecord {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction hostMatches(host: string, pattern: string): boolean {\n const normalized = pattern.toLowerCase().replace(/\\.$/, '');\n if (normalized.startsWith('*.')) {\n const suffix = normalized.slice(1);\n return host.endsWith(suffix) && host.length > suffix.length;\n }\n return host === normalized;\n}\n\nfunction isPrivateIpv4(host: string): boolean {\n const parts = host.split('.');\n if (parts.length !== 4) return false;\n const octets = parts.map(Number);\n if (\n octets.some(\n (part, index) =>\n !Number.isInteger(part) ||\n part < 0 ||\n part > 255 ||\n String(part) !== parts[index]\n )\n ) {\n return false;\n }\n const [a, b] = octets;\n return (\n a === 0 ||\n a === 10 ||\n a === 127 ||\n (a === 100 && b >= 64 && b <= 127) ||\n (a === 169 && b === 254) ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 192 && (b === 0 || b === 168)) ||\n (a === 198 && (b === 18 || b === 19)) ||\n a >= 224\n );\n}\n\nfunction isPrivateHost(host: string): boolean {\n const normalized = host\n .toLowerCase()\n .replace(/^\\[|\\]$/g, '')\n .replace(/\\.$/, '');\n if (\n normalized === 'localhost' ||\n normalized.endsWith('.localhost') ||\n normalized === '::' ||\n normalized === '::1'\n ) {\n return true;\n }\n if (isPrivateIpv4(normalized)) return true;\n if (normalized.startsWith('::ffff:')) {\n const mapped = normalized.slice('::ffff:'.length);\n if (isPrivateIpv4(mapped)) return true;\n const groups = mapped.match(/^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);\n if (groups) {\n const value =\n Number.parseInt(groups[1], 16) * 0x1_0000 +\n Number.parseInt(groups[2], 16);\n return isPrivateIpv4(\n [\n value >>> 24,\n (value >>> 16) & 255,\n (value >>> 8) & 255,\n value & 255,\n ].join('.')\n );\n }\n }\n const firstIpv6Group = Number.parseInt(normalized.split(':')[0] || '0', 16);\n return (\n (firstIpv6Group >= 0xfc00 && firstIpv6Group <= 0xfdff) ||\n (firstIpv6Group >= 0xfe80 && firstIpv6Group <= 0xfebf)\n );\n}\n\nfunction assertAllowedUrl(\n source: string,\n path: string,\n allowedHosts: readonly string[]\n): void {\n if (source.startsWith('data:')) return;\n\n let url: URL;\n try {\n url = new URL(source);\n } catch {\n throw new UnsafeOutboundSourceError(\n path,\n 'local and relative file paths are disabled for HTTP requests'\n );\n }\n\n if (url.protocol !== 'https:') {\n throw new UnsafeOutboundSourceError(path, 'only HTTPS URLs are allowed');\n }\n if (url.username || url.password) {\n throw new UnsafeOutboundSourceError(\n path,\n 'URLs containing credentials are not allowed'\n );\n }\n\n const host = url.hostname.toLowerCase().replace(/\\.$/, '');\n if (isPrivateHost(host)) {\n throw new UnsafeOutboundSourceError(\n path,\n `private or local host \"${host}\" is not allowed`\n );\n }\n if (!allowedHosts.some((pattern) => hostMatches(host, pattern))) {\n throw new UnsafeOutboundSourceError(\n path,\n `host \"${host}\" is not in OUTBOUND_HOST_ALLOWLIST`\n );\n }\n}\n\nfunction extractCssReferences(input: string): string[] {\n const references: string[] = [];\n const urlPattern = /url\\(\\s*(['\"]?)([^)'\"\\s]+)\\1\\s*\\)/gi;\n const importPattern = /@import\\s+(?:url\\(\\s*)?(['\"])([^'\"]+)\\1/gi;\n for (const pattern of [urlPattern, importPattern]) {\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(input))) references.push(match[2]);\n }\n return references;\n}\n\nfunction extractAbsoluteUrls(input: string): string[] {\n return input.match(/(?:https?|file):\\/\\/[^\\s'\"<>)}\\]]+/gi) ?? [];\n}\n\nfunction assertSafeSvg(\n svg: string,\n path: string,\n allowedHosts: readonly string[]\n): void {\n if (\n /<\\s*script\\b/i.test(svg) ||\n /<\\s*(?:foreignObject|iframe|object|embed)\\b/i.test(svg) ||\n /\\bon[a-z]+\\s*=/i.test(svg)\n ) {\n throw new UnsafeOutboundSourceError(path, 'active SVG content is disabled');\n }\n if (/<!\\s*(?:DOCTYPE|ENTITY)\\b/i.test(svg)) {\n throw new UnsafeOutboundSourceError(\n path,\n 'SVG document types and entities are disabled'\n );\n }\n\n const refPattern = /\\b(?:href|src)\\s*=\\s*(['\"])(.*?)\\1/gi;\n let match: RegExpExecArray | null;\n while ((match = refPattern.exec(svg))) {\n const ref = match[2].trim();\n if (!ref || ref.startsWith('#') || ref.startsWith('data:')) continue;\n assertAllowedUrl(ref, path, allowedHosts);\n }\n for (const ref of extractCssReferences(svg)) {\n if (ref.startsWith('#') || ref.startsWith('data:')) continue;\n assertAllowedUrl(ref, path, allowedHosts);\n }\n}\n\nfunction assertSafeEncodedImage(\n source: string,\n path: string,\n allowedHosts: readonly string[]\n): void {\n let candidate = source.trim();\n let declaredSvg = false;\n\n if (candidate.startsWith('data:')) {\n const comma = candidate.indexOf(',');\n if (comma < 0) {\n throw new UnsafeOutboundSourceError(path, 'malformed image data URL');\n }\n const metadata = candidate.slice(5, comma);\n declaredSvg = metadata.split(';')[0].toLowerCase() === 'image/svg+xml';\n if (!declaredSvg) return;\n const payload = candidate.slice(comma + 1);\n try {\n candidate = /(?:^|;)base64(?:;|$)/i.test(metadata)\n ? Buffer.from(payload, 'base64').toString('utf8')\n : decodeURIComponent(payload);\n } catch {\n throw new UnsafeOutboundSourceError(path, 'malformed SVG data URL');\n }\n } else if (!/<\\s*svg\\b/i.test(candidate)) {\n // The documented `base64` shape is a data URL, but inspect raw base64 too\n // so a migration client cannot smuggle active SVG through the same field.\n candidate = Buffer.from(candidate, 'base64').toString('utf8');\n }\n\n const svgStart = candidate.search(/<\\s*svg\\b/i);\n if (svgStart < 0) {\n if (declaredSvg) {\n throw new UnsafeOutboundSourceError(path, 'malformed SVG image data');\n }\n return;\n }\n assertSafeSvg(candidate, path, allowedHosts);\n}\n\nfunction assertSafeResources(\n resources: JsonRecord,\n path: string,\n allowedHosts: readonly string[]\n): void {\n if (typeof resources.js === 'string' && resources.js.trim()) {\n throw new UnsafeOutboundSourceError(\n `${path}.js`,\n 'remote renderer JavaScript resources are disabled'\n );\n }\n if (typeof resources.css === 'string') {\n for (const ref of extractCssReferences(resources.css)) {\n if (ref.startsWith('data:')) continue;\n assertAllowedUrl(ref, `${path}.css`, allowedHosts);\n }\n }\n // Highcharts loads `resources.files` as JavaScript. Allowlisting a host for\n // fonts or stylesheets must not also grant it script execution inside the\n // renderer, so reject the field outright rather than URL-checking it.\n if (Array.isArray(resources.files) && resources.files.length > 0) {\n throw new UnsafeOutboundSourceError(\n `${path}.files`,\n 'remote renderer JavaScript resources are disabled'\n );\n }\n}\n\nfunction assertNoRemoteReferences(\n value: unknown,\n path: string,\n allowedHosts: readonly string[],\n seen: WeakSet<object>\n): void {\n if (typeof value === 'string') {\n if (\n /\\b(?:fetch|XMLHttpRequest|WebSocket|EventSource)\\s*\\(/i.test(value) ||\n /\\b(?:javascript|file):/i.test(value)\n ) {\n throw new UnsafeOutboundSourceError(\n path,\n 'network-capable JavaScript and file URLs are disabled'\n );\n }\n for (const url of [\n ...extractAbsoluteUrls(value),\n ...extractCssReferences(value),\n ]) {\n if (url.startsWith('data:')) continue;\n assertAllowedUrl(url, path, allowedHosts);\n }\n if (/^\\/\\//.test(value.trim())) {\n throw new UnsafeOutboundSourceError(\n path,\n 'protocol-relative URLs are disabled'\n );\n }\n return;\n }\n if (!value || typeof value !== 'object' || seen.has(value)) return;\n seen.add(value);\n if (Array.isArray(value)) {\n value.forEach((entry, index) =>\n assertNoRemoteReferences(entry, `${path}[${index}]`, allowedHosts, seen)\n );\n return;\n }\n for (const [key, child] of Object.entries(value as JsonRecord)) {\n assertNoRemoteReferences(child, `${path}.${key}`, allowedHosts, seen);\n }\n}\n\nfunction visit(\n value: unknown,\n path: string,\n containerKey: string | undefined,\n allowedHosts: readonly string[],\n seen: WeakSet<object>\n): void {\n if (!value || typeof value !== 'object' || seen.has(value)) return;\n seen.add(value);\n\n if (Array.isArray(value)) {\n value.forEach((entry, index) =>\n visit(entry, `${path}[${index}]`, containerKey, allowedHosts, seen)\n );\n return;\n }\n\n const record = value as JsonRecord;\n for (const key of Object.keys(record)) {\n if (DANGEROUS_KEYS.has(key)) {\n throw new UnsafeOutboundSourceError(\n `${path}.${key}`,\n 'prototype mutation keys are disabled'\n );\n }\n }\n\n const kind = typeof record.kind === 'string' ? record.kind : undefined;\n if (kind === 'file' && typeof record.path === 'string') {\n throw new UnsafeOutboundSourceError(\n `${path}.path`,\n 'local file sources are disabled for HTTP requests'\n );\n }\n if (\n (kind === 'url' || kind === 'variable') &&\n typeof record.url === 'string'\n ) {\n assertAllowedUrl(record.url, `${path}.url`, allowedHosts);\n }\n\n const componentName =\n typeof record.name === 'string' ? record.name.toLowerCase() : undefined;\n const props = isRecord(record.props) ? record.props : undefined;\n if (componentName && props) {\n if (\n COMPONENT_SOURCE_NAMES.has(componentName) &&\n typeof props.path === 'string'\n ) {\n assertAllowedUrl(props.path, `${path}.props.path`, allowedHosts);\n assertSafeEncodedImage(props.path, `${path}.props.path`, allowedHosts);\n }\n if (\n COMPONENT_SOURCE_NAMES.has(componentName) &&\n typeof props.base64 === 'string'\n ) {\n assertSafeEncodedImage(\n props.base64,\n `${path}.props.base64`,\n allowedHosts\n );\n }\n if (\n (componentName === 'highcharts' || componentName === 'visual') &&\n typeof props.serverUrl === 'string'\n ) {\n assertAllowedUrl(\n props.serverUrl,\n `${path}.props.serverUrl`,\n allowedHosts\n );\n }\n if (componentName === 'highcharts') {\n if (isRecord(props.resources)) {\n assertSafeResources(\n props.resources,\n `${path}.props.resources`,\n allowedHosts\n );\n }\n assertNoRemoteReferences(\n props.options,\n `${path}.props.options`,\n allowedHosts,\n new WeakSet()\n );\n }\n }\n\n // Slide/template/visual canvas backgrounds store an image source in a plain\n // `{ image: { path } }` object rather than an image component.\n if (containerKey === 'image' && typeof record.path === 'string') {\n assertAllowedUrl(record.path, `${path}.path`, allowedHosts);\n assertSafeEncodedImage(record.path, `${path}.path`, allowedHosts);\n }\n if (typeof record.svg === 'string') {\n assertSafeSvg(record.svg, `${path}.svg`, allowedHosts);\n }\n if (containerKey === 'resources') {\n assertSafeResources(record, path, allowedHosts);\n }\n\n for (const [key, child] of Object.entries(record)) {\n visit(child, `${path}.${key}`, key, allowedHosts, seen);\n }\n}\n\n/**\n * Reject request-controlled sources capable of making the server read local\n * files or reach arbitrary network hosts. Development mode intentionally\n * preserves local playground workflows.\n */\nexport function assertSafeOutboundSources(\n value: unknown,\n policy: OutboundSourcePolicy,\n rootPath = 'request'\n): void {\n if (policy.mode === 'development') return;\n\n let parsed = value;\n if (typeof value === 'string') {\n try {\n parsed = JSON.parse(value);\n } catch {\n // Structural validation owns malformed JSON reporting.\n return;\n }\n }\n visit(parsed, rootPath, undefined, policy.allowedHosts, new WeakSet());\n}\n\n/** Extra checks for payloads executed by the Chromium-based chart renderer. */\nexport function assertSafeRendererPayload(\n value: unknown,\n policy: OutboundSourcePolicy\n): void {\n assertSafeOutboundSources(value, policy, 'export');\n if (policy.mode === 'development' || !isRecord(value)) return;\n assertNoRemoteReferences(\n value.infile,\n 'export.infile',\n policy.allowedHosts,\n new WeakSet()\n );\n}\n","import { MiddlewareHandler } from 'hono';\nimport { HTTPException } from 'hono/http-exception';\n\nexport interface RateLimitOptions {\n limit: number;\n window: number; // milliseconds\n keyGenerator?: (c: any) => string;\n namespace?: string | ((c: any) => string);\n maxEntries?: number;\n trustProxy?: boolean;\n}\n\nfunction clientAddress(c: any, trustProxy: boolean): string {\n if (trustProxy) {\n const forwarded =\n c.req.header('X-Real-IP') ||\n c.req.header('X-Forwarded-For')?.split(',')[0]?.trim();\n if (forwarded) return forwarded;\n }\n const incoming = c.env?.incoming || c.env?.req;\n return incoming?.socket?.remoteAddress || 'anonymous';\n}\n\n/**\n * Bounded, process-local rate limiting. Distributed deployments should put a\n * shared limiter at the edge too; this remains the last line of defence for a\n * single worker and never grows without bound.\n */\nexport const rateLimiter = (options: RateLimitOptions): MiddlewareHandler => {\n const { limit, window, keyGenerator } = options;\n const maxEntries = options.maxEntries ?? 10_000;\n const rateLimitStore = new Map<\n string,\n { count: number; resetTime: number }\n >();\n let lastCleanup = 0;\n\n return async (c, next) => {\n const clientKey = keyGenerator\n ? keyGenerator(c)\n : clientAddress(c, options.trustProxy === true);\n const namespace =\n typeof options.namespace === 'function'\n ? options.namespace(c)\n : options.namespace || `${c.req.method}:${c.req.path}`;\n const key = `${namespace}:${String(clientKey).slice(0, 256)}`;\n const now = Date.now();\n\n // Amortize cleanup; scanning an attacker-filled map on every request would\n // turn the protection itself into a CPU denial of service.\n if (now - lastCleanup >= Math.min(window, 60_000)) {\n for (const [storedKey, value] of rateLimitStore.entries()) {\n if (value.resetTime <= now) rateLimitStore.delete(storedKey);\n }\n lastCleanup = now;\n }\n\n const record = rateLimitStore.get(key);\n\n if (!record) {\n if (rateLimitStore.size >= maxEntries) {\n const oldestKey = rateLimitStore.keys().next().value as\n | string\n | undefined;\n if (oldestKey) rateLimitStore.delete(oldestKey);\n }\n rateLimitStore.set(key, {\n count: 1,\n resetTime: now + window,\n });\n } else if (record.resetTime <= now) {\n // Window expired, reset\n record.count = 1;\n record.resetTime = now + window;\n } else if (record.count >= limit) {\n // Rate limit exceeded\n const retryAfter = Math.ceil((record.resetTime - now) / 1000);\n\n c.header('X-RateLimit-Limit', String(limit));\n c.header('X-RateLimit-Remaining', '0');\n c.header('X-RateLimit-Reset', String(record.resetTime));\n c.header('Retry-After', String(retryAfter));\n\n throw new HTTPException(429, {\n message: 'Too many requests, please try again later',\n });\n } else {\n // Increment count\n record.count++;\n }\n\n // Add rate limit headers\n const currentRecord = rateLimitStore.get(key)!;\n c.header('X-RateLimit-Limit', String(limit));\n c.header(\n 'X-RateLimit-Remaining',\n String(Math.max(0, limit - currentRecord.count))\n );\n c.header('X-RateLimit-Reset', String(currentRecord.resetTime));\n\n await next();\n };\n};\n","import type { MiddlewareHandler } from 'hono';\n\nexport interface ConcurrencyLimitOptions {\n limit: number;\n retryAfterSeconds?: number;\n}\n\n/**\n * Process-local admission control for expensive generation/rendering work.\n * It rejects excess work instead of queueing unbounded request bodies in RAM.\n */\nexport function concurrencyLimiter(\n options: ConcurrencyLimitOptions\n): MiddlewareHandler {\n const limit = Math.max(1, Math.floor(options.limit));\n const retryAfterSeconds = options.retryAfterSeconds ?? 1;\n let active = 0;\n\n return async (c, next) => {\n c.header('X-Concurrency-Limit', String(limit));\n if (active >= limit) {\n c.header('Retry-After', String(retryAfterSeconds));\n return c.json(\n {\n success: false,\n error: 'Server is at capacity',\n code: 'CONCURRENCY_LIMIT_EXCEEDED',\n },\n 503\n );\n }\n\n active += 1;\n c.header('X-Concurrency-Remaining', String(Math.max(0, limit - active)));\n try {\n await next();\n } finally {\n active -= 1;\n }\n };\n}\n","import { createHash, timingSafeEqual } from 'node:crypto';\nimport { MiddlewareHandler } from 'hono';\nimport { config } from '../../config';\nimport type { ApiAuthMode } from '../../config';\n\nexport interface ApiKeyAuthOptions {\n mode: ApiAuthMode;\n apiKey?: string;\n headerName?: string;\n}\n\nfunction keysEqual(received: string, expected: string): boolean {\n const receivedDigest = createHash('sha256').update(received).digest();\n const expectedDigest = createHash('sha256').update(expected).digest();\n return timingSafeEqual(receivedDigest, expectedDigest);\n}\n\nfunction readCredential(\n headers: Headers,\n headerName: string\n): string | undefined {\n const direct = headers.get(headerName)?.trim();\n if (direct) {\n if (\n headerName.toLowerCase() === 'authorization' &&\n direct.toLowerCase().startsWith('bearer ')\n ) {\n return direct.slice(7).trim() || undefined;\n }\n return direct;\n }\n\n const authorization = headers.get('authorization')?.trim();\n if (authorization?.toLowerCase().startsWith('bearer ')) {\n return authorization.slice(7).trim() || undefined;\n }\n return undefined;\n}\n\n/**\n * API Key authentication middleware for Hono\n */\nexport function createApiKeyAuthMiddleware(\n options: ApiKeyAuthOptions\n): MiddlewareHandler {\n const headerName = options.headerName || 'x-api-key';\n\n return async (c, next) => {\n if (c.req.method === 'OPTIONS' || options.mode === 'disabled') {\n return next();\n }\n\n if (!options.apiKey) {\n if (options.mode === 'auto') return next();\n return c.json(\n {\n success: false,\n error: 'API authentication is not configured',\n code: 'AUTH_CONFIGURATION_ERROR',\n },\n 503\n );\n }\n\n const apiKey = readCredential(c.req.raw.headers, headerName);\n\n if (!apiKey) {\n return c.json(\n {\n success: false,\n error: 'API key required',\n code: 'UNAUTHORIZED',\n },\n 401\n );\n }\n\n if (!keysEqual(apiKey, options.apiKey)) {\n return c.json(\n {\n success: false,\n error: 'Invalid API key',\n code: 'UNAUTHORIZED',\n },\n 401\n );\n }\n\n await next();\n };\n}\n\nexport const apiKeyAuthMiddleware = createApiKeyAuthMiddleware({\n mode: config.API_AUTH_MODE,\n apiKey: config.API_KEY,\n headerName: config.API_KEY_HEADER,\n});\n","import dotenv from 'dotenv';\n\ndotenv.config();\n\nexport type ApiAuthMode = 'auto' | 'required' | 'disabled';\nexport type OutboundSourceMode = 'development' | 'safe';\n\nfunction positiveInteger(value: string | undefined, fallback: number): number {\n const parsed = Number.parseInt(value ?? '', 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;\n}\n\nfunction parseAuthMode(\n value: string | undefined,\n nodeEnv: 'development' | 'production' | 'test'\n): ApiAuthMode {\n if (value === 'auto' || value === 'required' || value === 'disabled') {\n return value;\n }\n // Public production APIs must opt out explicitly. Local playgrounds remain\n // zero-config; supplying a key there preserves the historical auto-enable.\n return nodeEnv === 'production' ? 'required' : 'auto';\n}\n\nfunction parseOutboundSourceMode(\n value: string | undefined,\n nodeEnv: 'development' | 'production' | 'test'\n): OutboundSourceMode {\n if (value === 'development' || value === 'safe') return value;\n return nodeEnv === 'production' ? 'safe' : 'development';\n}\n\n/**\n * Only the two explicitly local environments keep permissive defaults. Every\n * other value — `staging`, `prod`, a typo — gets production-grade hardening, so\n * a mislabelled deployment cannot silently disable auth, rate limits, or the\n * outbound-source (SSRF) checks.\n */\nfunction normalizeNodeEnv(\n value: string | undefined\n): 'development' | 'production' | 'test' {\n return value === 'development' || value === 'test' ? value : 'production';\n}\n\nfunction parseEnv(env: NodeJS.ProcessEnv) {\n const nodeEnv = normalizeNodeEnv(env.NODE_ENV || 'development');\n return {\n NODE_ENV: nodeEnv,\n // No PORT here: the listener is opened by the dev server from the CLI\n // config (`-p` > `server.port` > `PORT` env > format default), so a second\n // copy of it in this module would be silently ignored.\n CORS_ORIGIN: env.CORS_ORIGIN || '*',\n API_KEY: env.API_KEY,\n API_KEY_HEADER: env.API_KEY_HEADER || 'x-api-key',\n API_AUTH_MODE: parseAuthMode(env.API_AUTH_MODE, nodeEnv),\n RATE_LIMIT_WINDOW_MS: positiveInteger(env.RATE_LIMIT_WINDOW_MS, 900_000),\n RATE_LIMIT_MAX: positiveInteger(\n env.RATE_LIMIT_MAX,\n nodeEnv === 'production' ? 100 : 1000\n ),\n TRUST_PROXY_HEADERS: env.TRUST_PROXY_HEADERS === 'true',\n MAX_FILE_SIZE: positiveInteger(env.MAX_FILE_SIZE, 10 * 1024 * 1024),\n MAX_REQUEST_BODY_SIZE: positiveInteger(\n env.MAX_REQUEST_BODY_SIZE,\n 32 * 1024 * 1024\n ),\n MAX_CONCURRENT_REQUESTS: positiveInteger(\n env.MAX_CONCURRENT_REQUESTS,\n nodeEnv === 'production' ? 8 : 64\n ),\n OUTBOUND_SOURCE_MODE: parseOutboundSourceMode(\n env.OUTBOUND_SOURCE_MODE,\n nodeEnv\n ),\n OUTBOUND_HOST_ALLOWLIST: (env.OUTBOUND_HOST_ALLOWLIST || '')\n .split(',')\n .map((host) => host.trim().toLowerCase())\n .filter(Boolean),\n LIBREOFFICE_PATH: env.LIBREOFFICE_PATH,\n LIBREOFFICE_TIMEOUT_MS: env.LIBREOFFICE_TIMEOUT_MS\n ? positiveInteger(env.LIBREOFFICE_TIMEOUT_MS, 30000)\n : 30000,\n LOG_LEVEL: (env.LOG_LEVEL || 'info') as 'error' | 'warn' | 'info' | 'debug',\n CACHE_ENABLED: env.CACHE_ENABLED !== 'false',\n CACHE_MAX_SIZE_MB: positiveInteger(env.CACHE_MAX_SIZE_MB, 100),\n CACHE_TTL_SECONDS: positiveInteger(env.CACHE_TTL_SECONDS, 3600),\n CACHE_MAX_ITEMS: positiveInteger(env.CACHE_MAX_ITEMS, 1000),\n };\n}\n\nconst parsedEnv = parseEnv(process.env);\n\nexport const config = {\n ...parsedEnv,\n\n isDevelopment: parsedEnv.NODE_ENV === 'development',\n isProduction: parsedEnv.NODE_ENV === 'production',\n isTest: parsedEnv.NODE_ENV === 'test',\n\n features: {\n apiKey: parsedEnv.API_AUTH_MODE !== 'disabled',\n cache: parsedEnv.CACHE_ENABLED,\n },\n\n cors: {\n origin:\n parsedEnv.CORS_ORIGIN === '*'\n ? parsedEnv.CORS_ORIGIN\n : parsedEnv.CORS_ORIGIN.split(',').map((o) => o.trim()),\n credentials: true,\n },\n\n rateLimit: {\n windowMs: parsedEnv.RATE_LIMIT_WINDOW_MS,\n max: parsedEnv.RATE_LIMIT_MAX,\n trustProxy: parsedEnv.TRUST_PROXY_HEADERS,\n },\n\n requestLimits: {\n maxBodySize: parsedEnv.MAX_REQUEST_BODY_SIZE,\n maxConcurrent: parsedEnv.MAX_CONCURRENT_REQUESTS,\n maxFileSize: parsedEnv.MAX_FILE_SIZE,\n },\n\n outboundSources: {\n mode: parsedEnv.OUTBOUND_SOURCE_MODE,\n allowedHosts: parsedEnv.OUTBOUND_HOST_ALLOWLIST,\n },\n\n cache: {\n enabled: parsedEnv.CACHE_ENABLED,\n maxSizeMB: parsedEnv.CACHE_MAX_SIZE_MB,\n ttlSeconds: parsedEnv.CACHE_TTL_SECONDS,\n maxItems: parsedEnv.CACHE_MAX_ITEMS,\n },\n} as const;\n\nexport type ServerConfig = typeof config;\n"],"mappings":"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","names":["bodyLimit","HTTPException","HTTPException","path","HTTPException","HTTPException","positiveInteger","isRecord","HTTPException","bodyLimit"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@json-to-office/jto",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "JSON to Office CLI - Unified document and presentation generation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
19
|
"dist",
|
|
20
|
-
"templates",
|
|
21
20
|
"LICENSE"
|
|
22
21
|
],
|
|
23
22
|
"dependencies": {
|
|
@@ -85,12 +84,12 @@
|
|
|
85
84
|
"vite": "6.0.5",
|
|
86
85
|
"zod": "^4.0.0",
|
|
87
86
|
"zustand": "5.0.2",
|
|
88
|
-
"@json-to-office/core-
|
|
89
|
-
"@json-to-office/core-
|
|
90
|
-
"@json-to-office/jto-cli": "^0.
|
|
91
|
-
"@json-to-office/shared": "^0.
|
|
92
|
-
"@json-to-office/shared
|
|
93
|
-
"@json-to-office/shared-pptx": "^0.
|
|
87
|
+
"@json-to-office/core-pptx": "^0.28.0",
|
|
88
|
+
"@json-to-office/core-docx": "^0.28.0",
|
|
89
|
+
"@json-to-office/jto-cli": "^0.28.0",
|
|
90
|
+
"@json-to-office/shared-docx": "^0.28.0",
|
|
91
|
+
"@json-to-office/shared": "^0.28.0",
|
|
92
|
+
"@json-to-office/shared-pptx": "^0.28.0"
|
|
94
93
|
},
|
|
95
94
|
"devDependencies": {
|
|
96
95
|
"@types/lodash.debounce": "4.0.9",
|