@authhero/cloudflare-adapter 3.2.5 → 3.3.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.
@@ -0,0 +1,37 @@
1
+ import type { DataAdapters } from "@authhero/adapter-interfaces";
2
+ import { type SyncConfigFn } from "@authhero/multi-tenancy";
3
+ import type { DispatchNamespace } from "../code-executor";
4
+ export interface DispatchSyncConfigOptions {
5
+ /** The dispatch namespace binding the tenant workers live in. */
6
+ dispatcher: DispatchNamespace;
7
+ /** Script-name convention. Supports `{tenant_id}`. */
8
+ scriptNameTemplate?: string;
9
+ /**
10
+ * Shared secret the tenant worker checks on `/internal/sync-config`
11
+ * (`WFP_INTERNAL_SYNC_SECRET`). Sent as a bearer token.
12
+ */
13
+ internalSecret: string;
14
+ /**
15
+ * The control plane, used as implicit parent for tenants without a
16
+ * `parent_tenant_id`.
17
+ */
18
+ controlPlaneTenantId: string;
19
+ /**
20
+ * Adapters for reading the chain. Use the layer that returns secrets as
21
+ * stored (ciphertext): they are copied without being decrypted.
22
+ */
23
+ controlPlaneAdapters: DataAdapters;
24
+ /** Maximum ancestors, see `resolveTenantChain`. */
25
+ maxDepth?: number;
26
+ /** Per-push timeout. Defaults to 30s. */
27
+ timeoutMs?: number;
28
+ }
29
+ /**
30
+ * Builds the `sync-defaults` operation's transport for tenants with their own
31
+ * worker and database: resolve the tenant's chain on the control plane, push
32
+ * the resolved block to the tenant worker's `/internal/sync-config`, and return
33
+ * what it applied (or, for a dry run, what it would). The tenant worker never
34
+ * calls back to the control plane. Rejects when the push fails or the worker
35
+ * answers non-2xx, which fails the operation.
36
+ */
37
+ export declare function createDispatchSyncConfig(options: DispatchSyncConfigOptions): SyncConfigFn;
@@ -5,5 +5,6 @@
5
5
  * **optional peer dependencies**: install them only if you import this subpath.
6
6
  */
7
7
  export { createDispatchSyncDefaults, type DispatchSyncDefaultsOptions, } from "./dispatch-sync-defaults";
8
+ export { createDispatchSyncConfig, type DispatchSyncConfigOptions, } from "./dispatch-sync-config";
8
9
  export { createDispatchRenewSigningCertificates, type DispatchRenewSigningCertificatesOptions, type DispatchRenewSigningCertificatesResult, } from "./dispatch-renew-signing-certificates";
9
10
  export { createWfpTenantApp, type WfpTenantEnv, type WfpTenantAppOptions, type WfpSigningCertificateAuthority, } from "./tenant-app";
@@ -31,6 +31,15 @@ export interface WfpTenantEnv {
31
31
  CONTROL_PLANE_ENCRYPTION_KEY?: string;
32
32
  /** Shared secret authenticating control-plane pushes to `/internal/sync-defaults`. */
33
33
  WFP_INTERNAL_SYNC_SECRET?: string;
34
+ /**
35
+ * The tenant this worker serves, set when the script is provisioned (plain
36
+ * text binding or secret, next to `ISSUER`). When present,
37
+ * `/internal/sync-config` refuses a push addressed to any other tenant, so a
38
+ * misrouted push can never write another tenant's config into this database.
39
+ * Optional so workers provisioned before it existed keep working; set it from
40
+ * the provisioner's `secrets` resolver.
41
+ */
42
+ TENANT_ID?: string;
34
43
  [key: string]: unknown;
35
44
  }
36
45
  export interface WfpTenantAppOptions<Env extends WfpTenantEnv = WfpTenantEnv> {
@@ -31,6 +31,7 @@
31
31
  * createWfpWorkflowProvisioningHook,
32
32
  * reconcileTenantOperations,
33
33
  * runProvisionOperation,
34
+ * runSyncDefaultsOperation,
34
35
  * type ProvisionOperationDeps,
35
36
  * type TenantOperationWorkflowParams,
36
37
  * } from "@authhero/cloudflare-adapter/workflows";
@@ -40,7 +41,10 @@
40
41
  * createWfpTenantProvisioningHook,
41
42
  * CloudflareApiClient,
42
43
  * } from "@authhero/cloudflare-adapter";
43
- * import { createDispatchSyncDefaults } from "@authhero/cloudflare-adapter/wfp";
44
+ * import {
45
+ * createDispatchSyncConfig,
46
+ * createDispatchSyncDefaults,
47
+ * } from "@authhero/cloudflare-adapter/wfp";
44
48
  *
45
49
  * // Host-owned: build every workflow dependency from the worker env. Params
46
50
  * // carry only { operation_id, tenant_id, kind } — never secrets.
@@ -69,8 +73,33 @@
69
73
  *
