@hogsend/engine 0.21.0 → 0.21.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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hogsend/engine",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -40,14 +40,14 @@
|
|
|
40
40
|
"svix": "^1.95.1",
|
|
41
41
|
"winston": "^3.19.0",
|
|
42
42
|
"zod": "^4.4.3",
|
|
43
|
-
"@hogsend/core": "^0.21.
|
|
44
|
-
"@hogsend/db": "^0.21.
|
|
45
|
-
"@hogsend/email": "^0.21.
|
|
46
|
-
"@hogsend/plugin-posthog": "^0.21.
|
|
47
|
-
"@hogsend/plugin-resend": "^0.21.
|
|
43
|
+
"@hogsend/core": "^0.21.1",
|
|
44
|
+
"@hogsend/db": "^0.21.1",
|
|
45
|
+
"@hogsend/email": "^0.21.1",
|
|
46
|
+
"@hogsend/plugin-posthog": "^0.21.1",
|
|
47
|
+
"@hogsend/plugin-resend": "^0.21.1"
|
|
48
48
|
},
|
|
49
49
|
"optionalDependencies": {
|
|
50
|
-
"@hogsend/plugin-postmark": "^0.21.
|
|
50
|
+
"@hogsend/plugin-postmark": "^0.21.1"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/node": "^22.15.3",
|
|
@@ -297,6 +297,25 @@ export async function deleteProviderCredential(
|
|
|
297
297
|
return deleted.length > 0;
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
+
/**
|
|
301
|
+
* Purge EVERY stored credential for a provider — the oauth grant AND the
|
|
302
|
+
* server-derived config (minted webhook secret + grabbed phc_). Disconnect
|
|
303
|
+
* must leave no orphaned rows; the single-kind `deleteProviderCredential`
|
|
304
|
+
* stays unchanged so token-lifecycle callers can still target one kind.
|
|
305
|
+
* Returns which kinds were removed. Never decrypts.
|
|
306
|
+
*/
|
|
307
|
+
export async function deleteAllProviderCredentials(
|
|
308
|
+
db: Database,
|
|
309
|
+
providerId: string,
|
|
310
|
+
): Promise<{ oauth: boolean; derived: boolean }> {
|
|
311
|
+
const [oauth, derived] = await Promise.all([
|
|
312
|
+
deleteProviderCredential(db, providerId, "oauth"),
|
|
313
|
+
deleteProviderCredential(db, providerId, "derived"),
|
|
314
|
+
]);
|
|
315
|
+
|
|
316
|
+
return { oauth, derived };
|
|
317
|
+
}
|
|
318
|
+
|
|
300
319
|
/**
|
|
301
320
|
* Read + decrypt the kind="derived" config for a provider. `null` when none
|
|
302
321
|
* stored; throws `ProviderCredentialDecryptError` when a row exists but cannot
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
provisionPostHogLoop,
|
|
15
15
|
} from "../../lib/provision-posthog-loop.js";
|
|
16
16
|
import { errorSchema } from "../../lib/schemas.js";
|
|
17
|
+
import { invalidateStoredPosthogSecret } from "../webhooks/sources.js";
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Admin analytics-connection routes — the server half of
|
|
@@ -170,9 +171,8 @@ const provisionLoopRoute = createRoute({
|
|
|
170
171
|
description:
|
|
171
172
|
"Refused: `no_posthog_credential` (no OAuth credential and no " +
|
|
172
173
|
"personal API key), `posthog_not_configured` (no PostHog env signal " +
|
|
173
|
-
"at all), `
|
|
174
|
-
"
|
|
175
|
-
"PostHog cannot deliver to it)",
|
|
174
|
+
"at all), or `api_public_url_unreachable` (API_PUBLIC_URL is loopback " +
|
|
175
|
+
"— PostHog cannot deliver to it)",
|
|
176
176
|
},
|
|
177
177
|
502: {
|
|
178
178
|
content: { "application/json": { schema: provisionFailureSchema } },
|
|
@@ -273,6 +273,9 @@ export const analyticsAdminRouter = new OpenAPIHono<AppEnv>()
|
|
|
273
273
|
...(storedDerived ?? {}),
|
|
274
274
|
webhookSecret,
|
|
275
275
|
});
|
|
276
|
+
// Bust the inbound posthog webhook source's cached secret so it enforces
|
|
277
|
+
// the freshly-minted value immediately instead of after the ~30s TTL.
|
|
278
|
+
invalidateStoredPosthogSecret();
|
|
276
279
|
}
|
|
277
280
|
|
|
278
281
|
try {
|
|
@@ -318,9 +321,6 @@ export const analyticsAdminRouter = new OpenAPIHono<AppEnv>()
|
|
|
318
321
|
);
|
|
319
322
|
} catch (error) {
|
|
320
323
|
if (error instanceof ProvisionPostHogLoopError) {
|
|
321
|
-
if (error.code === "missing-webhook-secret") {
|
|
322
|
-
return c.json({ error: "webhook_secret_missing" }, 409);
|
|
323
|
-
}
|
|
324
324
|
return c.json(
|
|
325
325
|
{
|
|
326
326
|
error: error.code,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
|
|
2
2
|
import type { AppEnv } from "../../app.js";
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
deleteAllProviderCredentials,
|
|
5
5
|
getProviderCredential,
|
|
6
6
|
ProviderCredentialDecryptError,
|
|
7
7
|
type ProviderCredentialMeta,
|
|
@@ -108,7 +108,11 @@ const deleteRoute = createRoute({
|
|
|
108
108
|
method: "delete",
|
|
109
109
|
path: "/{providerId}",
|
|
110
110
|
tags: ["Admin — Provider Credentials"],
|
|
111
|
-
summary: "
|
|
111
|
+
summary: "Purge a provider's stored credentials (oauth + derived)",
|
|
112
|
+
description:
|
|
113
|
+
"Disconnect: hard-deletes EVERY stored credential for the provider — " +
|
|
114
|
+
"the oauth grant AND the server-derived config (minted webhook secret + " +
|
|
115
|
+
"grabbed phc_) — so no orphaned rows remain.",
|
|
112
116
|
request: {
|
|
113
117
|
params: providerIdParam,
|
|
114
118
|
},
|
|
@@ -117,11 +121,11 @@ const deleteRoute = createRoute({
|
|
|
117
121
|
content: {
|
|
118
122
|
"application/json": { schema: z.object({ deleted: z.boolean() }) },
|
|
119
123
|
},
|
|
120
|
-
description: "
|
|
124
|
+
description: "Credentials hard-deleted (at least one row removed)",
|
|
121
125
|
},
|
|
122
126
|
404: {
|
|
123
127
|
content: { "application/json": { schema: errorSchema } },
|
|
124
|
-
description: "No
|
|
128
|
+
description: "No credentials stored for this provider",
|
|
125
129
|
},
|
|
126
130
|
},
|
|
127
131
|
});
|
|
@@ -180,8 +184,13 @@ export const providerCredentialsRouter = new OpenAPIHono<AppEnv>()
|
|
|
180
184
|
|
|
181
185
|
// Never decrypts — DELETE must succeed even when the payload is
|
|
182
186
|
// undecryptable (the operator's escape hatch after a secret rotation).
|
|
183
|
-
|
|
184
|
-
|
|
187
|
+
// Disconnect purges BOTH kinds (oauth grant + derived config) so the
|
|
188
|
+
// minted webhook secret + grabbed phc_ never linger orphaned.
|
|
189
|
+
const { oauth, derived } = await deleteAllProviderCredentials(
|
|
190
|
+
db,
|
|
191
|
+
providerId,
|
|
192
|
+
);
|
|
193
|
+
if (!oauth && !derived) {
|
|
185
194
|
return c.json({ error: "Provider credential not found" }, 404);
|
|
186
195
|
}
|
|
187
196
|
return c.json({ deleted: true }, 200);
|
|
@@ -52,6 +52,16 @@ async function resolveStoredPosthogSecret(
|
|
|
52
52
|
return value;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Drop the module-level stored-secret cache so the next inbound PostHog webhook
|
|
57
|
+
* re-reads from the `kind="derived"` store. Called right after `hogsend connect`
|
|
58
|
+
* mints + persists a secret, so the freshly-minted value is enforced
|
|
59
|
+
* immediately instead of waiting out the `STORED_SECRET_RECHECK_MS` window.
|
|
60
|
+
*/
|
|
61
|
+
export function invalidateStoredPosthogSecret(): void {
|
|
62
|
+
storedPosthogSecret = undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
55
65
|
export function registerWebhookSourceRoutes(
|
|
56
66
|
app: OpenAPIHono<AppEnv>,
|
|
57
67
|
sources: DefinedWebhookSource[],
|