pipework 0.1.2 → 0.1.3

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.
Files changed (83) hide show
  1. package/dist/cli/commands/migrate.d.ts +4 -2
  2. package/dist/cli/commands/migrate.d.ts.map +1 -1
  3. package/dist/cli/commands/migrate.js +16 -3
  4. package/dist/cli/commands/migrate.js.map +1 -1
  5. package/dist/cli/index.d.ts.map +1 -1
  6. package/dist/cli/index.js +14 -4
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/di/builder.d.ts +4 -0
  9. package/dist/di/builder.d.ts.map +1 -1
  10. package/dist/di/builder.js +8 -0
  11. package/dist/di/builder.js.map +1 -1
  12. package/dist/di/resolve.d.ts +1 -0
  13. package/dist/di/resolve.d.ts.map +1 -1
  14. package/dist/di/resolve.js +7 -14
  15. package/dist/di/resolve.js.map +1 -1
  16. package/dist/di/types.d.ts +1 -0
  17. package/dist/di/types.d.ts.map +1 -1
  18. package/dist/http/server.d.ts.map +1 -1
  19. package/dist/http/server.js +21 -1
  20. package/dist/http/server.js.map +1 -1
  21. package/dist/http/types.d.ts +1 -0
  22. package/dist/http/types.d.ts.map +1 -1
  23. package/dist/idempotency/index.d.ts +4 -0
  24. package/dist/idempotency/index.d.ts.map +1 -0
  25. package/dist/idempotency/index.js +3 -0
  26. package/dist/idempotency/index.js.map +1 -0
  27. package/dist/idempotency/middleware.d.ts +21 -0
  28. package/dist/idempotency/middleware.d.ts.map +1 -0
  29. package/dist/idempotency/middleware.js +56 -0
  30. package/dist/idempotency/middleware.js.map +1 -0
  31. package/dist/idempotency/store.d.ts +3 -0
  32. package/dist/idempotency/store.d.ts.map +1 -0
  33. package/dist/idempotency/store.js +63 -0
  34. package/dist/idempotency/store.js.map +1 -0
  35. package/dist/idempotency/types.d.ts +28 -0
  36. package/dist/idempotency/types.d.ts.map +1 -0
  37. package/dist/idempotency/types.js +2 -0
  38. package/dist/idempotency/types.js.map +1 -0
  39. package/dist/index.d.ts +5 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +3 -0
  42. package/dist/index.js.map +1 -1
  43. package/dist/migrate/check.d.ts +14 -0
  44. package/dist/migrate/check.d.ts.map +1 -0
  45. package/dist/migrate/check.js +58 -0
  46. package/dist/migrate/check.js.map +1 -0
  47. package/dist/migrate/index.d.ts +3 -1
  48. package/dist/migrate/index.d.ts.map +1 -1
  49. package/dist/migrate/index.js +3 -1
  50. package/dist/migrate/index.js.map +1 -1
  51. package/dist/migrate/run.d.ts +13 -2
  52. package/dist/migrate/run.d.ts.map +1 -1
  53. package/dist/migrate/run.js +24 -3
  54. package/dist/migrate/run.js.map +1 -1
  55. package/dist/migrate/safety.d.ts +10 -0
  56. package/dist/migrate/safety.d.ts.map +1 -0
  57. package/dist/migrate/safety.js +115 -0
  58. package/dist/migrate/safety.js.map +1 -0
  59. package/dist/webhook/inbound.d.ts +16 -0
  60. package/dist/webhook/inbound.d.ts.map +1 -0
  61. package/dist/webhook/inbound.js +21 -0
  62. package/dist/webhook/inbound.js.map +1 -0
  63. package/dist/webhook/index.d.ts +5 -0
  64. package/dist/webhook/index.d.ts.map +1 -0
  65. package/dist/webhook/index.js +5 -0
  66. package/dist/webhook/index.js.map +1 -0
  67. package/dist/webhook/namespace.d.ts +15 -0
  68. package/dist/webhook/namespace.d.ts.map +1 -0
  69. package/dist/webhook/namespace.js +15 -0
  70. package/dist/webhook/namespace.js.map +1 -0
  71. package/dist/webhook/outbound.d.ts +48 -0
  72. package/dist/webhook/outbound.d.ts.map +1 -0
  73. package/dist/webhook/outbound.js +160 -0
  74. package/dist/webhook/outbound.js.map +1 -0
  75. package/dist/webhook/sign.d.ts +12 -0
  76. package/dist/webhook/sign.d.ts.map +1 -0
  77. package/dist/webhook/sign.js +12 -0
  78. package/dist/webhook/sign.js.map +1 -0
  79. package/dist/webhook/verify.d.ts +14 -0
  80. package/dist/webhook/verify.d.ts.map +1 -0
  81. package/dist/webhook/verify.js +61 -0
  82. package/dist/webhook/verify.js.map +1 -0
  83. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"namespace.js","sourceRoot":"","sources":["../../src/webhook/namespace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AACvC,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAErD,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,aAAa,EAAE,oBAAoB;IACnC,cAAc,EAAE,qBAAqB;IAErC,MAAM,EAAE;QACN,IAAI,EAAE,YAAY;QAClB,MAAM,EAAE,cAAc;QACtB,MAAM,EAAE,cAAc;KACvB;IAED,IAAI,EAAE,WAAW;CACT,CAAA"}