70
74
  * export class TenantOperationWorkflow extends WorkflowEntrypoint<Env, TenantOperationWorkflowParams> {
71
75
  * async run(event: WorkflowEvent<TenantOperationWorkflowParams>, step: WorkflowStep) {
72
- * // CF's WorkflowStep satisfies StepRunner structurally.
73
- * await runProvisionOperation(buildProvisionDeps(this.env), event.payload, step);
76
+ * // CF's WorkflowStep satisfies StepRunner structurally. One class runs
77
+ * // every kind; params carry the kind, never secrets.
78
+ * switch (event.payload.kind) {
79
+ * case "sync-defaults": {
80
+ * const adapters = createControlPlaneAdapters(this.env);
81
+ * return runSyncDefaultsOperation(
82
+ * {
83
+ * stores: {
84
+ * tenantOperations: adapters.tenantOperations!,
85
+ * tenantOperationEvents: adapters.tenantOperationEvents!,
86
+ * },
87
+ * // Reads the chain as stored (ciphertext) and pushes the
88
+ * // resolved block to the tenant worker's /internal/sync-config.
89
+ * syncConfig: createDispatchSyncConfig({
90
+ * dispatcher: this.env.DISPATCHER,
91
+ * internalSecret: this.env.WFP_INTERNAL_SYNC_SECRET,
92
+ * controlPlaneTenantId: this.env.CONTROL_PLANE_TENANT_ID,
93
+ * controlPlaneAdapters: adapters,
94
+ * }),
95
+ * },
96
+ * event.payload,
97
+ * step,
98
+ * );
99
+ * }
100
+ * default:
101
+ * return runProvisionOperation(buildProvisionDeps(this.env), event.payload, step);
102
+ * }
74
103
  * }
75
104
  * }
76
105
  *
@@ -87,6 +116,14 @@
87
116
  * binding: env.TENANT_OPERATIONS_WORKFLOW,
88
117
  * });
89
118
  * console.log("reconcileTenantOperations", result);
119
+ *
120
+ * // Fleet rollouts of synced config (initMultiTenant's `rollouts`):
121
+ * // find tenants whose applied config no longer matches what their
122
+ * // parents publish (the safety net for lost write triggers), then start
123
+ * // the next wave of every open rollout. Pass a Workflows executor as
124
+ * // initMultiTenant's `rolloutExecutor` to run the waves durably.
125
+ * // await reconcileConfigDrift(rollouts);
126
+ * // await advanceRollouts(rollouts);
90
127
  * },
91
128
  * };
92
129
  *
@@ -13,6 +13,7 @@
13
13
  * structurally.
14
14
  */
15
15
  export { runProvisionOperation, type ProvisionOperationDeps, type ProvisionStepName, } from "./provision-operation";
16
+ export { runSyncDefaultsOperation, type SyncDefaultsOperationDeps, type SyncDefaultsStepName, } from "./sync-defaults-operation";
16
17
  export { createProvisionVerifier, TenantProvisionVerificationError, type ProvisionVerifierOptions, } from "./verify";
17
18
  export { createCloudflareWorkflowsExecutor, type CloudflareWorkflowsExecutorOptions, } from "./executor";
18
19
  export { reconcileTenantOperations, type ReconcileTenantOperationsOptions, type ReconcileTenantOperationsResult, } from "./reconcile";
@@ -0,0 +1,28 @@
1
+ import { type StepConfig, type StepRunner, type SyncConfigFn, type TenantOperationStores } from "@authhero/multi-tenancy";
2
+ import type { TenantOperationWorkflowParams } from "./types";
3
+ export type SyncDefaultsStepName = "mark-running" | "sync-defaults" | "mark-succeeded" | "mark-failed";
4
+ export interface SyncDefaultsOperationDeps {
5
+ /** Control-plane operation log stores. */
6
+ stores: TenantOperationStores;
7
+ /**
8
+ * How the tenant's resolved config reaches its database, for example
9
+ * `createDispatchSyncConfig(...)`.
10
+ */
11
+ syncConfig: SyncConfigFn;
12
+ /** Per-step overrides of the retry/timeout defaults. */
13
+ stepConfig?: Partial<Record<SyncDefaultsStepName, StepConfig>>;
14
+ }
15
+ /**
16
+ * Durable `sync-defaults` operation: sync one tenant's resolved parent-chain
17
+ * config into its database as a Workflows instance, writing to the control-plane
18
+ * operation log at every step boundary (the same shape as
19
+ * {@link runProvisionOperation}, and the same step logic as the inline
20
+ * executor). The operation's `input` (`dry_run`, `force`) is read from its row,
21
+ * so nothing but ids crosses the Workflows boundary.
22
+ *
23
+ * A dry run records its diff in the operation's `result` and writes nothing to
24
+ * the tenant. Any failure runs `mark-failed` (event + failed row) and rethrows
25
+ * so the instance ends `errored`; if even that dies, the reconciler copies the
26
+ * engine's terminal state into the row.
27
+ */
28
+ export declare function runSyncDefaultsOperation(deps: SyncDefaultsOperationDeps, params: TenantOperationWorkflowParams, step: StepRunner): Promise<void>;
@@ -14,8 +14,8 @@
14
14
  export interface TenantOperationWorkflowParams {
15
15
  operation_id: string;
16
16
  tenant_id: string;
17
- /** Widened beyond "provision" in later phases (upgrade, backup, …). */
18
- kind: "provision";
17
+ /** Widened beyond these in later phases (upgrade, backup, …). */
18
+ kind: "provision" | "sync-defaults";
19
19
  }
