@chatarmin/os 1.7.0 → 1.8.1

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.
@@ -7,6 +7,23 @@ export { generateTestWebhookHeaders } from './generate-test-headers.js';
7
7
  export { buildWebhookSignature, buildWebhookSignatureMaterial, verifyWebhookSignature, } from './signing.js';
8
8
  /**
9
9
  * Verify inbound OS product webhooks.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { webhooks } from '@chatarmin/os/webhooks'
14
+ *
15
+ * export async function POST(request: Request) {
16
+ * const rawBody = await request.text()
17
+ * const event = webhooks.constructEvent(
18
+ * rawBody,
19
+ * request.headers,
20
+ * process.env.OS_WEBHOOK_SIGNING_SECRET!,
21
+ * )
22
+ *
23
+ * console.log(event.type, event.action, event.webhookId)
24
+ * return Response.json({ received: true })
25
+ * }
26
+ * ```
10
27
  */
11
28
  export const webhooks = {
12
29
  constructEvent: constructProductWebhookEvent,
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/webhooks/constants.ts","../../src/webhooks/errors.ts","../../src/webhooks/read-header.ts","../../src/webhooks/signing.ts","../../src/webhooks/construct-event.ts","../../src/webhooks/generate-test-headers.ts","../../src/webhooks/index.ts"],"sourcesContent":["export const WEBHOOK_SIGNATURE_HEADER = 'X-OS-Webhook-Signature'\nexport const WEBHOOK_TIMESTAMP_HEADER = 'X-OS-Webhook-Timestamp'\nexport const WEBHOOK_ID_HEADER = 'X-OS-Webhook-Id'\n\nexport const DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300\n","export class OsWebhookSignatureVerificationError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'OsWebhookSignatureVerificationError'\n }\n}\n","import type { ProductWebhookHeaders } from './types.js'\n\nexport function readWebhookHeader(\n headers: ProductWebhookHeaders,\n name: string,\n): string | null {\n if (headers instanceof Headers) {\n return headers.get(name)\n }\n\n const lowerName = name.toLowerCase()\n\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() !== lowerName || value == null) {\n continue\n }\n\n if (Array.isArray(value)) {\n return value[0] ?? null\n }\n\n return value\n }\n\n return null\n}\n","import { createHmac, timingSafeEqual } from 'node:crypto'\n\nexport function buildWebhookSignatureMaterial(\n timestamp: string,\n rawBody: string,\n): string {\n return `${timestamp}.${rawBody}`\n}\n\nexport function buildWebhookSignature(\n secret: string,\n timestamp: string,\n rawBody: string,\n): string {\n return createHmac('sha256', secret)\n .update(buildWebhookSignatureMaterial(timestamp, rawBody))\n .digest('hex')\n}\n\nexport function verifyWebhookSignature(\n secret: string,\n timestamp: string,\n rawBody: string,\n signature: string,\n): boolean {\n const expected = buildWebhookSignature(secret, timestamp, rawBody)\n\n if (expected.length !== signature.length) {\n return false\n }\n\n return timingSafeEqual(\n Buffer.from(expected, 'utf8'),\n Buffer.from(signature, 'utf8'),\n )\n}\n","import {\n DEFAULT_WEBHOOK_TOLERANCE_SECONDS,\n WEBHOOK_ID_HEADER,\n WEBHOOK_SIGNATURE_HEADER,\n WEBHOOK_TIMESTAMP_HEADER,\n} from './constants.js'\nimport { OsWebhookSignatureVerificationError } from './errors.js'\nimport { readWebhookHeader } from './read-header.js'\nimport { verifyWebhookSignature } from './signing.js'\nimport type {\n ConstructProductWebhookEventOptions,\n ProductWebhookEventPayload,\n ProductWebhookHeaders,\n VerifiedProductWebhookEvent,\n} from './types.js'\n\nfunction assertNonEmpty(value: string | null, message: string): string {\n const trimmed = value?.trim()\n if (!trimmed) {\n throw new OsWebhookSignatureVerificationError(message)\n }\n\n return trimmed\n}\n\nfunction assertTimestampWithinTolerance(\n timestamp: string,\n tolerance: number,\n now: Date,\n): void {\n if (tolerance <= 0) {\n return\n }\n\n const timestampMs = Number(timestamp)\n if (!Number.isFinite(timestampMs)) {\n throw new OsWebhookSignatureVerificationError('Invalid webhook timestamp')\n }\n\n const ageSeconds = Math.abs(now.getTime() - timestampMs) / 1000\n if (ageSeconds > tolerance) {\n throw new OsWebhookSignatureVerificationError(\n 'Webhook timestamp outside the tolerance window',\n )\n }\n}\n\nfunction parseWebhookPayload(rawBody: string): ProductWebhookEventPayload {\n try {\n const parsed = JSON.parse(rawBody) as ProductWebhookEventPayload\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n typeof parsed.type !== 'string' ||\n typeof parsed.action !== 'string' ||\n typeof parsed.idempotency_key !== 'string' ||\n typeof parsed.data !== 'object' ||\n parsed.data === null\n ) {\n throw new OsWebhookSignatureVerificationError('Invalid webhook payload shape')\n }\n\n return parsed\n } catch (error) {\n if (error instanceof OsWebhookSignatureVerificationError) {\n throw error\n }\n\n throw new OsWebhookSignatureVerificationError('Invalid JSON payload')\n }\n}\n\nexport function constructProductWebhookEvent(\n rawBody: string,\n headers: ProductWebhookHeaders,\n secret: string,\n options?: ConstructProductWebhookEventOptions,\n): VerifiedProductWebhookEvent {\n const signingSecret = secret\n if (!signingSecret) {\n throw new OsWebhookSignatureVerificationError('Webhook signing secret is required')\n }\n\n const signature = assertNonEmpty(\n readWebhookHeader(headers, WEBHOOK_SIGNATURE_HEADER),\n `Missing ${WEBHOOK_SIGNATURE_HEADER} header`,\n )\n const timestamp = assertNonEmpty(\n readWebhookHeader(headers, WEBHOOK_TIMESTAMP_HEADER),\n `Missing ${WEBHOOK_TIMESTAMP_HEADER} header`,\n )\n const webhookId = assertNonEmpty(\n readWebhookHeader(headers, WEBHOOK_ID_HEADER),\n `Missing ${WEBHOOK_ID_HEADER} header`,\n )\n\n assertTimestampWithinTolerance(\n timestamp,\n options?.tolerance ?? DEFAULT_WEBHOOK_TOLERANCE_SECONDS,\n options?.now ?? new Date(),\n )\n\n if (!verifyWebhookSignature(signingSecret, timestamp, rawBody, signature)) {\n throw new OsWebhookSignatureVerificationError(\n 'Webhook signature verification failed',\n )\n }\n\n const event = parseWebhookPayload(rawBody)\n\n return {\n ...event,\n webhookId,\n timestamp,\n }\n}\n","import {\n WEBHOOK_ID_HEADER,\n WEBHOOK_SIGNATURE_HEADER,\n WEBHOOK_TIMESTAMP_HEADER,\n} from './constants.js'\nimport { buildWebhookSignature } from './signing.js'\nimport type { GenerateTestWebhookHeadersInput } from './types.js'\n\nexport function generateTestWebhookHeaders(\n input: GenerateTestWebhookHeadersInput,\n): Record<string, string> {\n const timestamp = (input.timestamp ?? Date.now()).toString()\n const webhookId = input.webhookId ?? 'test_webhook_id'\n\n return {\n [WEBHOOK_SIGNATURE_HEADER]: buildWebhookSignature(\n input.secret,\n timestamp,\n input.payload,\n ),\n [WEBHOOK_TIMESTAMP_HEADER]: timestamp,\n [WEBHOOK_ID_HEADER]: webhookId,\n }\n}\n","import { constructProductWebhookEvent } from './construct-event.js'\nimport { generateTestWebhookHeaders } from './generate-test-headers.js'\n\nexport {\n DEFAULT_WEBHOOK_TOLERANCE_SECONDS,\n WEBHOOK_ID_HEADER,\n WEBHOOK_SIGNATURE_HEADER,\n WEBHOOK_TIMESTAMP_HEADER,\n} from './constants.js'\nexport { constructProductWebhookEvent } from './construct-event.js'\nexport { OsWebhookSignatureVerificationError } from './errors.js'\nexport { generateTestWebhookHeaders } from './generate-test-headers.js'\nexport {\n buildWebhookSignature,\n buildWebhookSignatureMaterial,\n verifyWebhookSignature,\n} from './signing.js'\nexport type {\n ConstructProductWebhookEventOptions,\n GenerateTestWebhookHeadersInput,\n ProductWebhookEventPayload,\n ProductWebhookHeaders,\n VerifiedProductWebhookEvent,\n} from './types.js'\n\n/**\n * Verify inbound OS product webhooks.\n *\n * @example\n * ```typescript\n * import { webhooks } from '@chatarmin/os/webhooks'\n *\n * export async function POST(request: Request) {\n * const rawBody = await request.text()\n * const event = webhooks.constructEvent(\n * rawBody,\n * request.headers,\n * process.env.OS_WEBHOOK_SIGNING_SECRET!,\n * )\n *\n * console.log(event.type, event.action, event.webhookId)\n * return Response.json({ received: true })\n * }\n * ```\n */\nexport const webhooks = {\n constructEvent: constructProductWebhookEvent,\n generateTestHeaders: generateTestWebhookHeaders,\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatarmin/os",
3
- "version": "1.7.0",
3
+ "version": "1.8.1",
4
4
  "description": "Type-safe SDK for ChatarminOS - Customer & Subscription Management",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -25,8 +25,7 @@
25
25
  "README.md"
26
26
  ],
27
27
  "scripts": {
28
- "build": "tsc -p tsconfig.webhooks.json --skipLibCheck",
29
- "build:full": "tsc --skipLibCheck",
28
+ "build": "tsc --skipLibCheck --noCheck",
30
29
  "dev": "tsc --watch --skipLibCheck",
31
30
  "typecheck": "tsc --noEmit --skipLibCheck",
32
31
  "test": "tsx test-sdk.ts",