@tekki-forge/plugin-webhooks 0.0.0-stage → 0.1.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/README.md CHANGED
@@ -1,3 +1,5 @@
1
- # Temporary Holding Version
1
+ # @tekki-forge/plugin-webhooks
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Forge webhooks plugin: signed, retried HTTP callbacks for record events, with delivery log.
4
+
5
+ Part of [Tekki Forge](https://www.npmjs.com/package/tekki-forge), which turns a `forge.config.ts` into a production-ready full-stack app. Apps created with `forge new` install this package for you; see the [tekki-forge](https://www.npmjs.com/package/tekki-forge) package for the documentation.
package/dist/api.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ export declare const MAX_ATTEMPTS = 8;
2
+ export declare const newSecret: () => string;
3
+ /** "Invoice.*, Customer.created" → patterns; throws a readable error for invalid ones. */
4
+ export declare function parsePatterns(value: string, entities?: string[]): string[];
5
+ /**
6
+ * Whether an endpoint's patterns match an event. A renamed entity keeps its storage name, and patterns
7
+ * saved before the rename ("Project.*") keep matching its events (now "Assignment.created").
8
+ */
9
+ export declare function patternsMatch(patterns: string[], event: {
10
+ type: string;
11
+ entity: string;
12
+ }, storageName?: string): boolean;
13
+ /** API runtime: endpoint management and event → delivery fan-out. */
14
+ declare const _default: (options?: Record<string, unknown> | undefined) => import("@tekki-forge/runtime-api").ForgeApiPlugin;
15
+ export default _default;
package/dist/api.js ADDED
@@ -0,0 +1,128 @@
1
+ import { randomBytes, randomUUID } from "node:crypto";
2
+ import { BadRequestException } from "@nestjs/common";
3
+ import { definePluginRuntime, eventMatches } from "@tekki-forge/runtime-api";
4
+ export const MAX_ATTEMPTS = 8;
5
+ const PATTERN = /^(\*|[A-Z][A-Za-z0-9]*)(\.(\*|created|updated|deleted|restored|transitioned|action))?$/;
6
+ export const newSecret = () => `whsec_${randomBytes(24).toString("base64url")}`;
7
+ /** "Invoice.*, Customer.created" → patterns; throws a readable error for invalid ones. */
8
+ export function parsePatterns(value, entities) {
9
+ const patterns = value.split(",").map((p) => p.trim()).filter(Boolean);
10
+ if (!patterns.length)
11
+ throw new BadRequestException({ statusCode: 400, message: "Validation failed", issues: [{ path: "events", message: 'Use "*" or patterns like "Invoice.*"' }] });
12
+ for (const p of patterns) {
13
+ if (!PATTERN.test(p))
14
+ throw new BadRequestException({ statusCode: 400, message: "Validation failed", issues: [{ path: "events", message: `"${p}" is not a pattern like "*", "Invoice.*" or "Invoice.created"` }] });
15
+ const entity = p.split(".")[0];
16
+ if (entities && entity !== "*" && !entities.includes(entity)) {
17
+ throw new BadRequestException({ statusCode: 400, message: "Validation failed", issues: [{ path: "events", message: `There is no entity "${entity}" (known: ${entities.join(", ")})` }] });
18
+ }
19
+ }
20
+ return patterns;
21
+ }
22
+ /**
23
+ * Whether an endpoint's patterns match an event. A renamed entity keeps its storage name, and patterns
24
+ * saved before the rename ("Project.*") keep matching its events (now "Assignment.created").
25
+ */
26
+ export function patternsMatch(patterns, event, storageName) {
27
+ const types = storageName && storageName !== event.entity ? [event.type, `${storageName}${event.type.slice(event.entity.length)}`] : [event.type];
28
+ return patterns.some((p) => types.some((type) => eventMatches(p.includes(".") ? p : `${p}.*`, type)));
29
+ }
30
+ function checkUrl(url) {
31
+ if (typeof url !== "string")
32
+ return;
33
+ let u;
34
+ try {
35
+ u = new URL(url);
36
+ }
37
+ catch {
38
+ throw new BadRequestException({ statusCode: 400, message: "Validation failed", issues: [{ path: "url", message: "Must be a valid URL" }] });
39
+ }
40
+ if (u.protocol !== "https:" && u.protocol !== "http:")
41
+ throw new BadRequestException({ statusCode: 400, message: "Validation failed", issues: [{ path: "url", message: "Must be an http(s) URL" }] });
42
+ if (u.username || u.password)
43
+ throw new BadRequestException({ statusCode: 400, message: "Validation failed", issues: [{ path: "url", message: "Put credentials in your receiver, not in the URL" }] });
44
+ }
45
+ /** API runtime: endpoint management and event → delivery fan-out. */
46
+ export default definePluginRuntime(() => ({
47
+ name: "webhooks",
48
+ setup(ctx) {
49
+ const queue = () => {
50
+ const q = ctx.use("jobs");
51
+ if (!q)
52
+ throw new Error("[webhooks] the jobs plugin must be set up before webhooks");
53
+ return q;
54
+ };
55
+ const retryDelay = Number(ctx.env.WEBHOOKS_RETRY_DELAY_MS ?? 60_000);
56
+ const enqueue = (deliveryId) => queue().enqueue("webhooks.deliver", { deliveryId, maxAttempts: MAX_ATTEMPTS }, { attempts: MAX_ATTEMPTS, backoff: retryDelay });
57
+ const prisma = ctx.prisma;
58
+ const userEntities = ctx.app.entities.filter((e) => e.name !== "WebhookEndpoint" && e.name !== "WebhookDelivery");
59
+ // Patterns saved before an entity was renamed name its storage name; both keep matching.
60
+ const entityNames = [...new Set(userEntities.flatMap((e) => [e.name, e.storageName]))];
61
+ const storageOf = new Map(userEntities.map((e) => [e.name, e.storageName]));
62
+ const tenancy = ctx.app.tenancy.enabled;
63
+ ctx.service.addExtension({
64
+ entity: "WebhookEndpoint",
65
+ hooks: {
66
+ beforeCreate: ({ data }) => {
67
+ checkUrl(data.url);
68
+ parsePatterns(data.events ?? "*", entityNames);
69
+ return { ...data, secret: newSecret() };
70
+ },
71
+ beforeUpdate: ({ data }) => {
72
+ checkUrl(data.url);
73
+ if (data.events !== undefined)
74
+ parsePatterns(data.events, entityNames);
75
+ },
76
+ },
77
+ actions: {
78
+ sendTest: async ({ record }) => {
79
+ const payload = { id: randomUUID(), type: "webhook.test", occurredAt: new Date().toISOString(), data: { message: "Test event from Forge" } };
80
+ const d = await prisma.webhookDelivery.create({
81
+ data: { endpointId: record.id, event: "webhook.test", eventId: payload.id, payload, ...(tenancy ? { organizationId: record.organizationId } : {}) },
82
+ });
83
+ await enqueue(d.id);
84
+ return { deliveryId: d.id };
85
+ },
86
+ rotateSecret: async ({ record }) => {
87
+ await prisma.webhookEndpoint.update({ where: { id: record.id }, data: { secret: newSecret() } });
88
+ },
89
+ },
90
+ });
91
+ ctx.service.addExtension({
92
+ entity: "WebhookDelivery",
93
+ actions: {
94
+ redeliver: async ({ record }) => {
95
+ await prisma.webhookDelivery.update({ where: { id: record.id }, data: { status: "pending", lastError: null } });
96
+ await enqueue(record.id);
97
+ },
98
+ },
99
+ });
100
+ ctx.events.on("*", async (event) => {
101
+ if (event.entity === "WebhookEndpoint" || event.entity === "WebhookDelivery")
102
+ return;
103
+ const endpoints = await prisma.webhookEndpoint.findMany({
104
+ where: { active: true, ...(tenancy ? { organizationId: event.organizationId ?? "__none__" } : {}) },
105
+ select: { id: true, events: true, ...(tenancy ? { organizationId: true } : {}) },
106
+ });
107
+ for (const ep of endpoints) {
108
+ const patterns = ep.events.split(",").map((p) => p.trim()).filter(Boolean);
109
+ if (!patternsMatch(patterns, event, storageOf.get(event.entity)))
110
+ continue;
111
+ const payload = {
112
+ id: event.id,
113
+ type: event.type,
114
+ occurredAt: event.occurredAt,
115
+ ...(event.name ? { name: event.name } : {}),
116
+ organizationId: event.organizationId,
117
+ actor: event.actor,
118
+ data: { record: event.record, previous: event.previous ?? null, changes: event.changes ?? null },
119
+ };
120
+ const d = await prisma.webhookDelivery.create({
121
+ data: { endpointId: ep.id, event: event.type, eventId: event.id, payload, ...(tenancy ? { organizationId: ep.organizationId } : {}) },
122
+ });
123
+ await enqueue(d.id);
124
+ }
125
+ }, "webhooks");
126
+ },
127
+ }));
128
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { type PluginDefinition } from "@tekki-forge/core";
2
+ /**
3
+ * Outgoing webhooks: admins register endpoints (URL + event patterns such as "Invoice.*"), every matching
4
+ * record event is POSTed as signed JSON by the worker, retried with exponential backoff, and logged.
5
+ * Requires the jobs plugin.
6
+ */
7
+ export declare function webhooks(): PluginDefinition;
8
+ /**
9
+ * Verify a Forge webhook on the receiving side.
10
+ * `header` is the Forge-Signature value ("t=<unix>,v1=<hex>"), `body` the raw request body.
11
+ */
12
+ export declare function verifyWebhookSignature(secret: string, header: string | null | undefined, body: string, toleranceSeconds?: number, now?: number): boolean;
13
+ export declare function signWebhook(secret: string, body: string, timestamp?: number): string;
package/dist/index.js ADDED
@@ -0,0 +1,79 @@
1
+ import { createHmac, timingSafeEqual } from "node:crypto";
2
+ import { action, boolean, datetime, definePlugin, entity, enumeration, integer, json, relation, string, text, url } from "@tekki-forge/core";
3
+ /**
4
+ * Outgoing webhooks: admins register endpoints (URL + event patterns such as "Invoice.*"), every matching
5
+ * record event is POSTed as signed JSON by the worker, retried with exponential backoff, and logged.
6
+ * Requires the jobs plugin.
7
+ */
8
+ export function webhooks() {
9
+ return definePlugin({
10
+ name: "webhooks",
11
+ package: "@tekki-forge/plugin-webhooks",
12
+ requires: ["jobs"],
13
+ runtime: { api: true, worker: true },
14
+ env: [{ name: "WEBHOOKS_ALLOW_PRIVATE", description: "Allow webhook URLs on private networks (default: only outside production)" }],
15
+ nav: [{ label: "Webhooks", path: "webhook-endpoints", icon: "layers", admin: true }],
16
+ entities: {
17
+ WebhookEndpoint: entity({
18
+ label: "Webhook endpoint",
19
+ icon: "layers",
20
+ nav: false,
21
+ display: "url",
22
+ fields: {
23
+ url: url({ required: true, maxLength: 2000, description: "HTTPS URL that receives POST requests" }),
24
+ description: string(),
25
+ events: string({ required: true, default: "*", description: 'Comma-separated patterns: "*", "Invoice.*", "Invoice.transitioned"' }),
26
+ active: boolean({ default: true }),
27
+ secret: string({ readonly: true, description: "Signing secret — verify the Forge-Signature header with it", permissions: { read: ["admin"] } }),
28
+ },
29
+ permissions: { create: ["admin"], read: ["admin"], update: ["admin"], delete: ["admin"] },
30
+ features: { audit: true },
31
+ actions: {
32
+ sendTest: action({ label: "Send test event" }),
33
+ rotateSecret: action({ label: "Rotate secret", confirm: "Receivers must switch to the new secret. Continue?", variant: "danger" }),
34
+ },
35
+ list: { columns: ["url", "events", "active", "createdAt"] },
36
+ }),
37
+ WebhookDelivery: entity({
38
+ label: "Webhook delivery",
39
+ icon: "layers",
40
+ nav: false,
41
+ display: "event",
42
+ fields: {
43
+ endpoint: relation({ entity: "WebhookEndpoint", required: true, onDelete: "cascade", inverse: "deliveries" }),
44
+ event: string({ required: true, readonly: true, searchable: true }),
45
+ eventId: string({ readonly: true }),
46
+ status: enumeration({ values: ["pending", "succeeded", "failed"], default: "pending", readonly: true }),
47
+ attempts: integer({ default: 0, readonly: true }),
48
+ responseStatus: integer({ readonly: true }),
49
+ lastError: text({ readonly: true }),
50
+ deliveredAt: datetime({ readonly: true }),
51
+ payload: json({ readonly: true }),
52
+ },
53
+ permissions: { create: [], read: ["admin"], update: ["admin"], delete: ["admin"] },
54
+ actions: { redeliver: action({ label: "Redeliver" }) },
55
+ list: { columns: ["event", "endpoint", "status", "attempts", "responseStatus", "createdAt"] },
56
+ }),
57
+ },
58
+ });
59
+ }
60
+ /**
61
+ * Verify a Forge webhook on the receiving side.
62
+ * `header` is the Forge-Signature value ("t=<unix>,v1=<hex>"), `body` the raw request body.
63
+ */
64
+ export function verifyWebhookSignature(secret, header, body, toleranceSeconds = 300, now = Date.now()) {
65
+ if (!header)
66
+ return false;
67
+ const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
68
+ const t = Number(parts.t);
69
+ if (!Number.isFinite(t) || !parts.v1 || Math.abs(now / 1000 - t) > toleranceSeconds)
70
+ return false;
71
+ const expected = createHmac("sha256", secret).update(`${t}.${body}`).digest("hex");
72
+ const a = Buffer.from(expected, "hex");
73
+ const b = Buffer.from(parts.v1, "hex");
74
+ return a.length === b.length && timingSafeEqual(a, b);
75
+ }
76
+ export function signWebhook(secret, body, timestamp = Math.floor(Date.now() / 1000)) {
77
+ return `t=${timestamp},v1=${createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex")}`;
78
+ }
79
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { PluginContext } from "@tekki-forge/runtime-api";
2
+ /** Private, loopback, link-local and other non-public ranges (SSRF protection). */
3
+ export declare function isPrivateAddress(ip: string): boolean;
4
+ export declare function assertPublicUrl(url: string, allowPrivate: boolean): Promise<void>;
5
+ /** Worker runtime: delivers queued webhooks. */
6
+ export default function webhooksWorker(_options?: Record<string, unknown>): {
7
+ name: string;
8
+ setup(ctx: PluginContext): void;
9
+ };
package/dist/worker.js ADDED
@@ -0,0 +1,78 @@
1
+ import { lookup } from "node:dns/promises";
2
+ import { isIP } from "node:net";
3
+ import { signWebhook } from "./index.js";
4
+ /** Private, loopback, link-local and other non-public ranges (SSRF protection). */
5
+ export function isPrivateAddress(ip) {
6
+ if (isIP(ip) === 4) {
7
+ const [a, b] = ip.split(".").map(Number);
8
+ return a === 10 || a === 127 || a === 0 || (a === 169 && b === 254) || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168) || (a === 100 && b >= 64 && b <= 127) || a >= 224;
9
+ }
10
+ const v = ip.toLowerCase();
11
+ if (v.startsWith("::ffff:"))
12
+ return isPrivateAddress(v.slice(7));
13
+ return v === "::1" || v === "::" || v.startsWith("fc") || v.startsWith("fd") || v.startsWith("fe8") || v.startsWith("fe9") || v.startsWith("fea") || v.startsWith("feb");
14
+ }
15
+ export async function assertPublicUrl(url, allowPrivate) {
16
+ if (allowPrivate)
17
+ return;
18
+ const host = new URL(url).hostname.replace(/^\[|\]$/g, "");
19
+ const addresses = isIP(host) ? [host] : (await lookup(host, { all: true })).map((a) => a.address);
20
+ if (addresses.some(isPrivateAddress))
21
+ throw new Error(`Refusing to call ${host}: it resolves to a private address (set WEBHOOKS_ALLOW_PRIVATE=true to allow)`);
22
+ }
23
+ /** Worker runtime: delivers queued webhooks. */
24
+ export default function webhooksWorker(_options = {}) {
25
+ return {
26
+ name: "webhooks",
27
+ setup(ctx) {
28
+ const allowPrivate = ctx.env.WEBHOOKS_ALLOW_PRIVATE ? ctx.env.WEBHOOKS_ALLOW_PRIVATE === "true" : ctx.env.NODE_ENV !== "production";
29
+ let handlers = ctx.use("jobHandlers");
30
+ if (!handlers) {
31
+ handlers = new Map();
32
+ ctx.provide("jobHandlers", handlers);
33
+ }
34
+ handlers.set("webhooks.deliver", async ({ data, prisma, attempt }) => {
35
+ const delivery = await prisma.webhookDelivery.findUnique({ where: { id: data.deliveryId }, include: { endpoint: true } });
36
+ if (!delivery)
37
+ return { skipped: "delivery deleted" };
38
+ if (!delivery.endpoint.active) {
39
+ await prisma.webhookDelivery.update({ where: { id: delivery.id }, data: { status: "failed", lastError: "Endpoint is disabled" } });
40
+ return { skipped: "endpoint disabled" };
41
+ }
42
+ const body = JSON.stringify(delivery.payload);
43
+ let status = null;
44
+ try {
45
+ await assertPublicUrl(delivery.endpoint.url, allowPrivate);
46
+ const res = await fetch(delivery.endpoint.url, {
47
+ method: "POST",
48
+ redirect: "manual",
49
+ signal: AbortSignal.timeout(10_000),
50
+ headers: {
51
+ "content-type": "application/json",
52
+ "user-agent": "Forge-Webhooks/1.0",
53
+ "forge-event": delivery.event,
54
+ "forge-delivery": delivery.id,
55
+ "forge-signature": signWebhook(delivery.endpoint.secret, body),
56
+ },
57
+ body,
58
+ });
59
+ status = res.status;
60
+ await res.body?.cancel().catch(() => undefined);
61
+ if (status < 200 || status >= 300)
62
+ throw new Error(`HTTP ${status}`);
63
+ await prisma.webhookDelivery.update({ where: { id: delivery.id }, data: { status: "succeeded", attempts: attempt, responseStatus: status, lastError: null, deliveredAt: new Date() } });
64
+ return { status };
65
+ }
66
+ catch (e) {
67
+ const final = attempt >= (data.maxAttempts ?? 8);
68
+ await prisma.webhookDelivery.update({
69
+ where: { id: delivery.id },
70
+ data: { status: final ? "failed" : "pending", attempts: attempt, responseStatus: status, lastError: e.message.slice(0, 1000) },
71
+ });
72
+ throw e; // BullMQ retries with exponential backoff
73
+ }
74
+ });
75
+ },
76
+ };
77
+ }
78
+ //# sourceMappingURL=worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.js","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,47 @@
1
1
  {
2
2
  "name": "@tekki-forge/plugin-webhooks",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Forge webhooks plugin: signed, retried HTTP callbacks for record events, with delivery log",
5
+ "keywords": [
6
+ "code-generator",
7
+ "forge",
8
+ "full-stack",
9
+ "tekki-forge"
10
+ ],
11
+ "author": "Mahy KANE",
12
+ "license": "MIT",
13
+ "type": "module",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ },
19
+ "./api": {
20
+ "types": "./dist/api.d.ts",
21
+ "default": "./dist/api.js"
22
+ },
23
+ "./worker": {
24
+ "types": "./dist/worker.d.ts",
25
+ "default": "./dist/worker.js"
26
+ }
27
+ },
28
+ "files": [
29
+ "dist"
30
+ ],
31
+ "engines": {
32
+ "node": ">=22"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "dependencies": {
38
+ "@nestjs/common": "^12.1.2",
39
+ "@tekki-forge/compiler": "^0.1.0",
40
+ "@tekki-forge/runtime-api": "^0.1.0",
41
+ "@tekki-forge/core": "^0.1.0"
42
+ },
43
+ "scripts": {
44
+ "build": "tsc -p tsconfig.json",
45
+ "typecheck": "tsc -p tsconfig.json --noEmit"
46
+ }
6
47
  }