20
20
  /**
21
21
  * Instance status as reported by the engine. The exact value set has
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(()=>``),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=`/internal/renew-signing-certificates`;function l(e){let{dispatcher:t,scriptNameTemplate:n=r,internalSecret:a,timeoutMs:s=i}=e;return async e=>{let r=o(n,e),i=new AbortController,l=setTimeout(()=>i.abort(),s);try{let e=await t.get(r).fetch(`https://tenant.internal${c}`,{method:`POST`,headers:{authorization:`Bearer ${a}`},signal:i.signal});if(!e.ok){let t=await e.text().catch(()=>``),n=e.headers.get(`x-authhero-error`);throw Error(`signing-certificate renewal for "${r}" failed: ${e.status}${n?` (${n})`:``} ${t.slice(0,512)}`)}let n=await e.json();if(!u(n))throw Error(`signing-certificate renewal for "${r}" returned an unexpected body`);return{renewed:n.renewed,notDue:n.notDue}}finally{clearTimeout(l)}}}function u(e){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`}var d=`cp`,f=`/internal/sync-defaults`,p=`/internal/renew-signing-certificates`;function m(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 h(e){let t=`${n.TENANT_URI_PREFIX}${e.tenantId}`;return{issuer:e.issuer,validityDays:e.validityDays,subjectUri:()=>t}}function g(e){return({tenant_id:t})=>t===e.CONTROL_PLANE_TENANT_ID?[e.CONTROL_PLANE_ISSUER]:[]}async function _(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:{[d]:await(0,n.loadEncryptionKey)(r.CONTROL_PLANE_ENCRYPTION_KEY)}}:{default:o},c=(0,n.createEncryptedDataAdapterWithKeyRing)(i.createDataAdapter(r),s,{resolveEncryptKeyId:e=>e===a?d:void 0}),l=(0,e.withRuntimeFallback)(c,{controlPlaneTenantId:a}),u=i.signingCertificateAuthority?.(r),_=u?h(u):void 0,v={dataAdapter:l,..._?{signingCertificateAuthority:_}:{},additionalIssuers:(i.additionalIssuers??g)(r)},y=i.configure?i.configure(v,r):v,b=y.signingCertificateAuthority,{app:x}=(0,n.init)(y),S=new t.Hono;return S.post(f,async t=>{let o=r.WFP_INTERNAL_SYNC_SECRET,s=t.req.header(`authorization`);if(!o||s!==`Bearer ${o}`)return t.json({error:`unauthorized`},401);let l;try{l=await t.req.json()}catch{return t.json({error:`invalid JSON`},400)}let u;try{u=await(0,e.applyControlPlaneDefaultsPayload)(l,c,a,{continueOnError:i.continueOnError??!0})}catch(e){return m(t,`sync_defaults_apply_failed`,e)}let d;try{d={created:(await(0,n.ensureSigningKey)(c.keys,{name:r.ISSUER,certificateAuthority:b})).created}}catch(e){return m(t,`sync_defaults_signing_key_failed`,e,u)}if(i.onSyncResult)try{await i.onSyncResult(u,r)}catch(e){return m(t,`sync_defaults_on_result_failed`,e,u)}return t.json({...u,signingKey:d})}),S.post(p,async e=>{let t=r.WFP_INTERNAL_SYNC_SECRET,i=e.req.header(`authorization`);if(!t||i!==`Bearer ${t}`)return e.json({error:`unauthorized`},401);if(!b)return e.json({error:`no signing certificate authority`},404);try{let t=await(0,n.renewSigningCertificates)({dataAdapter:{keys:c.keys},certificateAuthority:b});return e.json({renewed:t.renewed,notDue:t.notDue})}catch(t){return t instanceof n.SigningCertificateRenewalError?(console.error(`[wfp-tenant] signing_certificate_renewal_failed:`,t),e.header(`X-Authhero-Error`,`signing_certificate_renewal_failed`),e.json({error:`signing_certificate_renewal_failed`,renewed:t.result.renewed,notDue:t.result.notDue,failed:t.result.failed.map(({kid:e,error:t})=>({kid:e,detail:t instanceof Error?t.message:String(t)}))},500)):m(e,`signing_certificate_renewal_failed`,t)}}),S.route(`/`,x),S}function v(e){let t=new WeakMap;return{fetch(n,r,i){let a=t.get(r);return a||(a=_(r,e),t.set(r,a)),a.then(e=>e.fetch(n,r,i))}}}exports.createDispatchRenewSigningCertificates=l,exports.createDispatchSyncDefaults=s,exports.createWfpTenantApp=v;
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=`/internal/sync-config`;function l(t){let{dispatcher:n,scriptNameTemplate:a=r,internalSecret:s,controlPlaneTenantId:l,controlPlaneAdapters:u,maxDepth:d,timeoutMs:f=i}=t;return async(t,{dryRun:r,force:i})=>{let p=await(0,e.buildResolvedConfigBlock)(u,t,{legacyControlPlane:l,maxDepth:d}),m=o(a,t),h=new AbortController,g=setTimeout(()=>h.abort(),f),_;try{_=await n.get(m).fetch(`https://tenant.internal${c}`,{method:`POST`,headers:{"content-type":`application/json`,authorization:`Bearer ${s}`},body:JSON.stringify({tenantId:t,resolved:p,dryRun:r,force:i}),signal:h.signal})}finally{clearTimeout(g)}if(!_.ok){let e=await _.text().catch(()=>``),t=_.headers.get(`x-authhero-error`);throw Error(`sync-config push to "${m}" failed: ${_.status}${t?` (${t})`:``} ${e.slice(0,256)}`)}return await _.json()}}var u=`/internal/renew-signing-certificates`;function d(e){let{dispatcher:t,scriptNameTemplate:n=r,internalSecret:a,timeoutMs:s=i}=e;return async e=>{let r=o(n,e),i=new AbortController,c=setTimeout(()=>i.abort(),s);try{let e=await t.get(r).fetch(`https://tenant.internal${u}`,{method:`POST`,headers:{authorization:`Bearer ${a}`},signal:i.signal});if(!e.ok){let t=await e.text().catch(()=>``),n=e.headers.get(`x-authhero-error`);throw Error(`signing-certificate renewal for "${r}" failed: ${e.status}${n?` (${n})`:``} ${t.slice(0,512)}`)}let n=await e.json();if(!f(n))throw Error(`signing-certificate renewal for "${r}" returned an unexpected body`);return{renewed:n.renewed,notDue:n.notDue}}finally{clearTimeout(c)}}}function f(e){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`}var p=`cp`,m=`/internal/sync-defaults`,h=`/internal/sync-config`,g=`/internal/renew-signing-certificates`;function _(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 v(e){let t=`${n.TENANT_URI_PREFIX}${e.tenantId}`;return{issuer:e.issuer,validityDays:e.validityDays,subjectUri:()=>t}}function y(e){return({tenant_id:t})=>t===e.CONTROL_PLANE_TENANT_ID?[e.CONTROL_PLANE_ISSUER]:[]}async function b(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:{[p]:await(0,n.loadEncryptionKey)(r.CONTROL_PLANE_ENCRYPTION_KEY)}}:{default:o},c=i.createDataAdapter(r),l=(0,n.createEncryptedDataAdapterWithKeyRing)(c,s,{resolveEncryptKeyId:e=>e===a?p:void 0}),u=(0,e.withRuntimeFallback)(l,{controlPlaneTenantId:a}),d=i.signingCertificateAuthority?.(r),f=d?v(d):void 0,b={dataAdapter:u,...f?{signingCertificateAuthority:f}:{},additionalIssuers:(i.additionalIssuers??y)(r)},x=i.configure?i.configure(b,r):b,S=x.signingCertificateAuthority,{app:C}=(0,n.init)(x),w=new t.Hono;return w.post(m,async t=>{let o=r.WFP_INTERNAL_SYNC_SECRET,s=t.req.header(`authorization`);if(!o||s!==`Bearer ${o}`)return t.json({error:`unauthorized`},401);let c;try{c=await t.req.json()}catch{return t.json({error:`invalid JSON`},400)}let u;try{u=await(0,e.applyControlPlaneDefaultsPayload)(c,l,a,{continueOnError:i.continueOnError??!0})}catch(e){return _(t,`sync_defaults_apply_failed`,e)}let d;try{d={created:(await(0,n.ensureSigningKey)(l.keys,{name:r.ISSUER,certificateAuthority:S})).created}}catch(e){return _(t,`sync_defaults_signing_key_failed`,e,u)}if(i.onSyncResult)try{await i.onSyncResult(u,r)}catch(e){return _(t,`sync_defaults_on_result_failed`,e,u)}return t.json({...u,signingKey:d})}),w.post(h,async t=>{let n=r.WFP_INTERNAL_SYNC_SECRET,a=t.req.header(`authorization`);if(!n||a!==`Bearer ${n}`)return t.json({error:`unauthorized`},401);let o;try{o=await t.req.json()}catch{return t.json({error:`invalid JSON`},400)}if(!o.tenantId||!o.resolved)return t.json({error:`tenantId and resolved are required`},400);if(r.TENANT_ID&&o.tenantId!==r.TENANT_ID)return console.error(`[wfp-tenant] sync_config_tenant_mismatch: push for "${o.tenantId}" reached the worker of "${r.TENANT_ID}"`),t.header(`X-Authhero-Error`,`sync_config_tenant_mismatch`),t.json({error:`sync_config_tenant_mismatch`},403);try{let n=await(0,e.applyResolvedConfigBlock)(o.resolved,c,o.tenantId,{dryRun:o.dryRun===!0,force:o.force===!0,keyRing:s,continueOnError:i.continueOnError??!0});return t.json(n)}catch(e){return e instanceof Error&&/key ring cannot decrypt/.test(e.message)?(console.error(`[wfp-tenant] sync_config_key_ring:`,e),t.header(`X-Authhero-Error`,`sync_config_key_ring`),t.json({error:`sync_config_key_ring`,detail:e.message},422)):_(t,`sync_config_apply_failed`,e)}}),w.post(g,async e=>{let t=r.WFP_INTERNAL_SYNC_SECRET,i=e.req.header(`authorization`);if(!t||i!==`Bearer ${t}`)return e.json({error:`unauthorized`},401);if(!S)return e.json({error:`no signing certificate authority`},404);try{let t=await(0,n.renewSigningCertificates)({dataAdapter:{keys:l.keys},certificateAuthority:S});return e.json({renewed:t.renewed,notDue:t.notDue})}catch(t){return t instanceof n.SigningCertificateRenewalError?(console.error(`[wfp-tenant] signing_certificate_renewal_failed:`,t),e.header(`X-Authhero-Error`,`signing_certificate_renewal_failed`),e.json({error:`signing_certificate_renewal_failed`,renewed:t.result.renewed,notDue:t.result.notDue,failed:t.result.failed.map(({kid:e,error:t})=>({kid:e,detail:t instanceof Error?t.message:String(t)}))},500)):_(e,`signing_certificate_renewal_failed`,t)}}),w.route(`/`,C),w}function x(e){let t=new WeakMap;return{fetch(n,r,i){let a=t.get(r);return a||(a=b(r,e),t.set(r,a)),a.then(e=>e.fetch(n,r,i))}}}exports.createDispatchRenewSigningCertificates=d,exports.createDispatchSyncConfig=l,exports.createDispatchSyncDefaults=s,exports.createWfpTenantApp=x;
package/dist/wfp.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DataAdapters } from '@authhero/adapter-interfaces';
2
- import { DefaultsPayloadEntities, ControlPlaneDefaultsApplyResult } from '@authhero/multi-tenancy';
2
+ import { DefaultsPayloadEntities, ControlPlaneDefaultsApplyResult, SyncConfigFn } from '@authhero/multi-tenancy';
3
3
  import { ExecutionContext } from 'hono';
4
4
  import { DataAdapters as DataAdapters$1, AuthHeroConfig, IssuerResolver, CertificateIssuer } from 'authhero';
5
5
 
@@ -60,6 +60,41 @@ interface DispatchSyncDefaultsOptions {
60
60
  */