@@ -0,0 +1,48 @@
1
+ import type { DB } from '../db/index.js';
2
+ import { type WebhookSignatureOptions } from './sign.js';
3
+ export interface OutboundWebhookConfig {
4
+ table?: string;
5
+ deliveryTable?: string;
6
+ signing?: WebhookSignatureOptions;
7
+ retry?: {
8
+ maxAttempts?: number;
9
+ initialDelayMs?: number;
10
+ maxDelayMs?: number;
11
+ };
12
+ timeoutMs?: number;
13
+ }
14
+ export interface WebhookEndpoint {
15
+ id: string;
16
+ tenantId: string;
17
+ url: string;
18
+ secret: string;
19
+ events: string[];
20
+ active: boolean;
21
+ }
22
+ export interface DeliveryAttempt {
23
+ id: string;
24
+ endpointId: string;
25
+ tenantId: string;
26
+ eventType: string;
27
+ payload: unknown;
28
+ statusCode: number | null;
29
+ responseBody: string | null;
30
+ attempt: number;
31
+ maxAttempts: number;
32
+ nextRetryAt: Date | null;
33
+ createdAt: Date;
34
+ }
35
+ export interface OutboundWebhook {
36
+ ensureTables(db: DB): Promise<void>;
37
+ send(db: DB, tenantId: string, eventType: string, payload: unknown): Promise<string[]>;
38
+ deliver(db: DB, deliveryId: string): Promise<{
39
+ success: boolean;
40
+ statusCode: number | null;
41
+ }>;
42
+ processPending(db: DB): Promise<number>;
43
+ registerEndpoint(db: DB, endpoint: Omit<WebhookEndpoint, 'id'>): Promise<string>;
44
+ listEndpoints(db: DB, tenantId: string): Promise<WebhookEndpoint[]>;
45
+ removeEndpoint(db: DB, tenantId: string, endpointId: string): Promise<boolean>;
46
+ }
47
+ export declare function createOutboundWebhook(config?: OutboundWebhookConfig): OutboundWebhook;
48
+ //# sourceMappingURL=outbound.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbound.d.ts","sourceRoot":"","sources":["../../src/webhook/outbound.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAA;AACxC,OAAO,EAAe,KAAK,uBAAuB,EAAE,MAAM,WAAW,CAAA;AAErE,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,uBAAuB,CAAA;IACjC,KAAK,CAAC,EAAE;QACN,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;IACD,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAA;IACxB,SAAS,EAAE,IAAI,CAAA;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,YAAY,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnC,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IACtF,OAAO,CAAC,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAA;IAC7F,cAAc,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACvC,gBAAgB,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,eAAe,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAChF,aAAa,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAAA;IACnE,cAAc,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAC/E;AAWD,wBAAgB,qBAAqB,CAAC,MAAM,GAAE,qBAA0B,GAAG,eAAe,CAqLzF"}
@@ -0,0 +1,160 @@
1
+ import { sql } from '../db/sql.js';
2
+ import { signPayload } from './sign.js';
3
+ const DEFAULTS = {
4
+ table: '__pipework_webhook_endpoints',
5
+ deliveryTable: '__pipework_webhook_deliveries',
6
+ maxAttempts: 5,
7
+ initialDelayMs: 1000,
8
+ maxDelayMs: 3_600_000,
9
+ timeoutMs: 30_000,
10
+ };
11
+ export function createOutboundWebhook(config = {}) {
12
+ const endpointTable = config.table ?? DEFAULTS.table;
13
+ const deliveryTable = config.deliveryTable ?? DEFAULTS.deliveryTable;
14
+ const maxAttempts = config.retry?.maxAttempts ?? DEFAULTS.maxAttempts;
15
+ const initialDelayMs = config.retry?.initialDelayMs ?? DEFAULTS.initialDelayMs;
16
+ const maxDelayMs = config.retry?.maxDelayMs ?? DEFAULTS.maxDelayMs;
17
+ const timeoutMs = config.timeoutMs ?? DEFAULTS.timeoutMs;
18
+ const signingOptions = config.signing ?? {};
19
+ return {
20
+ async ensureTables(db) {
21
+ await db.execute(sql.raw(`
22
+ CREATE TABLE IF NOT EXISTS ${endpointTable} (
23
+ id TEXT NOT NULL PRIMARY KEY DEFAULT gen_random_uuid()::text,
24
+ tenant_id TEXT NOT NULL,
25
+ url TEXT NOT NULL,
26
+ secret TEXT NOT NULL,
27
+ events TEXT[] NOT NULL DEFAULT '{}',
28
+ active BOOLEAN NOT NULL DEFAULT true,
29
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
30
+ )
31
+ `));
32
+ await db.execute(sql.raw(`
33
+ CREATE TABLE IF NOT EXISTS ${deliveryTable} (
34
+ id TEXT NOT NULL PRIMARY KEY DEFAULT gen_random_uuid()::text,
35
+ endpoint_id TEXT NOT NULL,
36
+ tenant_id TEXT NOT NULL,
37
+ event_type TEXT NOT NULL,
38
+ payload JSONB NOT NULL,
39
+ status_code INTEGER,
40
+ response_body TEXT,
41
+ attempt INTEGER NOT NULL DEFAULT 0,
42
+ max_attempts INTEGER NOT NULL,
43
+ next_retry_at TIMESTAMPTZ,
44
+ completed_at TIMESTAMPTZ,
45
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
46
+ )
47
+ `));
48
+ },
49
+ async send(db, tenantId, eventType, payload) {
50
+ const endpoints = await db.execute(sql `SELECT id, url, secret FROM ${sql.raw(endpointTable)}
51
+ WHERE tenant_id = ${tenantId}
52
+ AND active = true
53
+ AND (events = '{}' OR ${eventType} = ANY(events))`);
54
+ const deliveryIds = [];
55
+ for (const row of endpoints) {
56
+ const result = await db.execute(sql `INSERT INTO ${sql.raw(deliveryTable)} (endpoint_id, tenant_id, event_type, payload, max_attempts, next_retry_at)
57
+ VALUES (${row.id}, ${tenantId}, ${eventType}, ${JSON.stringify(payload)}, ${maxAttempts}, now())
58
+ RETURNING id`);
59
+ const inserted = result[0];
60
+ if (inserted !== undefined)
61
+ deliveryIds.push(inserted.id);
62
+ }
63
+ return deliveryIds;
64
+ },
65
+ async deliver(db, deliveryId) {
66
+ const rows = await db.execute(sql `SELECT d.id, d.endpoint_id, d.event_type, d.payload, d.attempt, d.max_attempts,
67
+ e.url, e.secret
68
+ FROM ${sql.raw(deliveryTable)} d
69
+ JOIN ${sql.raw(endpointTable)} e ON e.id = d.endpoint_id
70
+ WHERE d.id = ${deliveryId} AND d.completed_at IS NULL`);
71
+ const delivery = rows[0];
72
+ if (delivery === undefined)
73
+ return { success: false, statusCode: null };
74
+ const attempt = delivery['attempt'] + 1;
75
+ const signed = signPayload(delivery['payload'], delivery['secret'], signingOptions);
76
+ let statusCode = null;
77
+ let responseBody = null;
78
+ let success = false;
79
+ try {
80
+ const controller = new AbortController();
81
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
82
+ const response = await fetch(delivery['url'], {
83
+ method: 'POST',
84
+ headers: {
85
+ 'Content-Type': 'application/json',
86
+ 'X-Webhook-Id': signed.messageId,
87
+ 'X-Webhook-Timestamp': signed.timestamp.toString(),
88
+ 'X-Webhook-Signature': signed.signature,
89
+ },
90
+ body: signed.body,
91
+ signal: controller.signal,
92
+ });
93
+ clearTimeout(timer);
94
+ statusCode = response.status;
95
+ responseBody = await response.text().catch(() => null);
96
+ success = statusCode >= 200 && statusCode < 300;
97
+ }
98
+ catch {
99
+ statusCode = null;
100
+ responseBody = null;
101
+ success = false;
102
+ }
103
+ if (success || attempt >= delivery['max_attempts']) {
104
+ await db.execute(sql `UPDATE ${sql.raw(deliveryTable)}
105
+ SET attempt = ${attempt}, status_code = ${statusCode},
106
+ response_body = ${responseBody}, completed_at = now(), next_retry_at = NULL
107
+ WHERE id = ${deliveryId}`);
108
+ }
109
+ else {
110
+ const delayMs = Math.min(initialDelayMs * Math.pow(2, attempt - 1), maxDelayMs);
111
+ const nextRetry = new Date(Date.now() + delayMs);
112
+ await db.execute(sql `UPDATE ${sql.raw(deliveryTable)}
113
+ SET attempt = ${attempt}, status_code = ${statusCode},
114
+ response_body = ${responseBody}, next_retry_at = ${nextRetry.toISOString()}
115
+ WHERE id = ${deliveryId}`);
116
+ }
117
+ return { success, statusCode };
118
+ },
119
+ async processPending(db) {
120
+ const rows = await db.execute(sql `SELECT id FROM ${sql.raw(deliveryTable)}
121
+ WHERE completed_at IS NULL AND next_retry_at <= now()
122
+ ORDER BY next_retry_at ASC
123
+ LIMIT 100`);
124
+ let processed = 0;
125
+ for (const row of rows) {
126
+ await this.deliver(db, row.id);
127
+ processed++;
128
+ }
129
+ return processed;
130
+ },
131
+ async registerEndpoint(db, endpoint) {
132
+ const rows = await db.execute(sql `INSERT INTO ${sql.raw(endpointTable)} (tenant_id, url, secret, events, active)
133
+ VALUES (${endpoint.tenantId}, ${endpoint.url}, ${endpoint.secret},
134
+ ${`{${endpoint.events.join(',')}}`}, ${endpoint.active})
135
+ RETURNING id`);
136
+ return (rows[0]).id;
137
+ },
138
+ async listEndpoints(db, tenantId) {
139
+ const rows = await db.execute(sql `SELECT id, tenant_id, url, secret, events, active
140
+ FROM ${sql.raw(endpointTable)}
141
+ WHERE tenant_id = ${tenantId}
142
+ ORDER BY created_at ASC`);
143
+ return rows.map(row => ({
144
+ id: row['id'],
145
+ tenantId: row['tenant_id'],
146
+ url: row['url'],
147
+ secret: row['secret'],
148
+ events: row['events'],
149
+ active: row['active'],
150
+ }));
151
+ },
152
+ async removeEndpoint(db, tenantId, endpointId) {
153
+ const rows = await db.execute(sql `DELETE FROM ${sql.raw(endpointTable)}
154
+ WHERE id = ${endpointId} AND tenant_id = ${tenantId}
155
+ RETURNING id`);
156
+ return rows.length > 0;
157
+ },
158
+ };
159
+ }
160
+ //# sourceMappingURL=outbound.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbound.js","sourceRoot":"","sources":["../../src/webhook/outbound.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export interface WebhookSignatureOptions {
2
+ algorithm?: string;
3
+ encoding?: 'hex' | 'base64';
4
+ }
5
+ export interface SignedPayload {
6
+ body: string;
7
+ signature: string;
8
+ timestamp: number;
9
+ messageId: string;
10
+ }
11
+ export declare function signPayload(payload: unknown, secret: string, options?: WebhookSignatureOptions): SignedPayload;
12
+ //# sourceMappingURL=sign.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../../src/webhook/sign.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAA;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,WAAW,CACzB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,uBAA4B,GACpC,aAAa,CAWf"}
@@ -0,0 +1,12 @@
1
+ import { createHmac, randomUUID } from 'node:crypto';
2
+ export function signPayload(payload, secret, options = {}) {
3
+ const algorithm = options.algorithm ?? 'sha256';
4
+ const encoding = options.encoding ?? 'hex';
5
+ const messageId = randomUUID();
6
+ const timestamp = Math.floor(Date.now() / 1000);
7
+ const body = typeof payload === 'string' ? payload : JSON.stringify(payload);
8
+ const toSign = `${messageId}.${timestamp}.${body}`;
9
+ const signature = createHmac(algorithm, secret).update(toSign).digest(encoding);
10
+ return { body, signature, timestamp, messageId };
11
+ }
12
+ //# sourceMappingURL=sign.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sign.js","sourceRoot":"","sources":["../../src/webhook/sign.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAcpD,MAAM,UAAU,WAAW,CACzB,OAAgB,EAChB,MAAc,EACd,UAAmC,EAAE;IAErC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,QAAQ,CAAA;IAC/C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,KAAK,CAAA;IAE1C,MAAM,SAAS,GAAG,UAAU,EAAE,CAAA;IAC9B,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAA;IAC/C,MAAM,IAAI,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;IAC5E,MAAM,MAAM,GAAG,GAAG,SAAS,IAAI,SAAS,IAAI,IAAI,EAAE,CAAA;IAClD,MAAM,SAAS,GAAG,UAAU,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IAE/E,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,CAAA;AAClD,CAAC"}
@@ -0,0 +1,14 @@
1
+ export interface SignatureVerifier {
2
+ verify(rawBody: Buffer, headers: Readonly<Record<string, string | undefined>>): boolean;
3
+ }
4
+ export declare function hmacVerifier(options: {
5
+ secret: string;
6
+ algorithm?: string;
7
+ header: string;
8
+ prefix?: string;
9
+ encoding?: 'hex' | 'base64';
10
+ computePayload?: (rawBody: Buffer, headers: Readonly<Record<string, string | undefined>>) => Buffer;
11
+ }): SignatureVerifier;
12
+ export declare function stripeVerifier(secret: string): SignatureVerifier;
13
+ export declare function githubVerifier(secret: string): SignatureVerifier;
14
+ //# sourceMappingURL=verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/webhook/verify.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GAAG,OAAO,CAAA;CACxF;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE;IACpC,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAA;IAC3B,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,KAAK,MAAM,CAAA;CACpG,GAAG,iBAAiB,CAuBpB;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,CA2BhE;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,CAQhE"}
@@ -0,0 +1,61 @@
1
+ import { createHmac, timingSafeEqual } from 'node:crypto';
2
+ export function hmacVerifier(options) {
3
+ const algorithm = options.algorithm ?? 'sha256';
4
+ const encoding = options.encoding ?? 'hex';
5
+ const prefix = options.prefix ?? '';
6
+ return {
7
+ verify(rawBody, headers) {
8
+ const headerValue = headers[options.header.toLowerCase()];
9
+ if (headerValue === undefined || headerValue === '')
10
+ return false;
11
+ const payload = options.computePayload !== undefined
12
+ ? options.computePayload(rawBody, headers)
13
+ : rawBody;
14
+ const expected = prefix + createHmac(algorithm, options.secret).update(payload).digest(encoding);
15
+ try {
16
+ return timingSafeEqual(Buffer.from(headerValue), Buffer.from(expected));
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ },
22
+ };
23
+ }
24
+ export function stripeVerifier(secret) {
25
+ return {
26
+ verify(rawBody, headers) {
27
+ const sigHeader = headers['stripe-signature'];
28
+ if (sigHeader === undefined)
29
+ return false;
30
+ const parts = new Map();
31
+ for (const pair of sigHeader.split(',')) {
32
+ const eqIdx = pair.indexOf('=');
33
+ if (eqIdx === -1)
34
+ continue;
35
+ parts.set(pair.slice(0, eqIdx).trim(), pair.slice(eqIdx + 1).trim());
36
+ }
37
+ const timestamp = parts.get('t');
38
+ const v1Sig = parts.get('v1');
39
+ if (timestamp === undefined || v1Sig === undefined)
40
+ return false;
41
+ const signedPayload = Buffer.from(`${timestamp}.${rawBody.toString('utf-8')}`);
42
+ const expected = createHmac('sha256', secret).update(signedPayload).digest('hex');
43
+ try {
44
+ return timingSafeEqual(Buffer.from(v1Sig), Buffer.from(expected));
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ },
50
+ };
51
+ }
52
+ export function githubVerifier(secret) {
53
+ return hmacVerifier({
54
+ secret,
55
+ algorithm: 'sha256',
56
+ header: 'x-hub-signature-256',
57
+ prefix: 'sha256=',
58
+ encoding: 'hex',
59
+ });
60
+ }
61
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/webhook/verify.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pipework",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "TypeScript framework for multi-tenant SaaS applications. PostgreSQL-only.",
5
5
  "type": "module",
6
6
  "license": "MIT",