@authhero/cloudflare-adapter 3.2.5 → 3.4.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/cloudflare-adapter.cjs +1 -1
- package/dist/cloudflare-adapter.mjs +1 -1
- package/dist/{sync-defaults-errors-BlLZh6Gt.mjs → sync-defaults-errors-CKdx0nzD.mjs} +4 -2
- package/dist/{sync-defaults-errors-Htp1NWTq.js → sync-defaults-errors-pnzkP88r.js} +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/dist/types/wfp/dispatch-detach.d.ts +31 -0
- package/dist/types/wfp/dispatch-sync-config.d.ts +37 -0
- package/dist/types/wfp/dispatch-tenant-member-requests.d.ts +23 -0
- package/dist/types/wfp/index.d.ts +3 -0
- package/dist/types/wfp/tenant-app.d.ts +26 -1
- package/dist/types/workflows/detach-operation.d.ts +31 -0
- package/dist/types/workflows/entrypoint.example.d.ts +63 -3
- package/dist/types/workflows/index.d.ts +2 -0
- package/dist/types/workflows/sync-defaults-operation.d.ts +28 -0
- package/dist/types/workflows/types.d.ts +2 -2
- package/dist/wfp.cjs +1 -1
- package/dist/wfp.d.ts +114 -3
- package/dist/wfp.mjs +303 -61
- package/dist/workflows.cjs +1 -1
- package/dist/workflows.d.ts +62 -5
- package/dist/workflows.mjs +145 -41
- package/package.json +5 -5
package/dist/wfp.mjs
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { applyControlPlaneDefaultsPayload as e,
|
|
2
|
-
import { Hono as
|
|
3
|
-
import { SigningCertificateRenewalError as
|
|
1
|
+
import { applyControlPlaneDefaultsPayload as e, applyResolvedConfigBlock as t, assertDetachEligible as n, buildControlPlaneDefaultsPayload as r, buildResolvedConfigBlock as i, createShadowReporter as a, detachTenantRows as o, emptyDetachResult as s, markTenantDetached as c, withRuntimeFallback as l } from "@authhero/multi-tenancy";
|
|
2
|
+
import { Hono as u } from "hono";
|
|
3
|
+
import { SigningCertificateRenewalError as d, TENANT_URI_PREFIX as f, createEncryptedDataAdapterWithKeyRing as p, ensureSigningKey as m, init as h, loadEncryptionKey as g, renewSigningCertificates as _ } from "authhero";
|
|
4
4
|
//#region src/wfp/dispatch-sync-defaults.ts
|
|
5
|
-
var
|
|
6
|
-
function
|
|
5
|
+
var v = "tenant-{tenant_id}-auth", y = 3e4, b = "/internal/sync-defaults";
|
|
6
|
+
function x(e, t) {
|
|
7
7
|
return e.replace(/\{tenant_id\}/g, t);
|
|
8
8
|
}
|
|
9
|
-
function
|
|
10
|
-
let { dispatcher:
|
|
9
|
+
function S(e) {
|
|
10
|
+
let { dispatcher: t, scriptNameTemplate: n = v, internalSecret: i, controlPlaneTenantId: a, controlPlaneAdapters: o, entities: s, timeoutMs: c = y } = e;
|
|
11
11
|
return async (e) => {
|
|
12
|
-
let l = await
|
|
12
|
+
let l = await r(o, a, s, e), u = x(n, e), d = new AbortController(), f = setTimeout(() => d.abort(), c), p;
|
|
13
13
|
try {
|
|
14
|
-
|
|
14
|
+
p = await t.get(u).fetch(`https://tenant.internal${b}`, {
|
|
15
15
|
method: "POST",
|
|
16
16
|
headers: {
|
|
17
17
|
"content-type": "application/json",
|
|
@@ -23,22 +23,152 @@ function h(e) {
|
|
|
23
23
|
} finally {
|
|
24
24
|
clearTimeout(f);
|
|
25
25
|
}
|
|
26
|
+
if (!p.ok) {
|
|
27
|
+
let e = await p.text().catch(() => ""), t = p.headers.get("x-authhero-error");
|
|
28
|
+
throw Error(`sync-defaults push to "${u}" failed: ${p.status}${t ? ` (${t})` : ""} ${e.slice(0, 256)}`);
|
|
29
|
+
}
|
|
30
|
+
return await p.json();
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/wfp/dispatch-sync-config.ts
|
|
35
|
+
var C = "/internal/sync-config";
|
|
36
|
+
function w(e) {
|
|
37
|
+
let { dispatcher: t, scriptNameTemplate: n = v, internalSecret: r, controlPlaneTenantId: a, controlPlaneAdapters: o, maxDepth: s, timeoutMs: c = y } = e;
|
|
38
|
+
return async (e, { dryRun: l, force: u, prune: d }) => {
|
|
39
|
+
if ((await o.tenants.get(e))?.config_sync === "detached") throw Error(`Tenant "${e}" is detached from its parent: set config_sync back to "inherit-on-read" or "synced" before syncing it`);
|
|
40
|
+
let f = await i(o, e, {
|
|
41
|
+
legacyControlPlane: a,
|
|
42
|
+
maxDepth: s
|
|
43
|
+
}), p = x(n, e), m = new AbortController(), h = setTimeout(() => m.abort(), c), g;
|
|
44
|
+
try {
|
|
45
|
+
g = await t.get(p).fetch(`https://tenant.internal${C}`, {
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: {
|
|
48
|
+
"content-type": "application/json",
|
|
49
|
+
authorization: `Bearer ${r}`
|
|
50
|
+
},
|
|
51
|
+
body: JSON.stringify({
|
|
52
|
+
tenantId: e,
|
|
53
|
+
resolved: f,
|
|
54
|
+
dryRun: l,
|
|
55
|
+
force: u,
|
|
56
|
+
prune: d
|
|
57
|
+
}),
|
|
58
|
+
signal: m.signal
|
|
59
|
+
});
|
|
60
|
+
} finally {
|
|
61
|
+
clearTimeout(h);
|
|
62
|
+
}
|
|
63
|
+
if (!g.ok) {
|
|
64
|
+
let e = await g.text().catch(() => ""), t = g.headers.get("x-authhero-error");
|
|
65
|
+
throw Error(`sync-config push to "${p}" failed: ${g.status}${t ? ` (${t})` : ""} ${e.slice(0, 256)}`);
|
|
66
|
+
}
|
|
67
|
+
return await g.json();
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/wfp/dispatch-detach.ts
|
|
72
|
+
var T = "/internal/detach", E = 12e4;
|
|
73
|
+
function D(e) {
|
|
74
|
+
let { dispatcher: t, scriptNameTemplate: r = v, internalSecret: i, controlPlaneAdapters: a, timeoutMs: o = E } = e;
|
|
75
|
+
return async (e, { dryRun: l, clean: u }) => {
|
|
76
|
+
let d = await a.tenants.get(e);
|
|
77
|
+
if (!d) throw Error(`Tenant "${e}" not found`);
|
|
78
|
+
if (d.config_sync === "detached") return s("already-detached");
|
|
79
|
+
n(e, d);
|
|
80
|
+
let f = x(r, e), p = new AbortController(), m = setTimeout(() => p.abort(), o), h;
|
|
81
|
+
try {
|
|
82
|
+
h = await t.get(f).fetch(`https://tenant.internal${T}`, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: {
|
|
85
|
+
"content-type": "application/json",
|
|
86
|
+
authorization: `Bearer ${i}`
|
|
87
|
+
},
|
|
88
|
+
body: JSON.stringify({
|
|
89
|
+
tenantId: e,
|
|
90
|
+
dryRun: l,
|
|
91
|
+
clean: u
|
|
92
|
+
}),
|
|
93
|
+
signal: p.signal
|
|
94
|
+
});
|
|
95
|
+
} finally {
|
|
96
|
+
clearTimeout(m);
|
|
97
|
+
}
|
|
26
98
|
if (!h.ok) {
|
|
27
99
|
let e = await h.text().catch(() => ""), t = h.headers.get("x-authhero-error");
|
|
28
|
-
throw Error(`
|
|
100
|
+
throw Error(`detach push to "${f}" failed: ${h.status}${t ? ` (${t})` : ""} ${e.slice(0, 256)}`);
|
|
101
|
+
}
|
|
102
|
+
let g = await h.json();
|
|
103
|
+
return !l && g.status === "detached" && await c(a, e), g;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/wfp/dispatch-tenant-member-requests.ts
|
|
108
|
+
var O = "/internal/tenant-member-requests", k = 15e3;
|
|
109
|
+
function A(e) {
|
|
110
|
+
let { dispatcher: t, scriptNameTemplate: n = v, internalSecret: r, timeoutMs: i = k } = e;
|
|
111
|
+
async function a(e, a) {
|
|
112
|
+
let o = x(n, e), s = new AbortController(), c = setTimeout(() => s.abort(), i);
|
|
113
|
+
try {
|
|
114
|
+
return await t.get(o).fetch(`https://tenant.internal${O}`, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: {
|
|
117
|
+
"content-type": "application/json",
|
|
118
|
+
authorization: `Bearer ${r}`
|
|
119
|
+
},
|
|
120
|
+
body: JSON.stringify({
|
|
121
|
+
tenantId: e,
|
|
122
|
+
...a
|
|
123
|
+
}),
|
|
124
|
+
signal: s.signal
|
|
125
|
+
});
|
|
126
|
+
} finally {
|
|
127
|
+
clearTimeout(c);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async function o(e, t) {
|
|
131
|
+
let n = await t.text().catch(() => ""), r = t.headers.get("x-authhero-error");
|
|
132
|
+
return /* @__PURE__ */ Error(`tenant member requests of "${e}" answered ${t.status}${r ? ` (${r})` : ""} ${n.slice(0, 256)}`);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
async get(e, t) {
|
|
136
|
+
let n = await a(e, {
|
|
137
|
+
action: "get",
|
|
138
|
+
id: t
|
|
139
|
+
});
|
|
140
|
+
if (n.status === 404) return null;
|
|
141
|
+
if (!n.ok) throw await o(e, n);
|
|
142
|
+
return await n.json();
|
|
143
|
+
},
|
|
144
|
+
async report(e, t, n) {
|
|
145
|
+
let r = await a(e, {
|
|
146
|
+
action: "report",
|
|
147
|
+
id: t,
|
|
148
|
+
outcome: n
|
|
149
|
+
});
|
|
150
|
+
if (!r.ok) throw await o(e, r);
|
|
151
|
+
return (await r.json()).updated;
|
|
152
|
+
},
|
|
153
|
+
async listPending(e, { olderThan: t }) {
|
|
154
|
+
let n = await a(e, {
|
|
155
|
+
action: "list_pending",
|
|
156
|
+
olderThan: t
|
|
157
|
+
});
|
|
158
|
+
if (!n.ok) throw await o(e, n);
|
|
159
|
+
return (await n.json()).requests;
|
|
29
160
|
}
|
|
30
|
-
return await h.json();
|
|
31
161
|
};
|
|
32
162
|
}
|
|
33
163
|
//#endregion
|
|
34
164
|
//#region src/wfp/dispatch-renew-signing-certificates.ts
|
|
35
|
-
var
|
|
36
|
-
function
|
|
37
|
-
let { dispatcher: t, scriptNameTemplate: n =
|
|
165
|
+
var j = "/internal/renew-signing-certificates";
|
|
166
|
+
function M(e) {
|
|
167
|
+
let { dispatcher: t, scriptNameTemplate: n = v, internalSecret: r, timeoutMs: i = y } = e;
|
|
38
168
|
return async (e) => {
|
|
39
|
-
let a =
|
|
169
|
+
let a = x(n, e), o = new AbortController(), s = setTimeout(() => o.abort(), i);
|
|
40
170
|
try {
|
|
41
|
-
let e = await t.get(a).fetch(`https://tenant.internal${
|
|
171
|
+
let e = await t.get(a).fetch(`https://tenant.internal${j}`, {
|
|
42
172
|
method: "POST",
|
|
43
173
|
headers: { authorization: `Bearer ${r}` },
|
|
44
174
|
signal: o.signal
|
|
@@ -48,7 +178,7 @@ function _(e) {
|
|
|
48
178
|
throw Error(`signing-certificate renewal for "${a}" failed: ${e.status}${n ? ` (${n})` : ""} ${t.slice(0, 512)}`);
|
|
49
179
|
}
|
|
50
180
|
let n = await e.json();
|
|
51
|
-
if (!
|
|
181
|
+
if (!N(n)) throw Error(`signing-certificate renewal for "${a}" returned an unexpected body`);
|
|
52
182
|
return {
|
|
53
183
|
renewed: n.renewed,
|
|
54
184
|
notDue: n.notDue
|
|
@@ -58,13 +188,13 @@ function _(e) {
|
|
|
58
188
|
}
|
|
59
189
|
};
|
|
60
190
|
}
|
|
61
|
-
function
|
|
191
|
+
function N(e) {
|
|
62
192
|
return typeof e == "object" && !!e && "renewed" in e && "notDue" in e && Array.isArray(e.renewed) && e.renewed.every((e) => typeof e == "string") && typeof e.notDue == "number";
|
|
63
193
|
}
|
|
64
194
|
//#endregion
|
|
65
195
|
//#region src/wfp/tenant-app.ts
|
|
66
|
-
var
|
|
67
|
-
function
|
|
196
|
+
var P = "cp", F = "/internal/sync-defaults", I = "/internal/sync-config", L = "/internal/detach", R = "/internal/tenant-member-requests", z = "/internal/renew-signing-certificates";
|
|
197
|
+
function B(e, t, n, r) {
|
|
68
198
|
console.error(`[wfp-tenant] ${t}:`, n);
|
|
69
199
|
let i = n instanceof Error ? n.message : String(n);
|
|
70
200
|
return e.header("X-Authhero-Error", t), e.json({
|
|
@@ -73,74 +203,164 @@ function S(e, t, n, r) {
|
|
|
73
203
|
result: r
|
|
74
204
|
}, 500);
|
|
75
205
|
}
|
|
76
|
-
function
|
|
77
|
-
let t = `${
|
|
206
|
+
function V(e) {
|
|
207
|
+
let t = `${f}${e.tenantId}`;
|
|
78
208
|
return {
|
|
79
209
|
issuer: e.issuer,
|
|
80
210
|
validityDays: e.validityDays,
|
|
81
211
|
subjectUri: () => t
|
|
82
212
|
};
|
|
83
213
|
}
|
|
84
|
-
function
|
|
214
|
+
function H(e) {
|
|
85
215
|
return ({ tenant_id: t }) => t === e.CONTROL_PLANE_TENANT_ID ? [e.CONTROL_PLANE_ISSUER] : [];
|
|
86
216
|
}
|
|
87
|
-
async function
|
|
88
|
-
let
|
|
89
|
-
default:
|
|
90
|
-
keys: { [
|
|
91
|
-
} : { default:
|
|
92
|
-
dataAdapter:
|
|
93
|
-
...
|
|
94
|
-
additionalIssuers: (
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
let
|
|
98
|
-
if (!
|
|
99
|
-
let
|
|
217
|
+
async function U(n, r) {
|
|
218
|
+
let i = n.CONTROL_PLANE_TENANT_ID, s = await g(n.ENCRYPTION_KEY), f = n.CONTROL_PLANE_ENCRYPTION_KEY ? {
|
|
219
|
+
default: s,
|
|
220
|
+
keys: { [P]: await g(n.CONTROL_PLANE_ENCRYPTION_KEY) }
|
|
221
|
+
} : { default: s }, v = r.createDataAdapter(n), y = p(v, f, { resolveEncryptKeyId: (e) => e === i ? P : void 0 }), b = l(y, { controlPlaneTenantId: i }), x = r.signingCertificateAuthority?.(n), S = x ? V(x) : void 0, C = {
|
|
222
|
+
dataAdapter: b,
|
|
223
|
+
...S ? { signingCertificateAuthority: S } : {},
|
|
224
|
+
additionalIssuers: (r.additionalIssuers ?? H)(n)
|
|
225
|
+
}, w = r.configure ? r.configure(C, n) : C, T = w.signingCertificateAuthority, { app: E } = h(w), D = new u();
|
|
226
|
+
if (D.post(F, async (t) => {
|
|
227
|
+
let a = n.WFP_INTERNAL_SYNC_SECRET, o = t.req.header("authorization");
|
|
228
|
+
if (!a || o !== `Bearer ${a}`) return t.json({ error: "unauthorized" }, 401);
|
|
229
|
+
let s;
|
|
100
230
|
try {
|
|
101
|
-
|
|
231
|
+
s = await t.req.json();
|
|
102
232
|
} catch {
|
|
103
|
-
return
|
|
233
|
+
return t.json({ error: "invalid JSON" }, 400);
|
|
104
234
|
}
|
|
105
235
|
let c;
|
|
106
236
|
try {
|
|
107
|
-
c = await e(
|
|
237
|
+
c = await e(s, y, i, { continueOnError: r.continueOnError ?? !0 });
|
|
108
238
|
} catch (e) {
|
|
109
|
-
return
|
|
239
|
+
return B(t, "sync_defaults_apply_failed", e);
|
|
110
240
|
}
|
|
111
241
|
let l;
|
|
112
242
|
try {
|
|
113
|
-
l = { created: (await
|
|
114
|
-
name:
|
|
115
|
-
certificateAuthority:
|
|
243
|
+
l = { created: (await m(y.keys, {
|
|
244
|
+
name: n.ISSUER,
|
|
245
|
+
certificateAuthority: T
|
|
116
246
|
})).created };
|
|
117
247
|
} catch (e) {
|
|
118
|
-
return
|
|
248
|
+
return B(t, "sync_defaults_signing_key_failed", e, c);
|
|
119
249
|
}
|
|
120
|
-
if (
|
|
121
|
-
await
|
|
250
|
+
if (r.onSyncResult) try {
|
|
251
|
+
await r.onSyncResult(c, n);
|
|
122
252
|
} catch (e) {
|
|
123
|
-
return
|
|
253
|
+
return B(t, "sync_defaults_on_result_failed", e, c);
|
|
124
254
|
}
|
|
125
|
-
return
|
|
255
|
+
return t.json({
|
|
126
256
|
...c,
|
|
127
257
|
signingKey: l
|
|
128
258
|
});
|
|
129
|
-
}),
|
|
130
|
-
let
|
|
131
|
-
if (!
|
|
132
|
-
|
|
259
|
+
}), D.post(I, async (e) => {
|
|
260
|
+
let i = n.WFP_INTERNAL_SYNC_SECRET, a = e.req.header("authorization");
|
|
261
|
+
if (!i || a !== `Bearer ${i}`) return e.json({ error: "unauthorized" }, 401);
|
|
262
|
+
let o;
|
|
263
|
+
try {
|
|
264
|
+
o = await e.req.json();
|
|
265
|
+
} catch {
|
|
266
|
+
return e.json({ error: "invalid JSON" }, 400);
|
|
267
|
+
}
|
|
268
|
+
if (!o.tenantId || !o.resolved) return e.json({ error: "tenantId and resolved are required" }, 400);
|
|
269
|
+
if (n.TENANT_ID && o.tenantId !== n.TENANT_ID) return console.error(`[wfp-tenant] sync_config_tenant_mismatch: push for "${o.tenantId}" reached the worker of "${n.TENANT_ID}"`), e.header("X-Authhero-Error", "sync_config_tenant_mismatch"), e.json({ error: "sync_config_tenant_mismatch" }, 403);
|
|
270
|
+
try {
|
|
271
|
+
let n = await t(o.resolved, v, o.tenantId, {
|
|
272
|
+
dryRun: o.dryRun === !0,
|
|
273
|
+
force: o.force === !0,
|
|
274
|
+
prune: o.prune === !0,
|
|
275
|
+
keyRing: f,
|
|
276
|
+
continueOnError: r.continueOnError ?? !0
|
|
277
|
+
});
|
|
278
|
+
return e.json(n);
|
|
279
|
+
} catch (t) {
|
|
280
|
+
return t instanceof Error && /key ring cannot decrypt/.test(t.message) ? (console.error("[wfp-tenant] sync_config_key_ring:", t), e.header("X-Authhero-Error", "sync_config_key_ring"), e.json({
|
|
281
|
+
error: "sync_config_key_ring",
|
|
282
|
+
detail: t.message
|
|
283
|
+
}, 422)) : B(e, "sync_config_apply_failed", t);
|
|
284
|
+
}
|
|
285
|
+
}), D.post(L, async (e) => {
|
|
286
|
+
let t = n.WFP_INTERNAL_SYNC_SECRET, r = e.req.header("authorization");
|
|
287
|
+
if (!t || r !== `Bearer ${t}`) return e.json({ error: "unauthorized" }, 401);
|
|
288
|
+
let i;
|
|
133
289
|
try {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
290
|
+
i = await e.req.json();
|
|
291
|
+
} catch {
|
|
292
|
+
return e.json({ error: "invalid JSON" }, 400);
|
|
293
|
+
}
|
|
294
|
+
if (!i.tenantId) return e.json({ error: "tenantId is required" }, 400);
|
|
295
|
+
if (n.TENANT_ID && i.tenantId !== n.TENANT_ID) return console.error(`[wfp-tenant] detach_tenant_mismatch: detach for "${i.tenantId}" reached the worker of "${n.TENANT_ID}"`), e.header("X-Authhero-Error", "detach_tenant_mismatch"), e.json({ error: "detach_tenant_mismatch" }, 403);
|
|
296
|
+
try {
|
|
297
|
+
let t = await o(v, i.tenantId, {
|
|
298
|
+
dryRun: i.dryRun === !0,
|
|
299
|
+
clean: i.clean === !0
|
|
300
|
+
});
|
|
301
|
+
return t.status === "detached" && await c(v, i.tenantId), e.json(t);
|
|
302
|
+
} catch (t) {
|
|
303
|
+
return B(e, "detach_failed", t);
|
|
304
|
+
}
|
|
305
|
+
}), D.post(R, async (e) => {
|
|
306
|
+
let t = n.WFP_INTERNAL_SYNC_SECRET, r = e.req.header("authorization");
|
|
307
|
+
if (!t || r !== `Bearer ${t}`) return e.json({ error: "unauthorized" }, 401);
|
|
308
|
+
let i;
|
|
309
|
+
try {
|
|
310
|
+
i = await e.req.json();
|
|
311
|
+
} catch {
|
|
312
|
+
return e.json({ error: "invalid JSON" }, 400);
|
|
313
|
+
}
|
|
314
|
+
if (!i.tenantId) return e.json({ error: "tenantId is required" }, 400);
|
|
315
|
+
if (n.TENANT_ID && i.tenantId !== n.TENANT_ID) return console.error(`[wfp-tenant] member_requests_tenant_mismatch: a request for "${i.tenantId}" reached the worker of "${n.TENANT_ID}"`), e.header("X-Authhero-Error", "member_requests_tenant_mismatch"), e.json({ error: "member_requests_tenant_mismatch" }, 403);
|
|
316
|
+
let a = v.tenantMemberRequests;
|
|
317
|
+
if (!a) return e.json({ error: "tenant member requests are not available" }, 501);
|
|
318
|
+
try {
|
|
319
|
+
switch (i.action) {
|
|
320
|
+
case "get": {
|
|
321
|
+
if (!i.id) return e.json({ error: "id is required" }, 400);
|
|
322
|
+
let t = await a.get(i.tenantId, i.id);
|
|
323
|
+
return t ? e.json(t) : e.json({ error: "not_found" }, 404);
|
|
324
|
+
}
|
|
325
|
+
case "report": {
|
|
326
|
+
let { status: t, result: n, error: r } = i.outcome ?? {};
|
|
327
|
+
if (!i.id || t !== "succeeded" && t !== "failed") return e.json({ error: "id and a final status are required" }, 400);
|
|
328
|
+
let o = await a.update(i.tenantId, i.id, {
|
|
329
|
+
status: t,
|
|
330
|
+
result: n ?? null,
|
|
331
|
+
error: r ?? null,
|
|
332
|
+
finished_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
333
|
+
}, "pending");
|
|
334
|
+
return e.json({ updated: o });
|
|
335
|
+
}
|
|
336
|
+
case "list_pending": {
|
|
337
|
+
let t = await a.list(i.tenantId, {
|
|
338
|
+
status: "pending",
|
|
339
|
+
created_before: i.olderThan,
|
|
340
|
+
per_page: 100
|
|
341
|
+
});
|
|
342
|
+
return e.json({ requests: t.requests });
|
|
343
|
+
}
|
|
344
|
+
default: return e.json({ error: "unknown action" }, 400);
|
|
345
|
+
}
|
|
346
|
+
} catch (t) {
|
|
347
|
+
return B(e, "member_requests_failed", t);
|
|
348
|
+
}
|
|
349
|
+
}), D.post(z, async (e) => {
|
|
350
|
+
let t = n.WFP_INTERNAL_SYNC_SECRET, r = e.req.header("authorization");
|
|
351
|
+
if (!t || r !== `Bearer ${t}`) return e.json({ error: "unauthorized" }, 401);
|
|
352
|
+
if (!T) return e.json({ error: "no signing certificate authority" }, 404);
|
|
353
|
+
try {
|
|
354
|
+
let t = await _({
|
|
355
|
+
dataAdapter: { keys: y.keys },
|
|
356
|
+
certificateAuthority: T
|
|
137
357
|
});
|
|
138
358
|
return e.json({
|
|
139
359
|
renewed: t.renewed,
|
|
140
360
|
notDue: t.notDue
|
|
141
361
|
});
|
|
142
362
|
} catch (t) {
|
|
143
|
-
return t instanceof
|
|
363
|
+
return t instanceof d ? (console.error("[wfp-tenant] signing_certificate_renewal_failed:", t), e.header("X-Authhero-Error", "signing_certificate_renewal_failed"), e.json({
|
|
144
364
|
error: "signing_certificate_renewal_failed",
|
|
145
365
|
renewed: t.result.renewed,
|
|
146
366
|
notDue: t.result.notDue,
|
|
@@ -148,16 +368,38 @@ async function T(t, a) {
|
|
|
148
368
|
kid: e,
|
|
149
369
|
detail: t instanceof Error ? t.message : String(t)
|
|
150
370
|
}))
|
|
151
|
-
}, 500)) :
|
|
371
|
+
}, 500)) : B(e, "signing_certificate_renewal_failed", t);
|
|
152
372
|
}
|
|
153
|
-
}),
|
|
373
|
+
}), D.route("/", E), r.inheritanceShadow) {
|
|
374
|
+
let e = a(y, r.inheritanceShadow === !0 ? {} : r.inheritanceShadow), t = n.TENANT_ID || new URL(n.ISSUER).hostname.split(".")[0], o, s = async () => {
|
|
375
|
+
let e = await y.tenants.get(t);
|
|
376
|
+
for (let t = 0; t < 5; t++) {
|
|
377
|
+
let t = e?.parent_tenant_id;
|
|
378
|
+
if (!t || t === i) return !0;
|
|
379
|
+
if (e = await y.tenants.get(t), !e) return !1;
|
|
380
|
+
}
|
|
381
|
+
return !1;
|
|
382
|
+
};
|
|
383
|
+
W.set(D, (n) => {
|
|
384
|
+
if (!new URL(n.url).pathname.startsWith("/internal/") && t !== i) return (async () => {
|
|
385
|
+
if (!await (o ??= s())) return;
|
|
386
|
+
let n = e.schedule(t, i);
|
|
387
|
+
n && await n;
|
|
388
|
+
})();
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
return D;
|
|
154
392
|
}
|
|
155
|
-
|
|
393
|
+
var W = /* @__PURE__ */ new WeakMap();
|
|
394
|
+
function G(e) {
|
|
156
395
|
let t = /* @__PURE__ */ new WeakMap();
|
|
157
396
|
return { fetch(n, r, i) {
|
|
158
397
|
let a = t.get(r);
|
|
159
|
-
return a || (a =
|
|
398
|
+
return a || (a = U(r, e), t.set(r, a)), a.then(async (e) => {
|
|
399
|
+
let t = await e.fetch(n, r, i), a = W.get(e)?.(n);
|
|
400
|
+
return a && i.waitUntil(a), t;
|
|
401
|
+
});
|
|
160
402
|
} };
|
|
161
403
|
}
|
|
162
404
|
//#endregion
|
|
163
|
-
export {
|
|
405
|
+
export { O as TENANT_MEMBER_REQUESTS_PATH, D as createDispatchDetach, M as createDispatchRenewSigningCertificates, w as createDispatchSyncConfig, S as createDispatchSyncDefaults, A as createDispatchTenantMemberRequestStore, G as createWfpTenantApp };
|
package/dist/workflows.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./sync-defaults-errors-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./sync-defaults-errors-pnzkP88r.js");let t=require("@authhero/multi-tenancy");var n={retries:{limit:5,delay:`5 seconds`,backoff:`exponential`},timeout:`2 minutes`},r={retries:{limit:8,delay:`10 seconds`,backoff:`exponential`},timeout:`1 minute`};function i(e){return e instanceof Error?e.message:String(e)}async function a(a,o,s){let c=(0,t.createOperationRecorder)(a.stores),l=a.shouldProvision??(e=>e.deployment_type===`wfp`),u=e=>a.stepConfig?.[e]??(e===`verify`?r:n),d=o.operation_id,f=o.tenant_id,p=await s.do(`mark-running`,u(`mark-running`),async()=>{let e=await a.tenants.get(f);if(!e||!l(e))return await c.appendEvent(d,{step:`mark-running`,outcome:`skipped`,detail:{reason:e?`tenant is not WFP-provisioned`:`tenant row missing (rolled back?)`}}),await c.markSucceeded(d),{skipped:!0,scriptName:``,databaseName:``};let t=a.steps.validate(),n=a.steps.names(f);return await c.markRunning(d,`mark-running`),await c.appendEvent(d,{step:`mark-running`,outcome:`succeeded`}),{skipped:!1,...n,...t}});if(p.skipped)return;let m={worker_script_name:p.scriptName,bundle_configuration:p.bundleConfiguration,worker_version:p.workerVersion,database_version:p.databaseVersion};try{let t=await s.do(`create-database`,u(`create-database`),async()=>{await c.setCurrentStep(d,`create-database`);let{id:e,created:t}=await a.steps.findOrCreateDatabase(p.databaseName);return await c.appendEvent(d,{step:`create-database`,outcome:`succeeded`,detail:{database_id:e,created:t}}),{databaseId:e,created:t}});if(m.d1_database_id=t.databaseId,await s.do(`apply-migrations`,u(`apply-migrations`),async()=>{await c.setCurrentStep(d,`apply-migrations`),await a.steps.applyMigrations(t.databaseId,t.created),await c.appendEvent(d,{step:`apply-migrations`,outcome:`succeeded`,detail:{database_version:p.databaseVersion}})}),await s.do(`upload-script`,u(`upload-script`),async()=>{await c.setCurrentStep(d,`upload-script`),await a.steps.uploadScript(p.scriptName,t.databaseId),await c.appendEvent(d,{step:`upload-script`,outcome:`succeeded`,detail:{worker_version:p.workerVersion}})}),await s.do(`upload-secrets`,u(`upload-secrets`),async()=>{await c.setCurrentStep(d,`upload-secrets`),await a.steps.uploadSecrets(p.scriptName,f),await c.appendEvent(d,{step:`upload-secrets`,outcome:`succeeded`})}),a.syncDefaults){let t=a.syncDefaults;await s.do(`seed-defaults`,u(`seed-defaults`),async()=>{await c.setCurrentStep(d,`seed-defaults`);let n=e.t(await t(f));if(n.length>0)throw Error(`sync-defaults seed reported ${n.length} error(s): ${n.join(`; `)}`);await c.appendEvent(d,{step:`seed-defaults`,outcome:`succeeded`})})}if(a.verify){let e=a.verify;await s.do(`verify`,u(`verify`),async()=>{await c.setCurrentStep(d,`verify`),await e(t.databaseId,f),await c.appendEvent(d,{step:`verify`,outcome:`succeeded`})})}await s.do(`mark-ready`,u(`mark-ready`),async()=>{await c.setCurrentStep(d,`mark-ready`),await a.tenants.update(f,{...m,provisioning_state:`ready`,provisioning_error:void 0,provisioning_state_changed_at:new Date().toISOString()}),await c.appendEvent(d,{step:`mark-ready`,outcome:`succeeded`}),await c.markSucceeded(d)})}catch(e){throw await s.do(`mark-failed`,u(`mark-failed`),async()=>{let t=i(e);try{await a.tenants.update(f,{...m,provisioning_state:`failed`,provisioning_error:t.slice(0,2048),provisioning_state_changed_at:new Date().toISOString()})}catch(e){a.logger?.warn(`Failed to write provisioning_state="failed" for tenant ${f}:`,e)}await c.appendEvent(d,{step:`mark-failed`,outcome:`failed`,detail:{message:t}}),await c.markFailed(d,e)}),e}}var o={retries:{limit:3,delay:`5 seconds`,backoff:`exponential`},timeout:`2 minutes`};function s(e){return e instanceof Error?e.message:String(e)}async function c(e,n,r){let i=(0,t.createOperationRecorder)(e.stores),a=t=>e.stepConfig?.[t]??o,c=n.operation_id;try{await r.do(`mark-running`,a(`mark-running`),async()=>{await i.markRunning(c,`sync-defaults`)}),await r.do(`sync-defaults`,a(`sync-defaults`),async()=>{await i.setCurrentStep(c,`sync-defaults`);let n=await e.stores.tenantOperations.get(c);if(!n)throw Error(`Operation "${c}" not found`);let r=await(0,t.runSyncDefaultsStep)({operation:n,syncConfig:e.syncConfig,recorder:i});await i.appendEvent(c,{step:`sync-defaults`,outcome:`succeeded`,detail:r})}),await r.do(`mark-succeeded`,a(`mark-succeeded`),async()=>{await i.markSucceeded(c)})}catch(e){throw await r.do(`mark-failed`,a(`mark-failed`),async()=>{await i.appendEvent(c,{step:`mark-failed`,outcome:`failed`,detail:{message:s(e)}}),await i.markFailed(c,e)}),e}}var l={retries:{limit:3,delay:`5 seconds`,backoff:`exponential`},timeout:`2 minutes`},u={retries:{limit:0,delay:`5 seconds`},timeout:`5 minutes`};function d(e){return e instanceof Error?e.message:String(e)}async function f(e,n,r){let i=(0,t.createOperationRecorder)(e.stores),a=t=>e.stepConfig?.[t]??(t===`detach`?u:l),o=n.operation_id;try{await r.do(`mark-running`,a(`mark-running`),async()=>{await i.markRunning(o,`detach`)}),await r.do(`detach`,a(`detach`),async()=>{await i.setCurrentStep(o,`detach`);let n=await e.stores.tenantOperations.get(o);if(!n)throw Error(`Operation "${o}" not found`);let r=await(0,t.runDetachStep)({operation:n,detachTenant:e.detachTenant,recorder:i});await i.appendEvent(o,{step:`detach`,outcome:`succeeded`,detail:r})}),await r.do(`mark-succeeded`,a(`mark-succeeded`),async()=>{await i.markSucceeded(o)})}catch(e){throw await r.do(`mark-failed`,a(`mark-failed`),async()=>{await i.appendEvent(o,{step:`mark-failed`,outcome:`failed`,detail:{message:d(e)}}),await i.markFailed(o,e)}),e}}var p=class extends Error{checks;constructor(e,t){super(e),this.name=`TenantProvisionVerificationError`,this.checks=t}};function m(e,t){for(let n of e)for(let e of n.results??[])if(!(!e||typeof e!=`object`)){for(let[n,r]of Object.entries(e))if(n===t){if(typeof r==`number`)return r;if(typeof r==`string`&&r!==``&&!isNaN(Number(r)))return Number(r)}}return 0}function h(t){let n=t.minKeys??1;return async(r,i)=>{let a=await t.client.execD1(r,`SELECT (SELECT COUNT(*) FROM keys) AS key_count, (SELECT COUNT(*) FROM tenants WHERE id = '${e.r(i)}') AS tenant_count;`),o=m(a,`key_count`),s=m(a,`tenant_count`),c=[];if(o<n&&c.push(`expected at least ${n} signing key(s) in "keys", found ${o}`),s<1&&c.push(`tenant row "${i}" missing from "tenants"`),c.length>0)throw new p(`Tenant D1 verification failed for "${i}": ${c.join(`; `)}`,{keyCount:o,tenantRowCount:s})}}function g(e){let t=e instanceof Error?e.message:String(e??``);return/already exists|already been created|duplicate/i.test(t)}function _(e){return{engine:`cloudflare-workflows`,async start(n){if(n.kind!==`provision`&&n.kind!==`sync-defaults`&&n.kind!==`detach`)throw Error(`The Cloudflare Workflows executor does not support "${n.kind}" operations yet (it runs provision, sync-defaults and detach)`);if(!n.tenant_id)throw Error(`${n.kind} operations require a tenant_id`);let r=n.engine_instance_id??(0,t.buildEngineInstanceId)(n);try{await e.binding.create({id:r,params:{operation_id:n.id,tenant_id:n.tenant_id,kind:n.kind}})}catch(e){if(!g(e))throw e}}}}var v=`cloudflare-workflows`;async function y(e){let{stores:n,tenants:r,binding:i}=e,a=(0,t.createOperationRecorder)(n),o=e.minAgeMs??600*1e3,s=e.limit??100,c=new Date(Date.now()-o).toISOString(),{operations:l}=await n.tenantOperations.list({status:[`pending`,`running`],engine:v,updated_before:c,per_page:s}),u={scanned:l.length,markedFailed:0,markedSucceeded:0,stillRunning:0,instanceMissing:0,errors:0};for(let t of l)try{await f(t)}catch(n){u.errors++,e.logger?.warn(`Failed to reconcile tenant operation ${t.id}:`,n)}return u;async function d(e,t){await a.appendEvent(e.id,{step:e.current_step??`reconcile`,outcome:`reconciled`,detail:{resolution:`failed`,reason:t}}),await a.markFailed(e.id,t),e.kind===`provision`&&e.tenant_id&&(await r.get(e.tenant_id))?.provisioning_state===`pending`&&await r.update(e.tenant_id,{provisioning_state:`failed`,provisioning_error:t.slice(0,2048),provisioning_state_changed_at:new Date().toISOString()})}async function f(e){let o=await n.tenantOperations.get(e.id);if(!o||(0,t.isTerminalStatus)(o.status))return;let s=o.engine_instance_id??(0,t.buildEngineInstanceId)(o),c;try{c=await i.get(s)}catch{u.instanceMissing++,u.markedFailed++,await d(o,`engine instance "${s}" not found (expired past retention or never started)`);return}let l=await c.status();if(l.status===`errored`||l.status===`terminated`){let e=typeof l.error==`string`?l.error:l.error?.message??`engine reported ${l.status}`;u.markedFailed++,await d(o,e);return}if(l.status===`complete`){if(o.kind===`sync-defaults`||o.kind===`detach`){u.markedSucceeded++,await a.appendEvent(o.id,{step:o.current_step??`reconcile`,outcome:`reconciled`,detail:{resolution:`succeeded`}}),await a.markSucceeded(o.id);return}(o.tenant_id?await r.get(o.tenant_id):null)?.provisioning_state===`ready`?(u.markedSucceeded++,await a.appendEvent(o.id,{step:o.current_step??`reconcile`,outcome:`reconciled`,detail:{resolution:`succeeded`}}),await a.markSucceeded(o.id)):(u.markedFailed++,await d(o,`engine instance completed but the operation was never finalized and the tenant is not ready`));return}u.stillRunning++}}function b(e){let t=e.shouldProvision??(e=>e.deployment_type===`wfp`);return{async onProvision(n,r){let i=await e.tenants.get(n);i&&t(i)&&await e.enqueueOperation({kind:`provision`,tenant_id:n})},async onUpgrade(t,n){await e.inline.onUpgrade(t,n)},async onDeprovision(t){await e.inline.onDeprovision(t)}}}exports.TENANT_OPERATION_ENGINE=v,exports.TenantProvisionVerificationError=p,exports.createCloudflareWorkflowsExecutor=_,exports.createProvisionVerifier=h,exports.createWfpWorkflowProvisioningHook=b,exports.reconcileTenantOperations=y,exports.runDetachOperation=f,exports.runProvisionOperation=a,exports.runSyncDefaultsOperation=c;
|
package/dist/workflows.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TenantsDataAdapter, TenantOperation } from '@authhero/adapter-interfaces';
|
|
2
|
-
import { TenantOperationStores, StepConfig, StepRunner, TenantOperationExecutor } from '@authhero/multi-tenancy';
|
|
2
|
+
import { TenantOperationStores, StepConfig, StepRunner, SyncConfigFn, DetachConfigFn, TenantOperationExecutor } from '@authhero/multi-tenancy';
|
|
3
3
|
|
|
4
4
|
interface CfApiClientOptions {
|
|
5
5
|
accountId: string;
|
|
@@ -133,8 +133,8 @@ interface TenantProvisionerSteps {
|
|
|
133
133
|
interface TenantOperationWorkflowParams {
|
|
134
134
|
operation_id: string;
|
|
135
135
|
tenant_id: string;
|
|
136
|
-
/** Widened beyond
|
|
137
|
-
kind: "provision";
|
|
136
|
+
/** Widened beyond these in later phases (upgrade, backup, …). */
|
|
137
|
+
kind: "provision" | "sync-defaults" | "detach";
|
|
138
138
|
}
|
|
139
139
|
/**
|
|
140
140
|
* Instance status as reported by the engine. The exact value set has
|
|
@@ -220,6 +220,63 @@ interface ProvisionOperationDeps {
|
|
|
220
220
|
*/
|
|
221
221
|
declare function runProvisionOperation(deps: ProvisionOperationDeps, params: TenantOperationWorkflowParams, step: StepRunner): Promise<void>;
|
|
222
222
|
|
|
223
|
+
type SyncDefaultsStepName = "mark-running" | "sync-defaults" | "mark-succeeded" | "mark-failed";
|
|
224
|
+
interface SyncDefaultsOperationDeps {
|
|
225
|
+
/** Control-plane operation log stores. */
|
|
226
|
+
stores: TenantOperationStores;
|
|
227
|
+
/**
|
|
228
|
+
* How the tenant's resolved config reaches its database, for example
|
|
229
|
+
* `createDispatchSyncConfig(...)`.
|
|
230
|
+
*/
|
|
231
|
+
syncConfig: SyncConfigFn;
|
|
232
|
+
/** Per-step overrides of the retry/timeout defaults. */
|
|
233
|
+
stepConfig?: Partial<Record<SyncDefaultsStepName, StepConfig>>;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Durable `sync-defaults` operation: sync one tenant's resolved parent-chain
|
|
237
|
+
* config into its database as a Workflows instance, writing to the control-plane
|
|
238
|
+
* operation log at every step boundary (the same shape as
|
|
239
|
+
* {@link runProvisionOperation}, and the same step logic as the inline
|
|
240
|
+
* executor). The operation's `input` (`dry_run`, `force`) is read from its row,
|
|
241
|
+
* so nothing but ids crosses the Workflows boundary.
|
|
242
|
+
*
|
|
243
|
+
* A dry run records its diff in the operation's `result` and writes nothing to
|
|
244
|
+
* the tenant. Any failure runs `mark-failed` (event + failed row) and rethrows
|
|
245
|
+
* so the instance ends `errored`; if even that dies, the reconciler copies the
|
|
246
|
+
* engine's terminal state into the row.
|
|
247
|
+
*/
|
|
248
|
+
declare function runSyncDefaultsOperation(deps: SyncDefaultsOperationDeps, params: TenantOperationWorkflowParams, step: StepRunner): Promise<void>;
|
|
249
|
+
|
|
250
|
+
type DetachStepName = "mark-running" | "detach" | "mark-succeeded" | "mark-failed";
|
|
251
|
+
interface DetachOperationDeps {
|
|
252
|
+
/** Control-plane operation log stores. */
|
|
253
|
+
stores: TenantOperationStores;
|
|
254
|
+
/**
|
|
255
|
+
* How the detach reaches the tenant's database, for example
|
|
256
|
+
* `createDispatchDetach(...)`.
|
|
257
|
+
*/
|
|
258
|
+
detachTenant: DetachConfigFn;
|
|
259
|
+
/**
|
|
260
|
+
* Per-step overrides of the retry/timeout defaults. The `detach` step has no
|
|
261
|
+
* automatic retries unless a host sets them.
|
|
262
|
+
*/
|
|
263
|
+
stepConfig?: Partial<Record<DetachStepName, StepConfig>>;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Durable `detach` operation: a synced tenant leaves its parent, as a Workflows
|
|
267
|
+
* instance that writes to the control-plane operation log at every step
|
|
268
|
+
* boundary (the same shape as {@link runSyncDefaultsOperation}, and the same
|
|
269
|
+
* step logic as the inline executor). The operation's `input` (`dry_run`,
|
|
270
|
+
* `clean`) is read from its row, so nothing but ids crosses the Workflows
|
|
271
|
+
* boundary.
|
|
272
|
+
*
|
|
273
|
+
* A dry run records what would change in the operation's `result` and writes
|
|
274
|
+
* nothing. Any failure runs `mark-failed` (event + failed row) and rethrows
|
|
275
|
+
* so the instance ends `errored`; if even that dies, the reconciler copies the
|
|
276
|
+
* engine's terminal state into the row.
|
|
277
|
+
*/
|
|
278
|
+
declare function runDetachOperation(deps: DetachOperationDeps, params: TenantOperationWorkflowParams, step: StepRunner): Promise<void>;
|
|
279
|
+
|
|
223
280
|
interface ProvisionVerifierOptions {
|
|
224
281
|
client: CloudflareApiClient;
|
|
225
282
|
/** Minimum number of signing keys the tenant D1 must hold. Default 1. */
|
|
@@ -388,5 +445,5 @@ interface WfpWorkflowProvisioningHookOptions {
|
|
|
388
445
|
*/
|
|
389
446
|
declare function createWfpWorkflowProvisioningHook(options: WfpWorkflowProvisioningHookOptions): WfpTenantProvisioningHook;
|
|
390
447
|
|
|
391
|
-
export { TENANT_OPERATION_ENGINE, TenantProvisionVerificationError, createCloudflareWorkflowsExecutor, createProvisionVerifier, createWfpWorkflowProvisioningHook, reconcileTenantOperations, runProvisionOperation };
|
|
392
|
-
export type { CloudflareWorkflowsExecutorOptions, ProvisionOperationDeps, ProvisionStepName, ProvisionVerifierOptions, ReconcileTenantOperationsOptions, ReconcileTenantOperationsResult, TenantOperationWorkflowParams, WfpWorkflowProvisioningHookOptions, WorkflowInstanceHandle, WorkflowInstanceStatus, WorkflowsBinding };
|
|
448
|
+
export { TENANT_OPERATION_ENGINE, TenantProvisionVerificationError, createCloudflareWorkflowsExecutor, createProvisionVerifier, createWfpWorkflowProvisioningHook, reconcileTenantOperations, runDetachOperation, runProvisionOperation, runSyncDefaultsOperation };
|
|
449
|
+
export type { CloudflareWorkflowsExecutorOptions, DetachOperationDeps, DetachStepName, ProvisionOperationDeps, ProvisionStepName, ProvisionVerifierOptions, ReconcileTenantOperationsOptions, ReconcileTenantOperationsResult, SyncDefaultsOperationDeps, SyncDefaultsStepName, TenantOperationWorkflowParams, WfpWorkflowProvisioningHookOptions, WorkflowInstanceHandle, WorkflowInstanceStatus, WorkflowsBinding };
|