@authhero/cloudflare-adapter 2.35.2 → 2.36.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 +29 -29
- package/dist/cloudflare-adapter.d.ts +23 -0
- package/dist/cloudflare-adapter.mjs +170 -88
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/dist/types/wfp/dispatch-sync-defaults.d.ts +7 -2
- package/dist/types/wfp/tenant-app.d.ts +27 -1
- package/dist/types/wfp-provisioner/tenant-hook.d.ts +17 -0
- package/dist/types/wfp-provisioner/types.d.ts +6 -0
- package/dist/wfp.cjs +1 -1
- package/dist/wfp.d.ts +33 -3
- package/dist/wfp.mjs +35 -12
- package/package.json +4 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DataAdapters } from "@authhero/adapter-interfaces";
|
|
2
|
-
import { type DefaultsPayloadEntities } from "@authhero/multi-tenancy";
|
|
2
|
+
import { type ControlPlaneDefaultsApplyResult, type DefaultsPayloadEntities } from "@authhero/multi-tenancy";
|
|
3
3
|
import type { DispatchNamespace } from "../code-executor";
|
|
4
4
|
export interface DispatchSyncDefaultsOptions {
|
|
5
5
|
/** The dispatch namespace binding the tenant workers live in. */
|
|
@@ -29,5 +29,10 @@ export interface DispatchSyncDefaultsOptions {
|
|
|
29
29
|
* `/internal/sync-defaults` route (which applies it). The tenant worker never
|
|
30
30
|
* calls back to the control plane. Use the returned function as the
|
|
31
31
|
* provision-time seed and for on-change / rotation re-syncs.
|
|
32
|
+
*
|
|
33
|
+
* Resolves with the tenant worker's {@link ControlPlaneDefaultsApplyResult} so
|
|
34
|
+
* the caller can act on what actually landed — warn when an entity's `received`
|
|
35
|
+
* is `0`, surface per-entity `errors`, or assert the signing keys projected.
|
|
36
|
+
* Rejects (does not resolve) when the push fails or the worker returns non-2xx.
|
|
32
37
|
*/
|
|
33
|
-
export declare function createDispatchSyncDefaults(options: DispatchSyncDefaultsOptions): (tenantId: string) => Promise<
|
|
38
|
+
export declare function createDispatchSyncDefaults(options: DispatchSyncDefaultsOptions): (tenantId: string) => Promise<ControlPlaneDefaultsApplyResult>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ExecutionContext } from "hono";
|
|
2
|
-
import { type AuthHeroConfig, type DataAdapters } from "authhero";
|
|
2
|
+
import { type AuthHeroConfig, type DataAdapters, type IssuerResolver } from "authhero";
|
|
3
|
+
import { type ControlPlaneDefaultsApplyResult } from "@authhero/multi-tenancy";
|
|
3
4
|
/**
|
|
4
5
|
* Env contract for a WFP tenant worker — the bindings + secrets
|
|
5
6
|
* `createCloudflareWfpD1Provisioner` (and the host's `secrets` resolver) set on
|
|
@@ -45,6 +46,31 @@ export interface WfpTenantAppOptions<Env extends WfpTenantEnv = WfpTenantEnv> {
|
|
|
45
46
|
* scaffold's base config and the env.
|
|
46
47
|
*/
|
|
47
48
|
configure?: (base: AuthHeroConfig, env: Env) => AuthHeroConfig;
|
|
49
|
+
/**
|
|
50
|
+
* Resolver for the issuers accepted *in addition to* this tenant's own
|
|
51
|
+
* `ISSUER`. Defaults to a **gated** resolver that accepts
|
|
52
|
+
* `env.CONTROL_PLANE_ISSUER` **only** for control-plane-minted tokens
|
|
53
|
+
* (`tenant_id === env.CONTROL_PLANE_TENANT_ID`) and `[]` otherwise — so a
|
|
54
|
+
* tenant token can never assert the control-plane issuer. Provide your own
|
|
55
|
+
* resolver to widen or narrow this; it fully replaces the default.
|
|
56
|
+
*/
|
|
57
|
+
additionalIssuers?: (env: Env) => IssuerResolver;
|
|
58
|
+
/**
|
|
59
|
+
* When applying a `/internal/sync-defaults` push, keep going past a failing
|
|
60
|
+
* row and collect per-entity errors in the result rather than aborting the
|
|
61
|
+
* whole projection on the first failure. Defaults to `true` — a single bad
|
|
62
|
+
* inherited row shouldn't sink the entire sync into an opaque 500. Set
|
|
63
|
+
* `false` to restore fail-fast.
|
|
64
|
+
*/
|
|
65
|
+
continueOnError?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Called after a `/internal/sync-defaults` push is applied, with the result
|
|
68
|
+
* that is also returned to the control plane. Use it to log what landed
|
|
69
|
+
* (per-entity `received` / `upserted` / `errors`) without re-implementing the
|
|
70
|
+
* route. A throw here is caught and turns into a structured 500 — the apply
|
|
71
|
+
* has already committed, so treat it as best-effort logging.
|
|
72
|
+
*/
|
|
73
|
+
onSyncResult?: (result: ControlPlaneDefaultsApplyResult, env: Env) => void | Promise<void>;
|
|
48
74
|
}
|
|
49
75
|
/**
|
|
50
76
|
* Builds a WFP tenant worker `{ fetch }` handler: key-ring encryption over the
|
|
@@ -60,6 +60,23 @@ export interface WfpTenantProvisioningHookOptions {
|
|
|
60
60
|
* a silent no-op so this module stays test-quiet.
|
|
61
61
|
*/
|
|
62
62
|
logger?: Pick<Console, "warn">;
|
|
63
|
+
/**
|
|
64
|
+
* Optional post-provision seed run *after* the CF resources exist but
|
|
65
|
+
* *before* the tenant is marked `ready` — typically
|
|
66
|
+
* `createDispatchSyncDefaults(...)`, which projects the control plane's
|
|
67
|
+
* defaults (and the FK-target tenant rows) into the fresh D1.
|
|
68
|
+
*
|
|
69
|
+
* Folding the seed into provisioning closes the "ready but empty D1" gap: a
|
|
70
|
+
* best-effort seed fired separately after this hook can be lost, leaving a
|
|
71
|
+
* tenant marked `ready` whose D1 has no tenant row and no projected defaults.
|
|
72
|
+
* Here, if the seed throws — or resolves with collected per-entity errors
|
|
73
|
+
* (the seed applies with `continueOnError`, so failures surface in the
|
|
74
|
+
* result rather than as a rejection) — the tenant is marked `failed`
|
|
75
|
+
* (resource ids are still persisted so a re-provision can find them) and the
|
|
76
|
+
* error is re-thrown. Leave unset to mark `ready` as soon as the resources
|
|
77
|
+
* exist.
|
|
78
|
+
*/
|
|
79
|
+
syncDefaults?: (tenantId: string) => Promise<unknown>;
|
|
63
80
|
}
|
|
64
81
|
export interface WfpTenantProvisioningHook {
|
|
65
82
|
onProvision(tenantId: string): Promise<void>;
|
|
@@ -127,6 +127,12 @@ export interface CloudflareWfpD1ProvisionerOptions {
|
|
|
127
127
|
* tenant bundle also takes a noticeable chunk of that.
|
|
128
128
|
*/
|
|
129
129
|
timeoutMs?: number;
|
|
130
|
+
/**
|
|
131
|
+
* Optional `console`-compatible logger. Used to note non-fatal recovery
|
|
132
|
+
* decisions — e.g. skipping migrations while re-provisioning to heal an
|
|
133
|
+
* orphaned worker over an existing D1. Defaults to silent.
|
|
134
|
+
*/
|
|
135
|
+
logger?: Pick<Console, "warn">;
|
|
130
136
|
}
|
|
131
137
|
/**
|
|
132
138
|
* Outcome of a successful `onProvision` — returned so the caller can persist
|
package/dist/wfp.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@authhero/multi-tenancy"),t=require("hono"),n=require("authhero");var r=`tenant-{tenant_id}-auth`,i=3e4,a=`/internal/sync-defaults`;function o(e,t){return e.replace(/\{tenant_id\}/g,t)}function s(t){let{dispatcher:n,scriptNameTemplate:s=r,internalSecret:c,controlPlaneTenantId:l,controlPlaneAdapters:u,entities:d,timeoutMs:f=i}=t;return async t=>{let r=await(0,e.buildControlPlaneDefaultsPayload)(u,l,d,t),i=o(s,t),p=new AbortController,m=setTimeout(()=>p.abort(),f),h;try{h=await n.get(i).fetch(`https://tenant.internal${a}`,{method:`POST`,headers:{"content-type":`application/json`,authorization:`Bearer ${c}`},body:JSON.stringify(r),signal:p.signal})}finally{clearTimeout(m)}if(!h.ok){let e=await h.text().catch(()=>``);throw Error(`sync-defaults push to "${i}" failed: ${h.status} ${e.slice(0,256)}`)}}}var c=`cp`,l=`/internal/sync-defaults`;async function
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@authhero/multi-tenancy"),t=require("hono"),n=require("authhero");var r=`tenant-{tenant_id}-auth`,i=3e4,a=`/internal/sync-defaults`;function o(e,t){return e.replace(/\{tenant_id\}/g,t)}function s(t){let{dispatcher:n,scriptNameTemplate:s=r,internalSecret:c,controlPlaneTenantId:l,controlPlaneAdapters:u,entities:d,timeoutMs:f=i}=t;return async t=>{let r=await(0,e.buildControlPlaneDefaultsPayload)(u,l,d,t),i=o(s,t),p=new AbortController,m=setTimeout(()=>p.abort(),f),h;try{h=await n.get(i).fetch(`https://tenant.internal${a}`,{method:`POST`,headers:{"content-type":`application/json`,authorization:`Bearer ${c}`},body:JSON.stringify(r),signal:p.signal})}finally{clearTimeout(m)}if(!h.ok){let e=await h.text().catch(()=>``),t=h.headers.get(`x-authhero-error`);throw Error(`sync-defaults push to "${i}" failed: ${h.status}${t?` (${t})`:``} ${e.slice(0,256)}`)}return await h.json()}}var c=`cp`,l=`/internal/sync-defaults`;function u(e,t,n,r){console.error(`[wfp-tenant] ${t}:`,n);let i=n instanceof Error?n.message:String(n);return e.header(`X-Authhero-Error`,t),e.json({error:t,detail:i,result:r},500)}function d(e){return({tenant_id:t})=>t===e.CONTROL_PLANE_TENANT_ID?[e.CONTROL_PLANE_ISSUER]:[]}async function f(r,i){let a=r.CONTROL_PLANE_TENANT_ID,o=await(0,n.loadEncryptionKey)(r.ENCRYPTION_KEY),s=r.CONTROL_PLANE_ENCRYPTION_KEY?{default:o,keys:{[c]:await(0,n.loadEncryptionKey)(r.CONTROL_PLANE_ENCRYPTION_KEY)}}:{default:o},f=(0,n.createEncryptedDataAdapterWithKeyRing)(i.createDataAdapter(r),s,{resolveEncryptKeyId:e=>e===a?c:void 0}),p={dataAdapter:(0,e.withRuntimeFallback)(f,{controlPlaneTenantId:a}),additionalIssuers:(i.additionalIssuers??d)(r)},{app:m}=(0,n.init)(i.configure?i.configure(p,r):p),h=new t.Hono;return h.post(l,async t=>{let n=r.WFP_INTERNAL_SYNC_SECRET,o=t.req.header(`authorization`);if(!n||o!==`Bearer ${n}`)return t.json({error:`unauthorized`},401);let s;try{s=await t.req.json()}catch{return t.json({error:`invalid JSON`},400)}let c;try{c=await(0,e.applyControlPlaneDefaultsPayload)(s,f,a,{continueOnError:i.continueOnError??!0})}catch(e){return u(t,`sync_defaults_apply_failed`,e)}if(i.onSyncResult)try{await i.onSyncResult(c,r)}catch(e){return u(t,`sync_defaults_on_result_failed`,e,c)}return t.json(c)}),h.route(`/`,m),h}function p(e){let t=new WeakMap;return{fetch(n,r,i){let a=t.get(r);return a||(a=f(r,e),t.set(r,a)),a.then(e=>e.fetch(n,r,i))}}}exports.createDispatchSyncDefaults=s,exports.createWfpTenantApp=p;
|
package/dist/wfp.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DataAdapters } from '@authhero/adapter-interfaces';
|
|
2
|
-
import { DefaultsPayloadEntities } from '@authhero/multi-tenancy';
|
|
2
|
+
import { DefaultsPayloadEntities, ControlPlaneDefaultsApplyResult } from '@authhero/multi-tenancy';
|
|
3
3
|
import { ExecutionContext } from 'hono';
|
|
4
|
-
import { DataAdapters as DataAdapters$1, AuthHeroConfig } from 'authhero';
|
|
4
|
+
import { DataAdapters as DataAdapters$1, AuthHeroConfig, IssuerResolver } from 'authhero';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Cloudflare Workers for Platforms dispatch namespace binding type.
|
|
@@ -52,8 +52,13 @@ interface DispatchSyncDefaultsOptions {
|
|
|
52
52
|
* `/internal/sync-defaults` route (which applies it). The tenant worker never
|
|
53
53
|
* calls back to the control plane. Use the returned function as the
|
|
54
54
|
* provision-time seed and for on-change / rotation re-syncs.
|
|
55
|
+
*
|
|
56
|
+
* Resolves with the tenant worker's {@link ControlPlaneDefaultsApplyResult} so
|
|
57
|
+
* the caller can act on what actually landed — warn when an entity's `received`
|
|
58
|
+
* is `0`, surface per-entity `errors`, or assert the signing keys projected.
|
|
59
|
+
* Rejects (does not resolve) when the push fails or the worker returns non-2xx.
|
|
55
60
|
*/
|
|
56
|
-
declare function createDispatchSyncDefaults(options: DispatchSyncDefaultsOptions): (tenantId: string) => Promise<
|
|
61
|
+
declare function createDispatchSyncDefaults(options: DispatchSyncDefaultsOptions): (tenantId: string) => Promise<ControlPlaneDefaultsApplyResult>;
|
|
57
62
|
|
|
58
63
|
/**
|
|
59
64
|
* Env contract for a WFP tenant worker — the bindings + secrets
|
|
@@ -100,6 +105,31 @@ interface WfpTenantAppOptions<Env extends WfpTenantEnv = WfpTenantEnv> {
|
|
|
100
105
|
* scaffold's base config and the env.
|
|
101
106
|
*/
|
|
102
107
|
configure?: (base: AuthHeroConfig, env: Env) => AuthHeroConfig;
|
|
108
|
+
/**
|
|
109
|
+
* Resolver for the issuers accepted *in addition to* this tenant's own
|
|
110
|
+
* `ISSUER`. Defaults to a **gated** resolver that accepts
|
|
111
|
+
* `env.CONTROL_PLANE_ISSUER` **only** for control-plane-minted tokens
|
|
112
|
+
* (`tenant_id === env.CONTROL_PLANE_TENANT_ID`) and `[]` otherwise — so a
|
|
113
|
+
* tenant token can never assert the control-plane issuer. Provide your own
|
|
114
|
+
* resolver to widen or narrow this; it fully replaces the default.
|
|
115
|
+
*/
|
|
116
|
+
additionalIssuers?: (env: Env) => IssuerResolver;
|
|
117
|
+
/**
|
|
118
|
+
* When applying a `/internal/sync-defaults` push, keep going past a failing
|
|
119
|
+
* row and collect per-entity errors in the result rather than aborting the
|
|
120
|
+
* whole projection on the first failure. Defaults to `true` — a single bad
|
|
121
|
+
* inherited row shouldn't sink the entire sync into an opaque 500. Set
|
|
122
|
+
* `false` to restore fail-fast.
|
|
123
|
+
*/
|
|
124
|
+
continueOnError?: boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Called after a `/internal/sync-defaults` push is applied, with the result
|
|
127
|
+
* that is also returned to the control plane. Use it to log what landed
|
|
128
|
+
* (per-entity `received` / `upserted` / `errors`) without re-implementing the
|
|
129
|
+
* route. A throw here is caught and turns into a structured 500 — the apply
|
|
130
|
+
* has already committed, so treat it as best-effort logging.
|
|
131
|
+
*/
|
|
132
|
+
onSyncResult?: (result: ControlPlaneDefaultsApplyResult, env: Env) => void | Promise<void>;
|
|
103
133
|
}
|
|
104
134
|
/**
|
|
105
135
|
* Builds a WFP tenant worker `{ fetch }` handler: key-ring encryption over the
|
package/dist/wfp.mjs
CHANGED
|
@@ -24,23 +24,36 @@ function d(e) {
|
|
|
24
24
|
clearTimeout(m);
|
|
25
25
|
}
|
|
26
26
|
if (!h.ok) {
|
|
27
|
-
let e = await h.text().catch(() => "");
|
|
28
|
-
throw Error(`sync-defaults push to "${c}" failed: ${h.status} ${e.slice(0, 256)}`);
|
|
27
|
+
let e = await h.text().catch(() => ""), t = h.headers.get("x-authhero-error");
|
|
28
|
+
throw Error(`sync-defaults push to "${c}" failed: ${h.status}${t ? ` (${t})` : ""} ${e.slice(0, 256)}`);
|
|
29
29
|
}
|
|
30
|
+
return await h.json();
|
|
30
31
|
};
|
|
31
32
|
}
|
|
32
33
|
//#endregion
|
|
33
34
|
//#region src/wfp/tenant-app.ts
|
|
34
35
|
var f = "cp", p = "/internal/sync-defaults";
|
|
35
|
-
|
|
36
|
+
function m(e, t, n, r) {
|
|
37
|
+
console.error(`[wfp-tenant] ${t}:`, n);
|
|
38
|
+
let i = n instanceof Error ? n.message : String(n);
|
|
39
|
+
return e.header("X-Authhero-Error", t), e.json({
|
|
40
|
+
error: t,
|
|
41
|
+
detail: i,
|
|
42
|
+
result: r
|
|
43
|
+
}, 500);
|
|
44
|
+
}
|
|
45
|
+
function h(e) {
|
|
46
|
+
return ({ tenant_id: t }) => t === e.CONTROL_PLANE_TENANT_ID ? [e.CONTROL_PLANE_ISSUER] : [];
|
|
47
|
+
}
|
|
48
|
+
async function g(t, s) {
|
|
36
49
|
let c = t.CONTROL_PLANE_TENANT_ID, l = await o(t.ENCRYPTION_KEY), u = t.CONTROL_PLANE_ENCRYPTION_KEY ? {
|
|
37
50
|
default: l,
|
|
38
51
|
keys: { [f]: await o(t.CONTROL_PLANE_ENCRYPTION_KEY) }
|
|
39
|
-
} : { default: l }, d = i(s.createDataAdapter(t), u, { resolveEncryptKeyId: (e) => e === c ? f : void 0 }),
|
|
52
|
+
} : { default: l }, d = i(s.createDataAdapter(t), u, { resolveEncryptKeyId: (e) => e === c ? f : void 0 }), g = {
|
|
40
53
|
dataAdapter: n(d, { controlPlaneTenantId: c }),
|
|
41
|
-
additionalIssuers: (
|
|
42
|
-
}, { app:
|
|
43
|
-
return
|
|
54
|
+
additionalIssuers: (s.additionalIssuers ?? h)(t)
|
|
55
|
+
}, { app: _ } = a(s.configure ? s.configure(g, t) : g), v = new r();
|
|
56
|
+
return v.post(p, async (n) => {
|
|
44
57
|
let r = t.WFP_INTERNAL_SYNC_SECRET, i = n.req.header("authorization");
|
|
45
58
|
if (!r || i !== `Bearer ${r}`) return n.json({ error: "unauthorized" }, 401);
|
|
46
59
|
let a;
|
|
@@ -49,16 +62,26 @@ async function m(t, s) {
|
|
|
49
62
|
} catch {
|
|
50
63
|
return n.json({ error: "invalid JSON" }, 400);
|
|
51
64
|
}
|
|
52
|
-
let o
|
|
65
|
+
let o;
|
|
66
|
+
try {
|
|
67
|
+
o = await e(a, d, c, { continueOnError: s.continueOnError ?? !0 });
|
|
68
|
+
} catch (e) {
|
|
69
|
+
return m(n, "sync_defaults_apply_failed", e);
|
|
70
|
+
}
|
|
71
|
+
if (s.onSyncResult) try {
|
|
72
|
+
await s.onSyncResult(o, t);
|
|
73
|
+
} catch (e) {
|
|
74
|
+
return m(n, "sync_defaults_on_result_failed", e, o);
|
|
75
|
+
}
|
|
53
76
|
return n.json(o);
|
|
54
|
-
}),
|
|
77
|
+
}), v.route("/", _), v;
|
|
55
78
|
}
|
|
56
|
-
function
|
|
79
|
+
function _(e) {
|
|
57
80
|
let t = /* @__PURE__ */ new WeakMap();
|
|
58
81
|
return { fetch(n, r, i) {
|
|
59
82
|
let a = t.get(r);
|
|
60
|
-
return a || (a =
|
|
83
|
+
return a || (a = g(r, e), t.set(r, a)), a.then((e) => e.fetch(n, r, i));
|
|
61
84
|
} };
|
|
62
85
|
}
|
|
63
86
|
//#endregion
|
|
64
|
-
export { d as createDispatchSyncDefaults,
|
|
87
|
+
export { d as createDispatchSyncDefaults, _ as createWfpTenantApp };
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"type": "git",
|
|
12
12
|
"url": "https://github.com/markusahlstrand/authhero"
|
|
13
13
|
},
|
|
14
|
-
"version": "2.
|
|
14
|
+
"version": "2.36.0",
|
|
15
15
|
"files": [
|
|
16
16
|
"dist"
|
|
17
17
|
],
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"vite": "^8.0.14",
|
|
44
44
|
"vitest": "^4.1.7",
|
|
45
45
|
"@authhero/multi-tenancy": "14.25.1",
|
|
46
|
-
"authhero": "8.7.
|
|
46
|
+
"authhero": "8.7.1"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"@authhero/multi-tenancy": "^14.0.0",
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"nanoid": "^5.1.11",
|
|
64
64
|
"wretch": "^3.0.8",
|
|
65
|
-
"@authhero/adapter
|
|
66
|
-
"@authhero/
|
|
65
|
+
"@authhero/kysely-adapter": "11.8.10",
|
|
66
|
+
"@authhero/adapter-interfaces": "3.2.0"
|
|
67
67
|
},
|
|
68
68
|
"scripts": {
|
|
69
69
|
"build": "vite build && tsc -p tsconfig.types.json && rollup -c rollup.dts.config.mjs",
|