61
61
  declare function createDispatchSyncDefaults(options: DispatchSyncDefaultsOptions): (tenantId: string) => Promise<ControlPlaneDefaultsApplyResult>;
62
62
 
63
+ interface DispatchSyncConfigOptions {
64
+ /** The dispatch namespace binding the tenant workers live in. */
65
+ dispatcher: DispatchNamespace;
66
+ /** Script-name convention. Supports `{tenant_id}`. */
67
+ scriptNameTemplate?: string;
68
+ /**
69
+ * Shared secret the tenant worker checks on `/internal/sync-config`
70
+ * (`WFP_INTERNAL_SYNC_SECRET`). Sent as a bearer token.
71
+ */
72
+ internalSecret: string;
73
+ /**
74
+ * The control plane, used as implicit parent for tenants without a
75
+ * `parent_tenant_id`.
76
+ */
77
+ controlPlaneTenantId: string;
78
+ /**
79
+ * Adapters for reading the chain. Use the layer that returns secrets as
80
+ * stored (ciphertext): they are copied without being decrypted.
81
+ */
82
+ controlPlaneAdapters: DataAdapters;
83
+ /** Maximum ancestors, see `resolveTenantChain`. */
84
+ maxDepth?: number;
85
+ /** Per-push timeout. Defaults to 30s. */
86
+ timeoutMs?: number;
87
+ }
88
+ /**
89
+ * Builds the `sync-defaults` operation's transport for tenants with their own
90
+ * worker and database: resolve the tenant's chain on the control plane, push
91
+ * the resolved block to the tenant worker's `/internal/sync-config`, and return
92
+ * what it applied (or, for a dry run, what it would). The tenant worker never
93
+ * calls back to the control plane. Rejects when the push fails or the worker
94
+ * answers non-2xx, which fails the operation.
95
+ */
96
+ declare function createDispatchSyncConfig(options: DispatchSyncConfigOptions): SyncConfigFn;
97
+
63
98
  interface DispatchRenewSigningCertificatesOptions {
64
99
  /** The dispatch namespace binding the tenant workers live in. */
65
100
  dispatcher: DispatchNamespace;
@@ -117,6 +152,15 @@ interface WfpTenantEnv {
117
152
  CONTROL_PLANE_ENCRYPTION_KEY?: string;
118
153
  /** Shared secret authenticating control-plane pushes to `/internal/sync-defaults`. */
119
154
  WFP_INTERNAL_SYNC_SECRET?: string;
155
+ /**
156
+ * The tenant this worker serves, set when the script is provisioned (plain
157
+ * text binding or secret, next to `ISSUER`). When present,
158
+ * `/internal/sync-config` refuses a push addressed to any other tenant, so a
159
+ * misrouted push can never write another tenant's config into this database.
160
+ * Optional so workers provisioned before it existed keep working; set it from
161
+ * the provisioner's `secrets` resolver.
162
+ */
163
+ TENANT_ID?: string;
120
164
  [key: string]: unknown;
121
165
  }
122
166
  interface WfpTenantAppOptions<Env extends WfpTenantEnv = WfpTenantEnv> {
@@ -193,5 +237,5 @@ declare function createWfpTenantApp<Env extends WfpTenantEnv = WfpTenantEnv>(opt
193
237
  fetch: (request: Request, env: Env, ctx: ExecutionContext) => Promise<Response>;
194
238
  };
195
239
 
196
- export { createDispatchRenewSigningCertificates, createDispatchSyncDefaults, createWfpTenantApp };
197
- export type { DispatchRenewSigningCertificatesOptions, DispatchRenewSigningCertificatesResult, DispatchSyncDefaultsOptions, WfpSigningCertificateAuthority, WfpTenantAppOptions, WfpTenantEnv };
240
+ export { createDispatchRenewSigningCertificates, createDispatchSyncConfig, createDispatchSyncDefaults, createWfpTenantApp };
241
+ export type { DispatchRenewSigningCertificatesOptions, DispatchRenewSigningCertificatesResult, DispatchSyncConfigOptions, DispatchSyncDefaultsOptions, WfpSigningCertificateAuthority, WfpTenantAppOptions, WfpTenantEnv };
package/dist/wfp.mjs CHANGED
@@ -1,17 +1,17 @@
1
- import { applyControlPlaneDefaultsPayload as e, buildControlPlaneDefaultsPayload as t, withRuntimeFallback as n } from "@authhero/multi-tenancy";
2
- import { Hono as r } from "hono";
3
- import { SigningCertificateRenewalError as i, TENANT_URI_PREFIX as a, createEncryptedDataAdapterWithKeyRing as o, ensureSigningKey as s, init as c, loadEncryptionKey as l, renewSigningCertificates as u } from "authhero";
1
+ import { applyControlPlaneDefaultsPayload as e, applyResolvedConfigBlock as t, buildControlPlaneDefaultsPayload as n, buildResolvedConfigBlock as r, withRuntimeFallback as i } from "@authhero/multi-tenancy";
2
+ import { Hono as a } from "hono";
3
+ import { SigningCertificateRenewalError as o, TENANT_URI_PREFIX as s, createEncryptedDataAdapterWithKeyRing as c, ensureSigningKey as l, init as u, loadEncryptionKey as d, renewSigningCertificates as f } from "authhero";
4
4
  //#region src/wfp/dispatch-sync-defaults.ts
5
- var d = "tenant-{tenant_id}-auth", f = 3e4, p = "/internal/sync-defaults";
6
- function m(e, t) {
5
+ var p = "tenant-{tenant_id}-auth", m = 3e4, h = "/internal/sync-defaults";
6
+ function g(e, t) {
7
7
  return e.replace(/\{tenant_id\}/g, t);
8
8
  }
9
- function h(e) {
10
- let { dispatcher: n, scriptNameTemplate: r = d, internalSecret: i, controlPlaneTenantId: a, controlPlaneAdapters: o, entities: s, timeoutMs: c = f } = e;
9
+ function _(e) {
10
+ let { dispatcher: t, scriptNameTemplate: r = p, internalSecret: i, controlPlaneTenantId: a, controlPlaneAdapters: o, entities: s, timeoutMs: c = m } = e;
11
11
  return async (e) => {
12
- let l = await t(o, a, s, e), u = m(r, e), d = new AbortController(), f = setTimeout(() => d.abort(), c), h;
12
+ let l = await n(o, a, s, e), u = g(r, e), d = new AbortController(), f = setTimeout(() => d.abort(), c), p;
13
13
  try {
14
- h = await n.get(u).fetch(`https://tenant.internal${p}`, {
14
+ p = await t.get(u).fetch(`https://tenant.internal${h}`, {
15
15
  method: "POST",
16
16
  headers: {
17
17
  "content-type": "application/json",
@@ -23,22 +23,57 @@ 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 v = "/internal/sync-config";
36
+ function y(e) {
37
+ let { dispatcher: t, scriptNameTemplate: n = p, internalSecret: i, controlPlaneTenantId: a, controlPlaneAdapters: o, maxDepth: s, timeoutMs: c = m } = e;
38
+ return async (e, { dryRun: l, force: u }) => {
39
+ let d = await r(o, e, {
40
+ legacyControlPlane: a,
41
+ maxDepth: s
42
+ }), f = g(n, e), p = new AbortController(), m = setTimeout(() => p.abort(), c), h;
43
+ try {
44
+ h = await t.get(f).fetch(`https://tenant.internal${v}`, {
45
+ method: "POST",
46
+ headers: {
47
+ "content-type": "application/json",
48
+ authorization: `Bearer ${i}`
49
+ },
50
+ body: JSON.stringify({
51
+ tenantId: e,
52
+ resolved: d,
53
+ dryRun: l,
54
+ force: u
55
+ }),
56
+ signal: p.signal
57
+ });
58
+ } finally {
59
+ clearTimeout(m);
60
+ }
26
61
  if (!h.ok) {
27
62
  let e = await h.text().catch(() => ""), t = h.headers.get("x-authhero-error");
28
- throw Error(`sync-defaults push to "${u}" failed: ${h.status}${t ? ` (${t})` : ""} ${e.slice(0, 256)}`);
63
+ throw Error(`sync-config push to "${f}" failed: ${h.status}${t ? ` (${t})` : ""} ${e.slice(0, 256)}`);
29
64
  }
30
65
  return await h.json();
31
66
  };
32
67
  }
33
68
  //#endregion
34
69
  //#region src/wfp/dispatch-renew-signing-certificates.ts
35
- var g = "/internal/renew-signing-certificates";
36
- function _(e) {
37
- let { dispatcher: t, scriptNameTemplate: n = d, internalSecret: r, timeoutMs: i = f } = e;
70
+ var b = "/internal/renew-signing-certificates";
71
+ function x(e) {
72
+ let { dispatcher: t, scriptNameTemplate: n = p, internalSecret: r, timeoutMs: i = m } = e;
38
73
  return async (e) => {
39
- let a = m(n, e), o = new AbortController(), s = setTimeout(() => o.abort(), i);
74
+ let a = g(n, e), o = new AbortController(), s = setTimeout(() => o.abort(), i);
40
75
  try {
41
- let e = await t.get(a).fetch(`https://tenant.internal${g}`, {
76
+ let e = await t.get(a).fetch(`https://tenant.internal${b}`, {
42
77
  method: "POST",
43
78
  headers: { authorization: `Bearer ${r}` },
44
79
  signal: o.signal
@@ -48,7 +83,7 @@ function _(e) {
48
83
  throw Error(`signing-certificate renewal for "${a}" failed: ${e.status}${n ? ` (${n})` : ""} ${t.slice(0, 512)}`);
49
84
  }
50
85
  let n = await e.json();
51
- if (!v(n)) throw Error(`signing-certificate renewal for "${a}" returned an unexpected body`);
86
+ if (!S(n)) throw Error(`signing-certificate renewal for "${a}" returned an unexpected body`);
52
87
  return {
53
88
  renewed: n.renewed,
54
89
  notDue: n.notDue
@@ -58,13 +93,13 @@ function _(e) {
58
93
  }
59
94
  };
60
95
  }
61
- function v(e) {
96
+ function S(e) {
62
97
  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
98
  }
64
99
  //#endregion
65
100
  //#region src/wfp/tenant-app.ts
66
- var y = "cp", b = "/internal/sync-defaults", x = "/internal/renew-signing-certificates";
67
- function S(e, t, n, r) {
101
+ var C = "cp", w = "/internal/sync-defaults", T = "/internal/sync-config", E = "/internal/renew-signing-certificates";
102
+ function D(e, t, n, r) {
68
103
  console.error(`[wfp-tenant] ${t}:`, n);
69
104
  let i = n instanceof Error ? n.message : String(n);
70
105
  return e.header("X-Authhero-Error", t), e.json({
@@ -73,74 +108,99 @@ function S(e, t, n, r) {
73
108
  result: r
74
109
  }, 500);
75
110
  }
76
- function C(e) {
77
- let t = `${a}${e.tenantId}`;
111
+ function O(e) {
112
+ let t = `${s}${e.tenantId}`;
78
113
  return {
79
114
  issuer: e.issuer,
80
115
  validityDays: e.validityDays,
81
116
  subjectUri: () => t
82
117
  };
83
118
  }
84
- function w(e) {
119
+ function k(e) {
85
120
  return ({ tenant_id: t }) => t === e.CONTROL_PLANE_TENANT_ID ? [e.CONTROL_PLANE_ISSUER] : [];
86
121
  }
87
- async function T(t, a) {
88
- let d = t.CONTROL_PLANE_TENANT_ID, f = await l(t.ENCRYPTION_KEY), p = t.CONTROL_PLANE_ENCRYPTION_KEY ? {
89
- default: f,
90
- keys: { [y]: await l(t.CONTROL_PLANE_ENCRYPTION_KEY) }
91
- } : { default: f }, m = o(a.createDataAdapter(t), p, { resolveEncryptKeyId: (e) => e === d ? y : void 0 }), h = n(m, { controlPlaneTenantId: d }), g = a.signingCertificateAuthority?.(t), _ = g ? C(g) : void 0, v = {
92
- dataAdapter: h,
93
- ..._ ? { signingCertificateAuthority: _ } : {},
94
- additionalIssuers: (a.additionalIssuers ?? w)(t)
95
- }, T = a.configure ? a.configure(v, t) : v, E = T.signingCertificateAuthority, { app: D } = c(T), O = new r();
96
- return O.post(b, async (n) => {
97
- let r = t.WFP_INTERNAL_SYNC_SECRET, i = n.req.header("authorization");
98
- if (!r || i !== `Bearer ${r}`) return n.json({ error: "unauthorized" }, 401);
122
+ async function A(n, r) {
123
+ let s = n.CONTROL_PLANE_TENANT_ID, p = await d(n.ENCRYPTION_KEY), m = n.CONTROL_PLANE_ENCRYPTION_KEY ? {
124
+ default: p,
125
+ keys: { [C]: await d(n.CONTROL_PLANE_ENCRYPTION_KEY) }
126
+ } : { default: p }, h = r.createDataAdapter(n), g = c(h, m, { resolveEncryptKeyId: (e) => e === s ? C : void 0 }), _ = i(g, { controlPlaneTenantId: s }), v = r.signingCertificateAuthority?.(n), y = v ? O(v) : void 0, b = {
127
+ dataAdapter: _,
128
+ ...y ? { signingCertificateAuthority: y } : {},
129
+ additionalIssuers: (r.additionalIssuers ?? k)(n)
130
+ }, x = r.configure ? r.configure(b, n) : b, S = x.signingCertificateAuthority, { app: A } = u(x), j = new a();
131
+ return j.post(w, async (t) => {
132
+ let i = n.WFP_INTERNAL_SYNC_SECRET, a = t.req.header("authorization");
133
+ if (!i || a !== `Bearer ${i}`) return t.json({ error: "unauthorized" }, 401);
99
134
  let o;
100
135
  try {
101
- o = await n.req.json();
136
+ o = await t.req.json();
102
137
  } catch {
103
- return n.json({ error: "invalid JSON" }, 400);
138
+ return t.json({ error: "invalid JSON" }, 400);
104
139
  }
105
140
  let c;
106
141
  try {
107
- c = await e(o, m, d, { continueOnError: a.continueOnError ?? !0 });
142
+ c = await e(o, g, s, { continueOnError: r.continueOnError ?? !0 });
108
143
  } catch (e) {
109
- return S(n, "sync_defaults_apply_failed", e);
144
+ return D(t, "sync_defaults_apply_failed", e);
110
145
  }
111
- let l;
146
+ let u;
112
147
  try {
113
- l = { created: (await s(m.keys, {
114
- name: t.ISSUER,
115
- certificateAuthority: E
148
+ u = { created: (await l(g.keys, {
149
+ name: n.ISSUER,
150
+ certificateAuthority: S
116
151
  })).created };
117
152
  } catch (e) {
118
- return S(n, "sync_defaults_signing_key_failed", e, c);
153
+ return D(t, "sync_defaults_signing_key_failed", e, c);
119
154
  }
120
- if (a.onSyncResult) try {
121
- await a.onSyncResult(c, t);
155
+ if (r.onSyncResult) try {
156
+ await r.onSyncResult(c, n);
122
157
  } catch (e) {
123
- return S(n, "sync_defaults_on_result_failed", e, c);
158
+ return D(t, "sync_defaults_on_result_failed", e, c);
124
159
  }
125
- return n.json({
160
+ return t.json({
126
161
  ...c,
127
- signingKey: l
162
+ signingKey: u
128
163
  });
129
- }), O.post(x, async (e) => {
130
- let n = t.WFP_INTERNAL_SYNC_SECRET, r = e.req.header("authorization");
131
- if (!n || r !== `Bearer ${n}`) return e.json({ error: "unauthorized" }, 401);
132
- if (!E) return e.json({ error: "no signing certificate authority" }, 404);
164
+ }), j.post(T, async (e) => {
165
+ let i = n.WFP_INTERNAL_SYNC_SECRET, a = e.req.header("authorization");
166
+ if (!i || a !== `Bearer ${i}`) return e.json({ error: "unauthorized" }, 401);
167
+ let o;
168
+ try {
169
+ o = await e.req.json();
170
+ } catch {
171
+ return e.json({ error: "invalid JSON" }, 400);
172
+ }
173
+ if (!o.tenantId || !o.resolved) return e.json({ error: "tenantId and resolved are required" }, 400);
174
+ 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);
175
+ try {
176
+ let n = await t(o.resolved, h, o.tenantId, {
177
+ dryRun: o.dryRun === !0,
178
+ force: o.force === !0,
179
+ keyRing: m,
180
+ continueOnError: r.continueOnError ?? !0
181
+ });
182
+ return e.json(n);
183
+ } catch (t) {
184
+ 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({
185
+ error: "sync_config_key_ring",
186
+ detail: t.message
187
+ }, 422)) : D(e, "sync_config_apply_failed", t);
188
+ }
189
+ }), j.post(E, async (e) => {
190
+ let t = n.WFP_INTERNAL_SYNC_SECRET, r = e.req.header("authorization");
191
+ if (!t || r !== `Bearer ${t}`) return e.json({ error: "unauthorized" }, 401);
192
+ if (!S) return e.json({ error: "no signing certificate authority" }, 404);
133
193
  try {
134
- let t = await u({
135
- dataAdapter: { keys: m.keys },
136
- certificateAuthority: E
194
+ let t = await f({
195
+ dataAdapter: { keys: g.keys },
196
+ certificateAuthority: S
137
197
  });
138
198
  return e.json({
139
199
  renewed: t.renewed,
140
200
  notDue: t.notDue
141
201
  });
142
202
  } catch (t) {
143
- return t instanceof i ? (console.error("[wfp-tenant] signing_certificate_renewal_failed:", t), e.header("X-Authhero-Error", "signing_certificate_renewal_failed"), e.json({
203
+ return t instanceof o ? (console.error("[wfp-tenant] signing_certificate_renewal_failed:", t), e.header("X-Authhero-Error", "signing_certificate_renewal_failed"), e.json({
144
204
  error: "signing_certificate_renewal_failed",
145
205
  renewed: t.result.renewed,
146
206
  notDue: t.result.notDue,
@@ -148,16 +208,16 @@ async function T(t, a) {
148
208
  kid: e,
149
209
  detail: t instanceof Error ? t.message : String(t)
150
210
  }))
151
- }, 500)) : S(e, "signing_certificate_renewal_failed", t);
211
+ }, 500)) : D(e, "signing_certificate_renewal_failed", t);
152
212
  }
153
- }), O.route("/", D), O;
213
+ }), j.route("/", A), j;
154
214
  }
155
- function E(e) {
215
+ function j(e) {
156
216
  let t = /* @__PURE__ */ new WeakMap();
157
217
  return { fetch(n, r, i) {
158
218
  let a = t.get(r);
159
- return a || (a = T(r, e), t.set(r, a)), a.then((e) => e.fetch(n, r, i));
219
+ return a || (a = A(r, e), t.set(r, a)), a.then((e) => e.fetch(n, r, i));
160
220
  } };
161
221
  }
162
222
  //#endregion
163
- export { _ as createDispatchRenewSigningCertificates, h as createDispatchSyncDefaults, E as createWfpTenantApp };
223
+ export { x as createDispatchRenewSigningCertificates, y as createDispatchSyncConfig, _ as createDispatchSyncDefaults, j as createWfpTenantApp };
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./sync-defaults-errors-Htp1NWTq.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=class extends Error{checks;constructor(e,t){super(e),this.name=`TenantProvisionVerificationError`,this.checks=t}};function s(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 c(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;`),c=s(a,`key_count`),l=s(a,`tenant_count`),u=[];if(c<n&&u.push(`expected at least ${n} signing key(s) in "keys", found ${c}`),l<1&&u.push(`tenant row "${i}" missing from "tenants"`),u.length>0)throw new o(`Tenant D1 verification failed for "${i}": ${u.join(`; `)}`,{keyCount:c,tenantRowCount:l})}}function l(e){let t=e instanceof Error?e.message:String(e??``);return/already exists|already been created|duplicate/i.test(t)}function u(e){return{engine:`cloudflare-workflows`,async start(n){if(n.kind!==`provision`)throw Error(`The Cloudflare Workflows executor does not support "${n.kind}" operations yet (phase 2 covers provision only)`);if(!n.tenant_id)throw Error(`provision 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(!l(e))throw e}}}}var d=`cloudflare-workflows`;async function f(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:d,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 p(t)}catch(n){u.errors++,e.logger?.warn(`Failed to reconcile tenant operation ${t.id}:`,n)}return u;async function f(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 p(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 f(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 f(o,e);return}if(l.status===`complete`){(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 f(o,`engine instance completed but the operation was never finalized and the tenant is not ready`));return}u.stillRunning++}}function p(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=d,exports.TenantProvisionVerificationError=o,exports.createCloudflareWorkflowsExecutor=u,exports.createProvisionVerifier=c,exports.createWfpWorkflowProvisioningHook=p,exports.reconcileTenantOperations=f,exports.runProvisionOperation=a;
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=class extends Error{checks;constructor(e,t){super(e),this.name=`TenantProvisionVerificationError`,this.checks=t}};function u(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 d(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=u(a,`key_count`),s=u(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 l(`Tenant D1 verification failed for "${i}": ${c.join(`; `)}`,{keyCount:o,tenantRowCount:s})}}function f(e){let t=e instanceof Error?e.message:String(e??``);return/already exists|already been created|duplicate/i.test(t)}function p(e){return{engine:`cloudflare-workflows`,async start(n){if(n.kind!==`provision`&&n.kind!==`sync-defaults`)throw Error(`The Cloudflare Workflows executor does not support "${n.kind}" operations yet (it runs provision and sync-defaults)`);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(!f(e))throw e}}}}var m=`cloudflare-workflows`;async function h(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:m,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`){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 g(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=m,exports.TenantProvisionVerificationError=l,exports.createCloudflareWorkflowsExecutor=p,exports.createProvisionVerifier=d,exports.createWfpWorkflowProvisioningHook=g,exports.reconcileTenantOperations=h,exports.runProvisionOperation=a,exports.runSyncDefaultsOperation=c;