@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.
- 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-sync-config.d.ts +37 -0
- package/dist/types/wfp/index.d.ts +1 -0
- package/dist/types/wfp/tenant-app.d.ts +9 -0
- package/dist/types/workflows/entrypoint.example.d.ts +40 -3
- package/dist/types/workflows/index.d.ts +1 -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 +47 -3
- package/dist/wfp.mjs +121 -61
- package/dist/workflows.cjs +1 -1
- package/dist/workflows.d.ts +32 -5
- package/dist/workflows.mjs +94 -41
- package/package.json +5 -5
|
@@ -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 {
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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,
|
|
2
|
-
import { Hono as
|
|
3
|
-
import { SigningCertificateRenewalError as
|
|
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
|
|
6
|
-
function
|
|
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
|
|
10
|
-
let { dispatcher:
|
|
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
|
|
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
|
-
|
|
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-
|
|
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
|
|
36
|
-
function
|
|
37
|
-
let { dispatcher: t, scriptNameTemplate: n =
|
|
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 =
|
|
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${
|
|
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 (!
|
|
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
|
|
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
|
|
67
|
-
function
|
|
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
|
|
77
|
-
let t = `${
|
|
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
|
|
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
|
|
88
|
-
let
|
|
89
|
-
default:
|
|
90
|
-
keys: { [
|
|
91
|
-
} : { default:
|
|
92
|
-
dataAdapter:
|
|
93
|
-
...
|
|
94
|
-
additionalIssuers: (
|
|
95
|
-
},
|
|
96
|
-
return
|
|
97
|
-
let
|
|
98
|
-
if (!
|
|
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
|
|
136
|
+
o = await t.req.json();
|
|
102
137
|
} catch {
|
|
103
|
-
return
|
|
138
|
+
return t.json({ error: "invalid JSON" }, 400);
|
|
104
139
|
}
|
|
105
140
|
let c;
|
|
106
141
|
try {
|
|
107
|
-
c = await e(o,
|
|
142
|
+
c = await e(o, g, s, { continueOnError: r.continueOnError ?? !0 });
|
|
108
143
|
} catch (e) {
|
|
109
|
-
return
|
|
144
|
+
return D(t, "sync_defaults_apply_failed", e);
|
|
110
145
|
}
|
|
111
|
-
let
|
|
146
|
+
let u;
|
|
112
147
|
try {
|
|
113
|
-
|
|
114
|
-
name:
|
|
115
|
-
certificateAuthority:
|
|
148
|
+
u = { created: (await l(g.keys, {
|
|
149
|
+
name: n.ISSUER,
|
|
150
|
+
certificateAuthority: S
|
|
116
151
|
})).created };
|
|
117
152
|
} catch (e) {
|
|
118
|
-
return
|
|
153
|
+
return D(t, "sync_defaults_signing_key_failed", e, c);
|
|
119
154
|
}
|
|
120
|
-
if (
|
|
121
|
-
await
|
|
155
|
+
if (r.onSyncResult) try {
|
|
156
|
+
await r.onSyncResult(c, n);
|
|
122
157
|
} catch (e) {
|
|
123
|
-
return
|
|
158
|
+
return D(t, "sync_defaults_on_result_failed", e, c);
|
|
124
159
|
}
|
|
125
|
-
return
|
|
160
|
+
return t.json({
|
|
126
161
|
...c,
|
|
127
|
-
signingKey:
|
|
162
|
+
signingKey: u
|
|
128
163
|
});
|
|
129
|
-
}),
|
|
130
|
-
let
|
|
131
|
-
if (!
|
|
132
|
-
|
|
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
|
|
135
|
-
dataAdapter: { keys:
|
|
136
|
-
certificateAuthority:
|
|
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
|
|
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)) :
|
|
211
|
+
}, 500)) : D(e, "signing_certificate_renewal_failed", t);
|
|
152
212
|
}
|
|
153
|
-
}),
|
|
213
|
+
}), j.route("/", A), j;
|
|
154
214
|
}
|
|
155
|
-
function
|
|
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 =
|
|
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 {
|
|
223
|
+
export { x as createDispatchRenewSigningCertificates, y as createDispatchSyncConfig, _ as createDispatchSyncDefaults, j 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=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;
|